aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSadaf Ebrahimi <sadafebrahimi@google.com>2024-03-12 17:24:49 +0000
committerSadaf Ebrahimi <sadafebrahimi@google.com>2024-03-12 17:39:01 +0000
commit15c162f4666fec92509f7c8ea62308e2234a6067 (patch)
tree9f47d34cae17b90863bad0b94e6c9b3edd7b2b39
parente42fff9d341bc5cc34c7d9dfa1950fd97f7ff77b (diff)
parent23d4b31de15a723716f9fde5ec736773f2e1aa2e (diff)
downloadlibopus-15c162f4666fec92509f7c8ea62308e2234a6067.tar.gz
Upgrade libopus to v1.5
This project was upgraded with external_updater. Usage: tools/external_updater/updater.sh update external/libopus For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md Test: TreeHugger Change-Id: I5e8ccb33e729bd87984107ac9fc99987d8ff79f4
-rw-r--r--.appveyor.yml37
-rw-r--r--.github/workflows/autotools.yml51
-rw-r--r--.github/workflows/cmake.yml252
-rw-r--r--.github/workflows/dred.yml121
-rw-r--r--.github/workflows/repository.yml15
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml99
-rw-r--r--.travis.yml21
-rw-r--r--Android.bp2
-rw-r--r--CMakeLists.txt138
-rw-r--r--COPYING4
-rw-r--r--METADATA20
-rw-r--r--Makefile.am153
-rw-r--r--Makefile.mips14
-rw-r--r--Makefile.unix14
-rw-r--r--README30
-rw-r--r--autogen.bat13
-rwxr-xr-xautogen.sh2
-rw-r--r--celt/arm/arm_celt_map.c31
-rw-r--r--celt/arm/armcpu.c51
-rw-r--r--celt/arm/armcpu.h13
-rw-r--r--celt/arm/celt_neon_intr.c83
-rw-r--r--celt/arm/pitch_neon_intr.c7
-rw-r--r--celt/bands.c2
-rw-r--r--celt/celt.h25
-rw-r--r--celt/celt_decoder.c360
-rw-r--r--celt/celt_encoder.c68
-rw-r--r--celt/celt_lpc.c27
-rw-r--r--celt/celt_lpc.h2
-rw-r--r--celt/cpu_support.h7
-rw-r--r--celt/dump_modes/Makefile1
-rw-r--r--celt/entdec.c21
-rw-r--r--celt/entdec.h10
-rw-r--r--celt/entenc.c11
-rw-r--r--celt/entenc.h9
-rw-r--r--celt/laplace.c101
-rw-r--r--celt/laplace.h9
-rw-r--r--celt/mathops.h6
-rw-r--r--celt/meson.build4
-rw-r--r--celt/mips/celt_mipsr1.h6
-rw-r--r--celt/mips/mdct_mipsr1.h6
-rw-r--r--celt/mips/vq_mipsr1.h6
-rw-r--r--celt/os_support.h14
-rw-r--r--celt/pitch.c11
-rw-r--r--celt/pitch.h11
-rw-r--r--celt/stack_alloc.h2
-rw-r--r--celt/tests/test_unit_cwrs32.c1
-rw-r--r--celt/tests/test_unit_dft.c1
-rw-r--r--celt/tests/test_unit_laplace.c1
-rw-r--r--celt/tests/test_unit_mdct.c1
-rw-r--r--celt/tests/test_unit_rotation.c1
-rw-r--r--celt/x86/celt_lpc_sse4_1.c13
-rw-r--r--celt/x86/pitch_avx.c101
-rw-r--r--celt/x86/pitch_sse.h40
-rw-r--r--celt/x86/vq_sse.h6
-rw-r--r--celt/x86/vq_sse2.c8
-rw-r--r--celt/x86/x86_arch_macros.h47
-rw-r--r--celt/x86/x86_celt_map.c20
-rw-r--r--celt/x86/x86cpu.c16
-rw-r--r--celt/x86/x86cpu.h49
-rw-r--r--celt_headers.mk1
-rw-r--r--celt_sources.mk3
-rw-r--r--cmake/OpusConfig.cmake5
-rw-r--r--cmake/OpusFunctions.cmake22
-rw-r--r--cmake/OpusSources.cmake20
-rw-r--r--cmake/README.md132
-rw-r--r--configure.ac194
-rw-r--r--dnn/LPCNet.yml24
-rw-r--r--dnn/README1
-rw-r--r--dnn/README.md126
-rw-r--r--dnn/adaconvtest.c449
-rw-r--r--dnn/arm/arm_dnn_map.c88
-rw-r--r--dnn/arm/dnn_arm.h104
-rw-r--r--dnn/arm/nnet_dotprod.c38
-rw-r--r--dnn/arm/nnet_neon.c38
-rw-r--r--dnn/burg.c246
-rw-r--r--dnn/burg.h (renamed from win32/config.h)45
-rw-r--r--dnn/common.h56
-rw-r--r--dnn/datasets.txt173
-rw-r--r--dnn/download_model.bat9
-rwxr-xr-xdnn/download_model.sh10
-rw-r--r--dnn/dred_coding.c44
-rw-r--r--dnn/dred_coding.h36
-rw-r--r--dnn/dred_config.h54
-rw-r--r--dnn/dred_decoder.c129
-rw-r--r--dnn/dred_decoder.h49
-rw-r--r--dnn/dred_encoder.c363
-rw-r--r--dnn/dred_encoder.h71
-rw-r--r--dnn/dred_rdovae.h42
-rw-r--r--dnn/dred_rdovae_dec.c139
-rw-r--r--dnn/dred_rdovae_dec.h53
-rw-r--r--dnn/dred_rdovae_enc.c110
-rw-r--r--dnn/dred_rdovae_enc.h52
-rw-r--r--dnn/dump_data.c280
-rw-r--r--dnn/dump_lpcnet_tables.c104
-rw-r--r--dnn/fargan.c225
-rw-r--r--dnn/fargan.h68
-rw-r--r--dnn/freq.c328
-rw-r--r--dnn/freq.h61
-rw-r--r--dnn/fwgan.c322
-rw-r--r--dnn/fwgan.h83
-rw-r--r--dnn/kiss99.c81
-rw-r--r--dnn/kiss99.h46
-rw-r--r--dnn/lossgen.c196
-rw-r--r--dnn/lossgen.h55
-rw-r--r--dnn/lossgen_demo.c22
-rw-r--r--dnn/lpcnet.c283
-rw-r--r--dnn/lpcnet.h183
-rw-r--r--dnn/lpcnet_demo.c217
-rw-r--r--dnn/lpcnet_enc.c230
-rw-r--r--dnn/lpcnet_plc.c211
-rw-r--r--dnn/lpcnet_private.h90
-rw-r--r--dnn/lpcnet_tables.c307
-rw-r--r--dnn/meson.build64
-rw-r--r--dnn/nndsp.c416
-rw-r--r--dnn/nndsp.h143
-rw-r--r--dnn/nnet.c149
-rw-r--r--dnn/nnet.h163
-rw-r--r--dnn/nnet_arch.h247
-rw-r--r--dnn/nnet_default.c35
-rw-r--r--dnn/osce.c1419
-rw-r--r--dnn/osce.h84
-rw-r--r--dnn/osce_config.h60
-rw-r--r--dnn/osce_features.c454
-rw-r--r--dnn/osce_features.h50
-rw-r--r--dnn/osce_structs.h125
-rw-r--r--dnn/parse_lpcnet_weights.c238
-rw-r--r--dnn/pitchdnn.c79
-rw-r--r--dnn/pitchdnn.h34
-rw-r--r--dnn/tansig_table.h50
-rw-r--r--dnn/test_vec.c128
-rw-r--r--dnn/torch/dnntools/dnntools/__init__.py2
-rw-r--r--dnn/torch/dnntools/dnntools/quantization/__init__.py1
-rw-r--r--dnn/torch/dnntools/dnntools/quantization/softquant.py113
-rw-r--r--dnn/torch/dnntools/dnntools/relegance/__init__.py2
-rw-r--r--dnn/torch/dnntools/dnntools/relegance/meta_critic.py85
-rw-r--r--dnn/torch/dnntools/dnntools/relegance/relegance.py449
-rw-r--r--dnn/torch/dnntools/dnntools/sparsification/__init__.py6
-rw-r--r--dnn/torch/dnntools/dnntools/sparsification/base_sparsifier.py58
-rw-r--r--dnn/torch/dnntools/dnntools/sparsification/common.py123
-rw-r--r--dnn/torch/dnntools/dnntools/sparsification/conv1d_sparsifier.py133
-rw-r--r--dnn/torch/dnntools/dnntools/sparsification/conv_transpose1d_sparsifier.py134
-rw-r--r--dnn/torch/dnntools/dnntools/sparsification/gru_sparsifier.py178
-rw-r--r--dnn/torch/dnntools/dnntools/sparsification/linear_sparsifier.py128
-rw-r--r--dnn/torch/dnntools/dnntools/sparsification/utils.py64
-rw-r--r--dnn/torch/dnntools/requirements.txt1
-rw-r--r--dnn/torch/dnntools/setup.py48
-rw-r--r--dnn/torch/fargan/adv_train_fargan.py277
-rw-r--r--dnn/torch/fargan/dataset.py61
-rw-r--r--dnn/torch/fargan/dump_fargan_weights.py112
-rw-r--r--dnn/torch/fargan/fargan.py322
-rw-r--r--dnn/torch/fargan/filters.py46
-rw-r--r--dnn/torch/fargan/rc.py29
-rw-r--r--dnn/torch/fargan/stft_loss.py186
-rw-r--r--dnn/torch/fargan/test_fargan.py128
-rw-r--r--dnn/torch/fargan/train_fargan.py168
-rw-r--r--dnn/torch/fwgan/dump_model_weights.py88
-rw-r--r--dnn/torch/fwgan/inference.py141
-rw-r--r--dnn/torch/fwgan/models/__init__.py7
-rw-r--r--dnn/torch/fwgan/models/fwgan400.py308
-rw-r--r--dnn/torch/fwgan/models/fwgan500.py260
-rw-r--r--dnn/torch/lossgen/README.md27
-rw-r--r--dnn/torch/lossgen/export_lossgen.py101
-rw-r--r--dnn/torch/lossgen/lossgen.py29
-rwxr-xr-xdnn/torch/lossgen/process_data.sh17
-rw-r--r--dnn/torch/lossgen/test_lossgen.py42
-rw-r--r--dnn/torch/lossgen/train_lossgen.py99
-rw-r--r--dnn/torch/lpcnet/README.md27
-rw-r--r--dnn/torch/lpcnet/add_dataset_config.py77
-rw-r--r--dnn/torch/lpcnet/data/__init__.py1
-rw-r--r--dnn/torch/lpcnet/data/lpcnet_dataset.py227
-rw-r--r--dnn/torch/lpcnet/engine/lpcnet_engine.py141
-rw-r--r--dnn/torch/lpcnet/make_default_setup.py56
-rw-r--r--dnn/torch/lpcnet/make_test_config.py78
-rw-r--r--dnn/torch/lpcnet/models/__init__.py8
-rw-r--r--dnn/torch/lpcnet/models/lpcnet.py303
-rw-r--r--dnn/torch/lpcnet/models/multi_rate_lpcnet.py437
-rw-r--r--dnn/torch/lpcnet/print_lpcnet_complexity.py64
-rw-r--r--dnn/torch/lpcnet/scripts/collect_multi_run_results.py190
-rw-r--r--dnn/torch/lpcnet/scripts/loop_run.sh52
-rw-r--r--dnn/torch/lpcnet/scripts/make_animation.py67
-rw-r--r--dnn/torch/lpcnet/scripts/modify_dataset_target.py17
-rw-r--r--dnn/torch/lpcnet/scripts/multi_run.sh17
-rw-r--r--dnn/torch/lpcnet/scripts/run_inference_test.sh22
-rw-r--r--dnn/torch/lpcnet/scripts/update_checkpoints.py54
-rw-r--r--dnn/torch/lpcnet/scripts/update_output_folder.sh22
-rw-r--r--dnn/torch/lpcnet/scripts/update_setups.py57
-rw-r--r--dnn/torch/lpcnet/test_lpcnet.py89
-rw-r--r--dnn/torch/lpcnet/train_lpcnet.py272
-rw-r--r--dnn/torch/lpcnet/utils/__init__.py4
-rw-r--r--dnn/torch/lpcnet/utils/data.py141
-rw-r--r--dnn/torch/lpcnet/utils/endoscopy.py234
-rw-r--r--dnn/torch/lpcnet/utils/layers/__init__.py3
-rw-r--r--dnn/torch/lpcnet/utils/layers/dual_fc.py44
-rw-r--r--dnn/torch/lpcnet/utils/layers/pcm_embeddings.py71
-rw-r--r--dnn/torch/lpcnet/utils/layers/subconditioner.py497
-rw-r--r--dnn/torch/lpcnet/utils/misc.py65
-rw-r--r--dnn/torch/lpcnet/utils/pcm.py35
-rw-r--r--dnn/torch/lpcnet/utils/sample.py44
-rw-r--r--dnn/torch/lpcnet/utils/sparsification/__init__.py2
-rw-r--r--dnn/torch/lpcnet/utils/sparsification/common.py121
-rw-r--r--dnn/torch/lpcnet/utils/sparsification/gru_sparsifier.py187
-rw-r--r--dnn/torch/lpcnet/utils/templates.py157
-rw-r--r--dnn/torch/lpcnet/utils/ulaw.py58
-rw-r--r--dnn/torch/lpcnet/utils/wav.py43
-rw-r--r--dnn/torch/neural-pitch/README.md18
-rw-r--r--dnn/torch/neural-pitch/data_augmentation.py149
-rw-r--r--dnn/torch/neural-pitch/download_demand.sh43
-rw-r--r--dnn/torch/neural-pitch/evaluation.py349
-rw-r--r--dnn/torch/neural-pitch/experiments.py38
-rw-r--r--dnn/torch/neural-pitch/export_neuralpitch_weights.py109
-rw-r--r--dnn/torch/neural-pitch/models.py178
-rw-r--r--dnn/torch/neural-pitch/neural_pitch_update.py179
-rw-r--r--dnn/torch/neural-pitch/ptdb_process.sh34
-rw-r--r--dnn/torch/neural-pitch/run_crepe.py72
-rw-r--r--dnn/torch/neural-pitch/training.py162
-rw-r--r--dnn/torch/neural-pitch/utils.py59
-rw-r--r--dnn/torch/osce/README.md65
-rw-r--r--dnn/torch/osce/adv_train_model.py462
-rw-r--r--dnn/torch/osce/adv_train_vocoder.py451
-rw-r--r--dnn/torch/osce/create_testvectors.py165
-rw-r--r--dnn/torch/osce/data/__init__.py2
-rw-r--r--dnn/torch/osce/data/lpcnet_vocoding_dataset.py225
-rw-r--r--dnn/torch/osce/data/silk_conversion_set.py132
-rw-r--r--dnn/torch/osce/data/silk_enhancement_set.py140
-rw-r--r--dnn/torch/osce/engine/engine.py103
-rw-r--r--dnn/torch/osce/engine/vocoder_engine.py101
-rw-r--r--dnn/torch/osce/export_model_weights.py174
-rw-r--r--dnn/torch/osce/losses/stft_loss.py277
-rw-r--r--dnn/torch/osce/make_default_setup.py93
-rw-r--r--dnn/torch/osce/models/__init__.py42
-rw-r--r--dnn/torch/osce/models/fd_discriminator.py974
-rw-r--r--dnn/torch/osce/models/lace.py190
-rw-r--r--dnn/torch/osce/models/lavoce.py274
-rw-r--r--dnn/torch/osce/models/lavoce_400.py254
-rw-r--r--dnn/torch/osce/models/lpcnet_feature_net.py91
-rw-r--r--dnn/torch/osce/models/nns_base.py69
-rw-r--r--dnn/torch/osce/models/no_lace.py218
-rw-r--r--dnn/torch/osce/models/scale_embedding.py68
-rw-r--r--dnn/torch/osce/models/shape_up_48.py179
-rw-r--r--dnn/torch/osce/models/silk_feature_net.py86
-rw-r--r--dnn/torch/osce/models/silk_feature_net_pl.py127
-rw-r--r--dnn/torch/osce/requirements.txt9
-rw-r--r--dnn/torch/osce/stndrd/evaluation/create_input_data.sh25
-rw-r--r--dnn/torch/osce/stndrd/evaluation/env.rc7
-rw-r--r--dnn/torch/osce/stndrd/evaluation/evaluate.py113
-rw-r--r--dnn/torch/osce/stndrd/evaluation/lace_loss_metric.py330
-rw-r--r--dnn/torch/osce/stndrd/evaluation/make_boxplots.py116
-rw-r--r--dnn/torch/osce/stndrd/evaluation/make_boxplots_moctest.py109
-rw-r--r--dnn/torch/osce/stndrd/evaluation/make_tables.py124
-rw-r--r--dnn/torch/osce/stndrd/evaluation/make_tables_moctest.py121
-rw-r--r--dnn/torch/osce/stndrd/evaluation/moc.py182
-rw-r--r--dnn/torch/osce/stndrd/evaluation/moc2.py190
-rwxr-xr-xdnn/torch/osce/stndrd/evaluation/process_dataset.sh98
-rw-r--r--dnn/torch/osce/stndrd/evaluation/run_nomad.py138
-rw-r--r--dnn/torch/osce/stndrd/presentation/endoscopy.py205
-rw-r--r--dnn/torch/osce/stndrd/presentation/lace_demo.ipynb313
-rw-r--r--dnn/torch/osce/stndrd/presentation/linear_prediction.ipynb320
-rw-r--r--dnn/torch/osce/stndrd/presentation/playback.py25
-rw-r--r--dnn/torch/osce/stndrd/presentation/postfilter.ipynb275
-rw-r--r--dnn/torch/osce/stndrd/presentation/spectrogram.ipynb173
-rw-r--r--dnn/torch/osce/test_model.py96
-rw-r--r--dnn/torch/osce/test_vocoder.py103
-rw-r--r--dnn/torch/osce/train_model.py307
-rw-r--r--dnn/torch/osce/train_vocoder.py287
-rw-r--r--dnn/torch/osce/utils/ada_conv.py71
-rw-r--r--dnn/torch/osce/utils/complexity.py8
-rw-r--r--dnn/torch/osce/utils/endoscopy.py205
-rw-r--r--dnn/torch/osce/utils/layers/limited_adaptive_comb1d.py230
-rw-r--r--dnn/torch/osce/utils/layers/limited_adaptive_conv1d.py200
-rw-r--r--dnn/torch/osce/utils/layers/noise_shaper.py100
-rw-r--r--dnn/torch/osce/utils/layers/pitch_auto_correlator.py84
-rw-r--r--dnn/torch/osce/utils/layers/silk_upsampler.py167
-rw-r--r--dnn/torch/osce/utils/layers/td_shaper.py145
-rw-r--r--dnn/torch/osce/utils/lpcnet_features.py112
-rw-r--r--dnn/torch/osce/utils/misc.py95
-rw-r--r--dnn/torch/osce/utils/moc.py153
-rw-r--r--dnn/torch/osce/utils/pitch.py122
-rw-r--r--dnn/torch/osce/utils/silk_features.py144
-rw-r--r--dnn/torch/osce/utils/softquant.py110
-rw-r--r--dnn/torch/osce/utils/spec.py210
-rw-r--r--dnn/torch/osce/utils/templates.py347
-rw-r--r--dnn/torch/plc/export_plc.py100
-rw-r--r--dnn/torch/plc/plc.py144
-rw-r--r--dnn/torch/plc/plc_dataset.py60
-rw-r--r--dnn/torch/plc/train_plc.py145
-rw-r--r--dnn/torch/rdovae/README.md24
-rw-r--r--dnn/torch/rdovae/export_rdovae_weights.py365
-rw-r--r--dnn/torch/rdovae/fec_encoder.py212
-rw-r--r--dnn/torch/rdovae/import_rdovae_weights.py143
-rw-r--r--dnn/torch/rdovae/packets/__init__.py1
-rw-r--r--dnn/torch/rdovae/packets/fec_packets.c142
-rw-r--r--dnn/torch/rdovae/packets/fec_packets.h34
-rw-r--r--dnn/torch/rdovae/packets/fec_packets.py108
-rw-r--r--dnn/torch/rdovae/rdovae/__init__.py2
-rw-r--r--dnn/torch/rdovae/rdovae/dataset.py67
-rw-r--r--dnn/torch/rdovae/rdovae/rdovae.py719
-rw-r--r--dnn/torch/rdovae/requirements.txt4
-rw-r--r--dnn/torch/rdovae/train_rdovae.py278
-rw-r--r--dnn/torch/testsuite/README.md46
-rw-r--r--dnn/torch/testsuite/examples/lpcnet_c_example.yml6
-rw-r--r--dnn/torch/testsuite/examples/lpcnet_c_plc_example.yml5
-rw-r--r--dnn/torch/testsuite/examples/lpcnet_torch_example.yml5
-rw-r--r--dnn/torch/testsuite/requirements.txt12
-rw-r--r--dnn/torch/testsuite/run_test.py375
-rw-r--r--dnn/torch/testsuite/utils/__init__.py0
-rw-r--r--dnn/torch/testsuite/utils/files.py54
-rw-r--r--dnn/torch/testsuite/utils/pesq.py43
-rw-r--r--dnn/torch/testsuite/utils/pitch.py61
-rw-r--r--dnn/torch/weight-exchange/README.md21
-rw-r--r--dnn/torch/weight-exchange/requirements.txt1
-rw-r--r--dnn/torch/weight-exchange/setup.py48
-rw-r--r--dnn/torch/weight-exchange/wexchange/__init__.py30
-rw-r--r--dnn/torch/weight-exchange/wexchange/c_export/__init__.py31
-rw-r--r--dnn/torch/weight-exchange/wexchange/c_export/c_writer.py182
-rw-r--r--dnn/torch/weight-exchange/wexchange/c_export/common.py387
-rw-r--r--dnn/torch/weight-exchange/wexchange/tf/__init__.py5
-rw-r--r--dnn/torch/weight-exchange/wexchange/tf/tf.py188
-rw-r--r--dnn/torch/weight-exchange/wexchange/torch/__init__.py37
-rw-r--r--dnn/torch/weight-exchange/wexchange/torch/torch.py433
-rw-r--r--dnn/training_tf2/dataloader.py49
-rw-r--r--dnn/training_tf2/decode_rdovae.py111
-rw-r--r--dnn/training_tf2/diffembed.py49
-rwxr-xr-xdnn/training_tf2/dump_lpcnet.py388
-rwxr-xr-xdnn/training_tf2/dump_plc.py296
-rw-r--r--dnn/training_tf2/dump_rdovae.py306
-rw-r--r--dnn/training_tf2/encode_rdovae.py125
-rw-r--r--dnn/training_tf2/fec_encoder.py256
-rw-r--r--dnn/training_tf2/fec_packets.c142
-rw-r--r--dnn/training_tf2/fec_packets.h34
-rw-r--r--dnn/training_tf2/fec_packets.py108
-rw-r--r--dnn/training_tf2/keraslayerdump.py189
-rw-r--r--dnn/training_tf2/lossfuncs.py99
-rw-r--r--dnn/training_tf2/lpcnet.py339
-rw-r--r--dnn/training_tf2/lpcnet_plc.py101
-rw-r--r--dnn/training_tf2/mdense.py95
-rw-r--r--dnn/training_tf2/pade.py70
-rw-r--r--dnn/training_tf2/parameters.py29
-rw-r--r--dnn/training_tf2/plc_loader.py73
-rw-r--r--dnn/training_tf2/rdovae.py372
-rw-r--r--dnn/training_tf2/rdovae_exchange.py138
-rw-r--r--dnn/training_tf2/rdovae_import.py123
-rwxr-xr-xdnn/training_tf2/test_lpcnet.py120
-rw-r--r--dnn/training_tf2/test_plc.py92
-rw-r--r--dnn/training_tf2/tf_funcs.py70
-rwxr-xr-xdnn/training_tf2/train_lpcnet.py214
-rw-r--r--dnn/training_tf2/train_plc.py197
-rw-r--r--dnn/training_tf2/train_rdovae.py151
-rw-r--r--dnn/training_tf2/ulaw.py19
-rw-r--r--dnn/training_tf2/uniform_noise.py78
-rw-r--r--dnn/vec.h389
-rw-r--r--dnn/vec_avx.h884
-rw-r--r--dnn/vec_neon.h473
-rw-r--r--dnn/write_lpcnet_weights.c97
-rw-r--r--dnn/x86/dnn_x86.h121
-rw-r--r--dnn/x86/nnet_avx2.c40
-rw-r--r--dnn/x86/nnet_sse2.c40
-rw-r--r--dnn/x86/nnet_sse4_1.c40
-rw-r--r--dnn/x86/x86_dnn_map.c83
-rwxr-xr-xdoc/build_isobmff.sh2
-rw-r--r--doc/draft-ietf-codec-opus-update.xml20
-rw-r--r--doc/release.txt2
-rw-r--r--include/opus.h122
-rw-r--r--include/opus_defines.h29
-rw-r--r--include/opus_multistream.h2
-rw-r--r--lpcnet_headers.mk44
-rw-r--r--lpcnet_sources.mk44
-rw-r--r--meson.build76
-rw-r--r--meson/README.md88
-rw-r--r--meson_options.txt5
-rw-r--r--opus_headers.mk3
-rw-r--r--opus_sources.mk1
-rwxr-xr-xscripts/shrink_model.sh7
-rw-r--r--silk/API.h23
-rw-r--r--silk/NSQ.c14
-rw-r--r--silk/NSQ_del_dec.c22
-rw-r--r--silk/PLC.c61
-rw-r--r--silk/PLC.h3
-rw-r--r--silk/arm/NSQ_del_dec_arm.h4
-rw-r--r--silk/arm/NSQ_del_dec_neon_intr.c28
-rw-r--r--silk/arm/NSQ_neon.h4
-rw-r--r--silk/arm/arm_silk_map.c7
-rw-r--r--silk/control.h11
-rw-r--r--silk/dec_API.c69
-rw-r--r--silk/decode_frame.c60
-rw-r--r--silk/enc_API.c4
-rw-r--r--silk/fixed/encode_frame_FIX.c18
-rw-r--r--silk/float/SigProc_FLP.h14
-rw-r--r--silk/float/autocorrelation_FLP.c5
-rw-r--r--silk/float/burg_modified_FLP.c5
-rw-r--r--silk/float/corrMatrix_FLP.c10
-rw-r--r--silk/float/encode_frame_FLP.c17
-rw-r--r--silk/float/find_LPC_FLP.c7
-rw-r--r--silk/float/find_LTP_FLP.c7
-rw-r--r--silk/float/find_pitch_lags_FLP.c2
-rw-r--r--silk/float/find_pred_coefs_FLP.c4
-rw-r--r--silk/float/inner_product_FLP.c2
-rw-r--r--silk/float/main_FLP.h12
-rw-r--r--silk/float/noise_shape_analysis_FLP.c2
-rw-r--r--silk/float/pitch_analysis_core_FLP.c2
-rw-r--r--silk/float/warped_autocorrelation_FLP.c6
-rw-r--r--silk/float/x86/inner_product_FLP_avx2.c85
-rw-r--r--silk/init_decoder.c33
-rw-r--r--silk/init_encoder.c4
-rw-r--r--silk/main.h14
-rw-r--r--silk/meson.build12
-rw-r--r--silk/mips/NSQ_del_dec_mipsr1.h6
-rw-r--r--silk/mips/macros_mipsr1.h6
-rw-r--r--silk/structs.h28
-rw-r--r--silk/tests/test_unit_LPC_inv_pred_gain.c1
-rw-r--r--silk/x86/NSQ_del_dec_avx2.c1075
-rw-r--r--silk/x86/NSQ_del_dec_sse4_1.c38
-rw-r--r--silk/x86/NSQ_sse4_1.c38
-rw-r--r--silk/x86/VAD_sse4_1.c2
-rw-r--r--silk/x86/VQ_WMat_EC_sse4_1.c2
-rw-r--r--silk/x86/main_sse.h62
-rw-r--r--silk/x86/x86_silk_map.c28
-rw-r--r--silk_sources.mk6
-rw-r--r--src/analysis.c6
-rw-r--r--src/extensions.c315
-rw-r--r--src/mapping_matrix.c561
-rw-r--r--src/mapping_matrix.h12
-rw-r--r--src/meson.build2
-rw-r--r--src/mlp.c42
-rw-r--r--src/mlp.h20
-rw-r--r--src/mlp_data.c6
-rw-r--r--src/opus.c10
-rw-r--r--src/opus_decoder.c495
-rw-r--r--src/opus_demo.c359
-rw-r--r--src/opus_encoder.c651
-rw-r--r--src/opus_multistream_decoder.c4
-rw-r--r--src/opus_multistream_encoder.c2
-rw-r--r--src/opus_private.h28
-rw-r--r--src/opus_projection_encoder.c42
-rw-r--r--src/repacketizer.c144
-rw-r--r--tests/meson.build9
-rwxr-xr-xtests/random_config.sh10
-rw-r--r--tests/test_opus_api.c6
-rw-r--r--tests/test_opus_common.h1
-rw-r--r--tests/test_opus_decode.c18
-rw-r--r--tests/test_opus_dred.c107
-rw-r--r--tests/test_opus_encode.c39
-rw-r--r--tests/test_opus_extensions.c450
-rw-r--r--tests/test_opus_padding.c4
-rw-r--r--tests/test_opus_projection.c2
-rw-r--r--win32/.gitignore26
-rw-r--r--win32/VS2015/common.props82
-rw-r--r--win32/VS2015/opus.sln168
-rw-r--r--win32/VS2015/opus.vcxproj399
-rw-r--r--win32/VS2015/opus.vcxproj.filters585
-rw-r--r--win32/VS2015/opus_demo.vcxproj171
-rw-r--r--win32/VS2015/opus_demo.vcxproj.filters22
-rw-r--r--win32/VS2015/test_opus_api.vcxproj171
-rw-r--r--win32/VS2015/test_opus_api.vcxproj.filters14
-rw-r--r--win32/VS2015/test_opus_decode.vcxproj171
-rw-r--r--win32/VS2015/test_opus_decode.vcxproj.filters14
-rw-r--r--win32/VS2015/test_opus_encode.vcxproj172
-rw-r--r--win32/VS2015/test_opus_encode.vcxproj.filters17
-rw-r--r--win32/genversion.bat37
459 files changed, 48776 insertions, 2974 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index a0f4a776..00000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-image: Visual Studio 2015
-configuration:
-- Debug
-- DebugDLL
-- DebugDLL_fixed
-- Release
-- ReleaseDLL
-- ReleaseDLL_fixed
-
-platform:
-- Win32
-- x64
-
-environment:
- api_key:
- secure: kR3Ac0NjGwFnTmXdFrR8d6VXjdk5F7L4F/BilC4nvaM=
-
-build:
- project: win32\VS2015\opus.sln
- parallel: true
- verbosity: minimal
-
-after_build:
-- cd %APPVEYOR_BUILD_FOLDER%
-- 7z a opus.zip win32\VS2015\%PLATFORM%\%CONFIGURATION%\opus.??? include\*.h
-
-test_script:
-- cd %APPVEYOR_BUILD_FOLDER%\win32\VS2015\%PLATFORM%\%CONFIGURATION%
-- test_opus_api.exe
-- test_opus_decode.exe
-- test_opus_encode.exe
-
-artifacts:
-- path: opus.zip
-
-on_success:
-- ps: if ($env:api_key -and "$env:configuration/$env:platform" -eq "ReleaseDLL_fixed/x64") { Start-AppveyorBuild -ApiKey $env:api_key -ProjectSlug 'opus-tools' }
diff --git a/.github/workflows/autotools.yml b/.github/workflows/autotools.yml
new file mode 100644
index 00000000..bb66d5b0
--- /dev/null
+++ b/.github/workflows/autotools.yml
@@ -0,0 +1,51 @@
+name: Autotools
+
+on: [push, pull_request]
+
+jobs:
+
+ AutoMakeBuild:
+ name: AutoMake/${{ matrix.config.name }}
+ runs-on: ${{ matrix.config.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ config:
+ - {
+ name: "Linux/GCC",
+ os: ubuntu-latest,
+ compiler: gcc,
+ automakeconfig:
+ }
+ - {
+ name: "Linux/GCC/EnableAssertions",
+ os: ubuntu-latest,
+ compiler: gcc,
+ buildconfig: --enable-assertions
+ }
+ - {
+ name: "Linux/GCC/EnableCustomModes",
+ os: ubuntu-latest,
+ compiler: gcc,
+ buildconfig: --enable-assertions --enable-custom-modes
+ }
+ - {
+ name: "Linux/GCC/EnableDNN",
+ os: ubuntu-latest,
+ compiler: gcc,
+ buildconfig: --enable-assertions --enable-custom-modes --enable-dred --enable-osce
+ }
+ steps:
+ - uses: actions/checkout@v3
+ # No AutoMake on Mac so let's install it
+ - name: Install AutoConf, AutoMake and LibTool on MacOSX
+ if: matrix.config.os == 'macos-latest'
+ run: brew install autoconf automake libtool
+ - name: Autogen
+ run: CC=${{ matrix.config.compiler }} ./autogen.sh
+ - name: Configure
+ run: CFLAGS="-mavx -mfma -mavx2 -O2 -ffast-math" ./configure --enable-float-approx ${{ matrix.config.buildconfig }}
+ - name: Build
+ run: make -j 2
+ - name: Test
+ run: make check -j 2
diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
new file mode 100644
index 00000000..ebdc68dc
--- /dev/null
+++ b/.github/workflows/cmake.yml
@@ -0,0 +1,252 @@
+name: CMake
+
+on: [push, pull_request]
+
+jobs:
+ CMakeVersionTest:
+ name: Test build with CMake 3.1.0
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Download models
+ run: ./autogen.sh
+ - name: Install CMake 3.1
+ run: |
+ curl -sL https://github.com/Kitware/CMake/releases/download/v3.1.0/cmake-3.1.0-Linux-x86_64.sh -o cmakeinstall.sh
+ chmod +x cmakeinstall.sh
+ sudo ./cmakeinstall.sh --prefix=/usr/local --exclude-subdir
+ rm cmakeinstall.sh
+ sudo apt-get install libidn11
+ - name: Create Work Dir
+ run: mkdir build
+ - name: Configure
+ working-directory: ./build
+ run: cmake .. -DOPUS_BUILD_PROGRAMS=ON -DBUILD_TESTING=ON
+ - name: Build
+ working-directory: ./build
+ run: make -j 2 -s
+ - name: Test
+ working-directory: ./build
+ run: ctest -j 2
+
+ CMakeMINGW:
+ name: CMake MINGW
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Download models
+ run: ./autogen.sh
+ - name: Install MINGW
+ run: sudo apt-get install -y mingw-w64
+ - name: Create Work Dir
+ run: mkdir build
+ - name: Configure
+ working-directory: ./build
+ run: cmake .. -DOPUS_BUILD_PROGRAMS=ON -DBUILD_TESTING=ON -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc
+ - name: Build
+ working-directory: ./build
+ run: cmake --build . -j 2 --config Release --target package
+
+ CMakeBuild:
+ name: CMake/${{ matrix.config.name }}
+ runs-on: ${{ matrix.config.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ config:
+ - {
+ name: "Android/So/ARMv8/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: "-DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a -DBUILD_SHARED_LIBS=ON"
+ }
+ # TODO: Android ARMv7
+ # - {
+ # name: "Android/So/ARMv7/Release",
+ # os: ubuntu-latest,
+ # config: Release,
+ # args: "-DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a -DBUILD_SHARED_LIBS=ON"
+ # }
+ - {
+ name: "Android/So/X86/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: "-DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86 -DBUILD_SHARED_LIBS=ON"
+ }
+ - {
+ name: "Android/So/X64/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: "-DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86_64 -DBUILD_SHARED_LIBS=ON"
+ }
+ - {
+ name: "Windows/Dll/X86/Release",
+ os: windows-latest,
+ config: Release,
+ args: -G "Visual Studio 17 2022" -A Win32 -DBUILD_SHARED_LIBS=ON
+ }
+ - {
+ name: "Windows/Dll/X64/Release",
+ os: windows-latest,
+ config: Release,
+ args: -G "Visual Studio 17 2022" -A Win32 -DBUILD_SHARED_LIBS=ON
+ }
+ - {
+ name: "Windows/Dll/ARMv8/Release",
+ os: windows-latest,
+ config: Release,
+ args: -G "Visual Studio 17 2022" -A ARM64 -DBUILD_SHARED_LIBS=ON
+ }
+ - {
+ name: "Linux/So/X64/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: -DBUILD_SHARED_LIBS=ON
+ }
+ - {
+ name: "MacOSX/So/X64/Release",
+ os: macos-latest,
+ config: Release,
+ args: -DBUILD_SHARED_LIBS=ON
+ }
+ - {
+ name: "MacOSX/Framework/X64/Release",
+ os: macos-latest,
+ config: Release,
+ args: -DBUILD_FRAMEWORK=ON -DCMAKE_INSTALL_PREFIX=install
+ }
+ # use unix makefiles for iOS to avoid Xcode to complain about signing.
+ - {
+ name: "iOS/Dll/arm64/Release",
+ os: macos-latest,
+ config: Release,
+ args: -G "Unix Makefiles" -DBUILD_SHARED_LIBS=ON -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64
+ }
+ # use unix makefiles for iOS to avoid Xcode to complain about signing.
+ - {
+ name: "iOS/Framework/arm64/Release",
+ os: macos-latest,
+ config: Release,
+ args: -G "Unix Makefiles" -DBUILD_FRAMEWORK=ON -DCMAKE_INSTALL_PREFIX=install -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64
+ }
+ - {
+ name: "Windows/Lib/X86/Release",
+ os: windows-latest,
+ config: Release,
+ args: -G "Visual Studio 17 2022" -A Win32
+ }
+ - {
+ name: "Windows/Lib/X64/Release",
+ os: windows-latest,
+ config: Release,
+ args: -G "Visual Studio 17 2022" -A Win32
+ }
+ - {
+ name: "Windows/Lib/armv8/Release",
+ os: windows-latest,
+ config: Release,
+ args: -G "Visual Studio 17 2022" -A ARM64
+ }
+ - {
+ name: "Linux/Lib/X64/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: ""
+ }
+ - {
+ name: "MacOSX/Lib/X64/Release",
+ os: macos-latest,
+ config: Release,
+ args: ""
+ }
+ # use unix makefiles for iOS to avoid Xcode to complain about signing.
+ - {
+ name: "iOS/Lib/arm64/Release",
+ os: macos-latest,
+ config: Release,
+ args: -G "Unix Makefiles" -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64
+ }
+ - {
+ name: "Android/Lib/ARMv8/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: "-DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a"
+ }
+ # TODO: Android ARMv7
+ # - {
+ # name: "Android/Lib/ARMv7/Release",
+ # os: ubuntu-latest,
+ # config: Release,
+ # args: "-DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a"
+ # }
+ - {
+ name: "Android/Lib/X86/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: "-DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86"
+ }
+ - {
+ name: "Android/Lib/X64/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: "-DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86_64"
+ }
+ - {
+ name: "CustomModes/Linux/Lib/X64/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: "-DOPUS_CUSTOM_MODES=ON"
+ }
+ - {
+ name: "AssertionsFuzz/Windows/Lib/X64/Release",
+ os: windows-latest,
+ config: Release,
+ args: -G "Visual Studio 17 2022" -A Win32 -DOPUS_ASSERTIONS=ON -DOPUS_FUZZING=ON
+ }
+ - {
+ name: "AssertionsFuzz/Linux/Lib/X64/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: -DOPUS_ASSERTIONS=ON -DOPUS_FUZZING=ON
+ }
+ - {
+ name: "AssertionsFuzz/MacOSX/Lib/X64/Release",
+ os: macos-latest,
+ config: Release,
+ args: -DOPUS_ASSERTIONS=ON -DOPUS_FUZZING=ON
+ }
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Install AutoConf, AutoMake and LibTool # Needed for autogen.sh
+ if: matrix.config.os == 'macos-latest'
+ run: brew install autoconf automake libtool
+ - name: Download models Windows
+ if: contains(matrix.config.name, 'Windows')
+ run: .\autogen.bat
+ - name: Download models
+ if: contains(matrix.config.name, 'MacOSX') ||
+ contains(matrix.config.name, 'Linux') ||
+ contains(matrix.config.name, 'Android') ||
+ contains(matrix.config.name, 'iOS')
+ run: ./autogen.sh
+ - name: Create Work Dir
+ run: mkdir build
+ - name: Configure
+ working-directory: ./build
+ run: cmake .. ${{ matrix.config.args }} -DCMAKE_BUILD_TYPE=${{ matrix.config.config }} -DOPUS_BUILD_PROGRAMS=ON -DBUILD_TESTING=ON
+ - name: Build
+ working-directory: ./build
+ run: cmake --build . -j 2 --config ${{ matrix.config.config }} --target package
+ - name: Test
+ if: contains(matrix.config.name, 'Windows') && !contains(matrix.config.name, 'ARM') && !contains(matrix.config.name, 'Dll') ||
+ contains(matrix.config.name, 'MacOSX') && !contains(matrix.config.name, 'ARM') && !contains(matrix.config.name, 'Dll') ||
+ contains(matrix.config.name, 'Linux') && !contains(matrix.config.name, 'ARM') && !contains(matrix.config.name, 'Dll')
+ working-directory: ./build
+ run: ctest -j 2 -C ${{ matrix.config.config }} --output-on-failure
diff --git a/.github/workflows/dred.yml b/.github/workflows/dred.yml
new file mode 100644
index 00000000..ac703dd1
--- /dev/null
+++ b/.github/workflows/dred.yml
@@ -0,0 +1,121 @@
+# Configs that enables Deep Redudancy (DRED)
+name: DRED
+
+on: [push, pull_request]
+
+jobs:
+ CMakeBuild:
+ name: CMake/${{ matrix.config.name }}
+ runs-on: ${{ matrix.config.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ config:
+ - {
+ name: "Windows/Lib/X64/Release",
+ os: windows-latest,
+ config: Release,
+ args: -G "Visual Studio 17 2022" -DOPUS_X86_PRESUME_AVX2=ON
+ }
+ - {
+ name: "Windows/Lib/armv8/Release",
+ os: windows-latest,
+ config: Release,
+ args: -G "Visual Studio 17 2022" -A ARM64
+ }
+ - {
+ name: "Linux/Lib/X64/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: -DOPUS_X86_PRESUME_AVX2=ON
+ }
+ - {
+ name: "Android/Lib/X64/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: "-DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=x86_64"
+ }
+ - {
+ name: "Android/Lib/ARMv8/Release",
+ os: ubuntu-latest,
+ config: Release,
+ args: "-DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a"
+ }
+ - {
+ name: "MacOSX/Lib/X64/Release",
+ os: macos-latest,
+ config: Release,
+ # some macs are really old in githubs lab so they don't support avx
+ args: -DOPUS_X86_PRESUME_AVX2=OFF
+ }
+ - {
+ name: "iOS/Lib/arm64/Release",
+ os: macos-latest,
+ config: Release,
+ args: -G "Unix Makefiles" -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64
+ }
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Install AutoConf, AutoMake and LibTool # Needed for autogen.sh
+ if: matrix.config.os == 'macos-latest'
+ run: brew install autoconf automake libtool
+ - name: Download models Windows
+ if: contains(matrix.config.name, 'Windows')
+ run: .\autogen.bat
+ - name: Download models
+ if: contains(matrix.config.name, 'MacOSX') ||
+ contains(matrix.config.name, 'Linux') ||
+ contains(matrix.config.name, 'Android') ||
+ contains(matrix.config.name, 'iOS')
+ run: ./autogen.sh
+ - name: Create Work Dir
+ run: mkdir build
+ - name: Configure
+ working-directory: ./build
+ run: cmake .. ${{ matrix.config.args }} -DCMAKE_BUILD_TYPE=${{ matrix.config.config }} -DOPUS_BUILD_PROGRAMS=ON -DBUILD_TESTING=ON -DOPUS_FAST_MATH=ON -DOPUS_FLOAT_APPROX=ON -DOPUS_DRED=ON -DOPUS_OSCE=ON
+ - name: Build
+ working-directory: ./build
+ run: cmake --build . -j 2 --config ${{ matrix.config.config }} --target package
+ - name: Test
+ if: contains(matrix.config.name, 'Windows') && !contains(matrix.config.name, 'ARM') && !contains(matrix.config.name, 'Dll') ||
+ contains(matrix.config.name, 'MacOSX') && !contains(matrix.config.name, 'ARM') && !contains(matrix.config.name, 'Dll') ||
+ contains(matrix.config.name, 'Linux') && !contains(matrix.config.name, 'ARM') && !contains(matrix.config.name, 'Dll')
+ working-directory: ./build
+ run: ctest -j 2 -C ${{ matrix.config.config }} --output-on-failure
+
+ AutoToolsBuild:
+ name: AutoTools/${{ matrix.config.name }}
+ runs-on: ${{ matrix.config.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ config:
+ - {
+ name: "Linux/GCC",
+ os: ubuntu-latest,
+ compiler: gcc,
+ automakeconfig:
+ }
+ - {
+ name: "Linux/Clang",
+ os: ubuntu-latest,
+ compiler: clang,
+ automakeconfig:
+ }
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Install AutoConf, AutoMake and LibTool on MacOSX
+ if: matrix.config.os == 'macos-latest'
+ run: brew install autoconf automake libtool
+ - name: Autogen
+ run: CC=${{ matrix.config.compiler }} ./autogen.sh
+ - name: Configure
+ run: CFLAGS="-mavx -mfma -mavx2 -O2 -ffast-math" ./configure --enable-float-approx
+ - name: Build
+ run: make -j 2
+ - name: Test
+ run: make check -j 2
diff --git a/.github/workflows/repository.yml b/.github/workflows/repository.yml
new file mode 100644
index 00000000..857621dd
--- /dev/null
+++ b/.github/workflows/repository.yml
@@ -0,0 +1,15 @@
+name: Repository
+
+on: [push, pull_request]
+
+jobs:
+ CheckTrailingWhiteSpaces:
+ name: Check trailing white spaces
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Check Whitespaces
+ run: |
+ git diff-tree --check origin/opus-ng HEAD
diff --git a/.gitignore b/.gitignore
index 837619f9..5a49669c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,7 @@ tests/*test
tests/test_opus_api
tests/test_opus_decode
tests/test_opus_encode
+tests/test_opus_extensions
tests/test_opus_padding
tests/test_opus_projection
celt/arm/armopts.s
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 01b033fd..be6af958 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,27 +1,72 @@
include:
- template: 'Workflows/Branch-Pipelines.gitlab-ci.yml'
+# https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines
+workflow:
+ rules:
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+ - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"
+ when: never
+ - if: $CI_COMMIT_BRANCH
+ - if: $CI_COMMIT_TAG
+
default:
tags:
- docker
- # Image from https://hub.docker.com/_/gcc/ based on Debian
- image: gcc:9
+ image: 'debian:bookworm-slim'
+
+# https://docs.gitlab.com/ee/ci/yaml/yaml_optimization.html#reference-tags
+.snippets:
+ git_prep:
+ # Make sure we have a recent annotated tag, otherwise meson/get-version.py
+ # might fail later (e.g. shallow clone without enough history) or return
+ # a bogus version based on a much older tag. This can happen in merge request
+ # pipelines from a personal fork, as the fork might not have the latest
+ # upstream tags if it has been forked a long time ago. Also affects the
+ # git version picked up by autotools and cmake, not just meson.
+ - git fetch https://gitlab.xiph.org/xiph/opus.git refs/tags/v1.4:refs/tags/v1.4
+ - git describe
whitespace:
stage: test
+ before_script:
+ - apt-get update &&
+ apt-get install -y git
script:
- - git diff-tree --check origin/master HEAD
+ - git diff-tree --check origin/main HEAD
+
+# Make sure commits are GPG signed
+ci-fairy:
+ stage: test
+ script:
+ - apt update
+ - apt install -y python3-pip git
+ - pip3 install --break-system-packages git+https://gitlab.freedesktop.org/freedesktop/ci-templates@7811ba9814a3bad379377241c6c6b62d78b20eac
+ - echo Checking commits $CI_FAIRY_BASE_COMMIT..HEAD
+ - ci-fairy check-commits --gpg-signed-commit $CI_FAIRY_BASE_COMMIT..HEAD
+ tags:
+ - 'docker'
+ rules:
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+ variables:
+ CI_FAIRY_BASE_COMMIT: $CI_MERGE_REQUEST_DIFF_BASE_SHA
+ - if: $CI_PIPELINE_SOURCE != "merge_request_event"
+ variables:
+ CI_FAIRY_BASE_COMMIT: 'HEAD^1'
autoconf:
stage: build
+ tags:
+ - avx2
before_script:
- apt-get update &&
- apt-get install -y zip doxygen
+ apt-get install -y zip doxygen git automake libtool make wget
+ - !reference [.snippets, git_prep]
script:
- ./autogen.sh
- - ./configure
- - make -j4
- - make distcheck
+ - CFLAGS="-mavx -mfma -mavx2 -O2 -ffast-math" ./configure --enable-float-approx --enable-dred --enable-osce
+ - make -j16
+ - DISTCHECK_CONFIGURE_FLAGS="--enable-float-approx --enable-dred --enable-osce CFLAGS='-mavx -mfma -mavx2 -O2'" make distcheck -j16
cache:
paths:
- "src/*.o"
@@ -33,29 +78,45 @@ autoconf:
cmake:
stage: build
+ tags:
+ - avx2
before_script:
- apt-get update &&
- apt-get install -y cmake ninja-build
+ apt-get install -y cmake ninja-build git automake libtool wget
+ - !reference [.snippets, git_prep]
script:
+ - ./autogen.sh
- mkdir build
- - cmake -S . -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DOPUS_BUILD_TESTING=ON -DOPUS_BUILD_PROGRAMS=ON
+ - cmake -S . -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DOPUS_BUILD_PROGRAMS=ON -DBUILD_TESTING=ON -DOPUS_FAST_MATH=ON -DOPUS_FLOAT_APPROX=ON -DOPUS_DRED=ON -DOPUS_OSCE=ON -DOPUS_X86_PRESUME_AVX2=ON
- cmake --build build
- - cd build && ctest --output-on-failure
+ - cd build && ctest --output-on-failure -j 16
-meson:
+.meson:
+ image: 'debian:bookworm-slim'
stage: build
before_script:
- apt-get update &&
- apt-get install -y python3-pip ninja-build doxygen
- - export XDG_CACHE_HOME=$PWD/pip-cache
- - pip3 install --user meson
+ apt-get install -y ninja-build doxygen meson git automake libtool wget
+ - !reference [.snippets, git_prep]
script:
- - export PATH=$PATH:$HOME/.local/bin
+ - ./autogen.sh
- mkdir builddir
- - meson setup --werror -Dtests=enabled -Ddocs=enabled -Dbuildtype=release builddir
+ - meson setup -Denable-deep-plc=true -Denable-osce=true -Denable-dred=true -Dtests=enabled -Ddocs=enabled -Dbuildtype=release builddir
- meson compile -C builddir
- meson test -C builddir
#- meson dist --no-tests -C builddir
- cache:
- paths:
- - 'pip-cache/*'
+
+meson x86_64:
+ extends: '.meson'
+ tags:
+ - avx2
+ variables:
+ MESON_EXTRA_ARGS: '--werror'
+
+meson arm64:
+ extends: '.meson'
+ tags:
+ - 'gstreamer-arm64-linux-docker'
+ variables:
+ # arm64 build has a compiler warning still, so let's not use --werror for now
+ MESON_EXTRA_ARGS: '-Dwerror=false'
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 821c813e..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-language: c
-
-compiler:
- - gcc
- - clang
-
-os:
- - linux
- - osx
-
-env:
- - CONFIG=""
- - CONFIG="--enable-assertions"
- - CONFIG="--enable-fixed-point"
- - CONFIG="--enable-fixed-point --disable-float-api"
- - CONFIG="--enable-fixed-point --enable-assertions"
-
-script:
- - ./autogen.sh
- - ./configure $CONFIG
- - make distcheck
diff --git a/Android.bp b/Android.bp
index 29a421c4..28f37971 100644
--- a/Android.bp
+++ b/Android.bp
@@ -190,7 +190,7 @@ cc_library {
"src/opus_projection_encoder.c",
"src/opus_projection_decoder.c",
"src/repacketizer.c",
-
+ "src/extensions.c",
// OPUS_SOURCES_FLOAT
"src/analysis.c",
"src/mlp.c",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9d824cdc..06e9b675 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -71,6 +71,10 @@ set(OPUS_CHECK_ASM_HELP_STR "enable bit-exactness checks between optimized and c
option(OPUS_CHECK_ASM ${OPUS_CHECK_ASM_HELP_STR} OFF)
add_feature_info(OPUS_CHECK_ASM OPUS_CHECK_ASM ${OPUS_CHECK_ASM_HELP_STR})
+set(OPUS_DNN_FLOAT_DEBUG_HELP_STR "Run DNN computations as float for debugging purposes.")
+option(OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR} OFF)
+add_feature_info(OPUS_DNN_FLOAT_DEBUG OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR})
+
set(OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR "install pkg-config module.")
option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ON)
add_feature_info(OPUS_INSTALL_PKG_CONFIG_MODULE OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR})
@@ -79,6 +83,14 @@ set(OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR "install CMake package config modu
option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ON)
add_feature_info(OPUS_INSTALL_CMAKE_CONFIG_MODULE OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR})
+set(OPUS_DRED_HELP_STR "enable DRED.")
+option(OPUS_DRED ${OPUS_DRED_HELP_STR} OFF)
+add_feature_info(OPUS_DRED OPUS_DRED ${OPUS_DRED_HELP_STR})
+
+set(OPUS_OSCE_HELP_STR "enable OSCE.")
+option(OPUS_OSCE ${OPUS_OSCE_HELP_STR} OFF)
+add_feature_info(OPUS_OSCE OPUS_OSCE ${OPUS_OSCE_HELP_STR})
+
if(APPLE)
set(OPUS_BUILD_FRAMEWORK_HELP_STR "build Framework bundle for Apple systems.")
option(OPUS_BUILD_FRAMEWORK ${OPUS_BUILD_FRAMEWORK_HELP_STR} OFF)
@@ -173,13 +185,13 @@ if(OPUS_CPU_X86 OR OPUS_CPU_X64)
OFF)
add_feature_info(OPUS_X86_MAY_HAVE_SSE4_1 OPUS_X86_MAY_HAVE_SSE4_1 ${OPUS_X86_MAY_HAVE_SSE4_1_HELP_STR})
- set(OPUS_X86_MAY_HAVE_AVX_HELP_STR "does runtime check for AVX support.")
- cmake_dependent_option(OPUS_X86_MAY_HAVE_AVX
- ${OPUS_X86_MAY_HAVE_AVX_HELP_STR}
+ set(OPUS_X86_MAY_HAVE_AVX2_HELP_STR "does runtime check for AVX FMA AVX2 support.")
+ cmake_dependent_option(OPUS_X86_MAY_HAVE_AVX2
+ ${OPUS_X86_MAY_HAVE_AVX2_HELP_STR}
ON
- "AVX_SUPPORTED; NOT OPUS_DISABLE_INTRINSICS"
+ "AVX2_SUPPORTED; NOT OPUS_DISABLE_INTRINSICS"
OFF)
- add_feature_info(OPUS_X86_MAY_HAVE_AVX OPUS_X86_MAY_HAVE_AVX ${OPUS_X86_MAY_HAVE_AVX_HELP_STR})
+ add_feature_info(OPUS_X86_MAY_HAVE_AVX2 OPUS_X86_MAY_HAVE_AVX2 ${OPUS_X86_MAY_HAVE_AVX2_HELP_STR})
# PRESUME depends on MAY HAVE, but PRESUME will override runtime detection
set(OPUS_X86_PRESUME_SSE_HELP_STR "assume target CPU has SSE1 support (override runtime check).")
@@ -220,13 +232,13 @@ if(OPUS_CPU_X86 OR OPUS_CPU_X64)
OFF)
add_feature_info(OPUS_X86_PRESUME_SSE4_1 OPUS_X86_PRESUME_SSE4_1 ${OPUS_X86_PRESUME_SSE4_1_HELP_STR})
- set(OPUS_X86_PRESUME_AVX_HELP_STR "assume target CPU has AVX support (override runtime check).")
- cmake_dependent_option(OPUS_X86_PRESUME_AVX
- ${OPUS_X86_PRESUME_AVX_HELP_STR}
+ set(OPUS_X86_PRESUME_AVX2_HELP_STR "assume target CPU has AVX FMA AVX2 support (override runtime check).")
+ cmake_dependent_option(OPUS_X86_PRESUME_AVX2
+ ${OPUS_X86_PRESUME_AVX2_HELP_STR}
OFF
- "OPUS_X86_MAY_HAVE_AVX; NOT OPUS_DISABLE_INTRINSICS"
+ "OPUS_X86_MAY_HAVE_AVX2; NOT OPUS_DISABLE_INTRINSICS"
OFF)
- add_feature_info(OPUS_X86_PRESUME_AVX OPUS_X86_PRESUME_AVX ${OPUS_X86_PRESUME_AVX_HELP_STR})
+ add_feature_info(OPUS_X86_PRESUME_AVX2 OPUS_X86_PRESUME_AVX2 ${OPUS_X86_PRESUME_AVX2_HELP_STR})
endif()
feature_summary(WHAT ALL)
@@ -274,6 +286,7 @@ target_include_directories(
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/opus>
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/dnn
celt
silk)
@@ -309,6 +322,10 @@ if(OPUS_CHECK_ASM)
target_compile_definitions(opus PRIVATE OPUS_CHECK_ASM)
endif()
+if(NOT OPUS_DNN_FLOAT_DEBUG)
+ target_compile_definitions(opus PRIVATE DISABLE_DEBUG_FLOAT)
+endif()
+
if(OPUS_VAR_ARRAYS)
target_compile_definitions(opus PRIVATE VAR_ARRAYS)
elseif(OPUS_USE_ALLOCA)
@@ -365,11 +382,33 @@ if(NOT OPUS_ENABLE_FLOAT_API)
target_compile_definitions(opus PRIVATE DISABLE_FLOAT_API)
endif()
+if (OPUS_DEEP_PLC OR OPUS_DRED OR OPUS_OSCE)
+ add_sources_group(opus lpcnet ${deep_plc_headers} ${deep_plc_sources})
+ set(OPUS_DNN TRUE)
+else()
+ set(OPUS_DNN FALSE)
+endif()
+
+if (OPUS_DNN)
+ add_sources_group(opus lpcnet ${deep_plc_headers} ${deep_plc_sources})
+ target_compile_definitions(opus PRIVATE ENABLE_DEEP_PLC)
+endif()
+
+if (OPUS_DRED)
+ add_sources_group(opus lpcnet ${dred_headers} ${dred_sources})
+ target_compile_definitions(opus PRIVATE ENABLE_DRED)
+endif()
+
+if (OPUS_OSCE)
+ add_sources_group(opus lpcnet ${osce_headers} ${osce_sources})
+ target_compile_definitions(opus PRIVATE ENABLE_OSCE)
+endif()
+
if(NOT OPUS_DISABLE_INTRINSICS)
if(((OPUS_X86_MAY_HAVE_SSE AND NOT OPUS_X86_PRESUME_SSE) OR
(OPUS_X86_MAY_HAVE_SSE2 AND NOT OPUS_X86_PRESUME_SSE2) OR
(OPUS_X86_MAY_HAVE_SSE4_1 AND NOT OPUS_X86_PRESUME_SSE4_1) OR
- (OPUS_X86_MAY_HAVE_AVX AND NOT OPUS_X86_PRESUME_AVX)) AND
+ (OPUS_X86_MAY_HAVE_AVX2 AND NOT OPUS_X86_PRESUME_AVX2)) AND
RUNTIME_CPU_CAPABILITY_DETECTION)
target_compile_definitions(opus PRIVATE OPUS_HAVE_RTCD)
if(NOT MSVC)
@@ -383,6 +422,9 @@ if(NOT OPUS_DISABLE_INTRINSICS)
endif()
add_sources_group(opus celt ${celt_sources_x86_rtcd})
add_sources_group(opus silk ${silk_sources_x86_rtcd})
+ if (OPUS_DNN)
+ add_sources_group(opus lpcnet ${dnn_sources_x86_rtcd})
+ endif()
endif()
if(SSE1_SUPPORTED)
@@ -404,9 +446,12 @@ if(NOT OPUS_DISABLE_INTRINSICS)
if(SSE2_SUPPORTED)
if(OPUS_X86_MAY_HAVE_SSE2)
add_sources_group(opus celt ${celt_sources_sse2})
+ if (OPUS_DNN)
+ add_sources_group(opus lpcnet ${dnn_sources_sse2})
+ endif()
target_compile_definitions(opus PRIVATE OPUS_X86_MAY_HAVE_SSE2)
if(NOT MSVC)
- set_source_files_properties(${celt_sources_sse2} PROPERTIES COMPILE_FLAGS -msse2)
+ set_source_files_properties(${celt_sources_sse2} ${dnn_sources_sse2} PROPERTIES COMPILE_FLAGS -msse2)
endif()
endif()
if(OPUS_X86_PRESUME_SSE2)
@@ -421,9 +466,12 @@ if(NOT OPUS_DISABLE_INTRINSICS)
if(OPUS_X86_MAY_HAVE_SSE4_1)
add_sources_group(opus celt ${celt_sources_sse4_1})
add_sources_group(opus silk ${silk_sources_sse4_1})
+ if (OPUS_DNN)
+ add_sources_group(opus lpcnet ${dnn_sources_sse4_1})
+ endif()
target_compile_definitions(opus PRIVATE OPUS_X86_MAY_HAVE_SSE4_1)
if(NOT MSVC)
- set_source_files_properties(${celt_sources_sse4_1} ${silk_sources_sse4_1} PROPERTIES COMPILE_FLAGS -msse4.1)
+ set_source_files_properties(${celt_sources_sse4_1} ${silk_sources_sse4_1} ${dnn_sources_sse4_1} PROPERTIES COMPILE_FLAGS -msse4.1)
endif()
if(OPUS_FIXED_POINT)
@@ -441,22 +489,37 @@ if(NOT OPUS_DISABLE_INTRINSICS)
endif()
endif()
- if(AVX_SUPPORTED)
- # mostly placeholder in case of avx intrinsics is added
- if(OPUS_X86_MAY_HAVE_AVX)
- target_compile_definitions(opus PRIVATE OPUS_X86_MAY_HAVE_AVX)
+ if(AVX2_SUPPORTED)
+ if(OPUS_X86_MAY_HAVE_AVX2)
+ add_sources_group(opus celt ${celt_sources_avx2})
+ add_sources_group(opus silk ${silk_sources_avx2})
+ add_sources_group(opus silk ${silk_sources_float_avx2})
+ if (OPUS_DNN)
+ add_sources_group(opus lpcnet ${dnn_sources_avx2})
+ endif()
+ target_compile_definitions(opus PRIVATE OPUS_X86_MAY_HAVE_AVX2)
+ if(MSVC)
+ set(AVX2_FLAGS "${AVX2_FLAGS} /arch:AVX2")
+ else()
+ set(AVX2_FLAGS "${AVX2_FLAGS} -mavx2 -mfma -mavx")
+ endif()
+ set_source_files_properties(${celt_sources_avx2} PROPERTIES COMPILE_FLAGS ${AVX2_FLAGS})
+ set_source_files_properties(${silk_sources_avx2} PROPERTIES COMPILE_FLAGS ${AVX2_FLAGS})
+ set_source_files_properties(${silk_sources_float_avx2} PROPERTIES COMPILE_FLAGS ${AVX2_FLAGS})
+ set_source_files_properties(${dnn_sources_avx2} PROPERTIES COMPILE_FLAGS ${AVX2_FLAGS})
endif()
- if(OPUS_X86_PRESUME_AVX)
- target_compile_definitions(opus PRIVATE OPUS_X86_PRESUME_AVX)
+ if(OPUS_X86_PRESUME_AVX2)
+ target_compile_definitions(opus PRIVATE OPUS_X86_PRESUME_AVX2)
+ target_compile_definitions(opus PRIVATE OPUS_X86_PRESUME_SSE4_1)
if(NOT MSVC)
- target_compile_options(opus PRIVATE -mavx)
+ target_compile_options(opus PRIVATE -mavx2 -mfma -mavx)
endif()
endif()
endif()
if(MSVC)
- if(AVX_SUPPORTED AND OPUS_X86_PRESUME_AVX) # on 64 bit and 32 bits
- add_definitions(/arch:AVX)
+ if(AVX2_SUPPORTED AND OPUS_X86_PRESUME_AVX2) # on 64 bit and 32 bits
+ add_definitions(/arch:AVX2)
elseif(OPUS_CPU_X86) # if AVX not supported then set SSE flag
if((SSE4_1_SUPPORTED AND OPUS_X86_PRESUME_SSE4_1)
OR (SSE2_SUPPORTED AND OPUS_X86_PRESUME_SSE2))
@@ -486,6 +549,9 @@ if(NOT OPUS_DISABLE_INTRINSICS)
add_sources_group(opus celt ${celt_sources_arm_neon_intr})
add_sources_group(opus silk ${silk_sources_arm_neon_intr})
+ if (OPUS_DNN)
+ add_sources_group(opus lpcnet ${dnn_sources_arm_neon})
+ endif()
# silk arm neon depends on main_Fix.h
target_include_directories(opus PRIVATE silk/fixed)
@@ -582,6 +648,7 @@ if(OPUS_BUILD_PROGRAMS)
target_include_directories(opus_demo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(opus_demo PRIVATE silk) # debug.h
target_include_directories(opus_demo PRIVATE celt) # arch.h
+ target_include_directories(opus_demo PRIVATE dnn)
target_link_libraries(opus_demo PRIVATE opus ${OPUS_REQUIRED_LIBRARIES})
target_compile_definitions(opus_demo PRIVATE OPUS_BUILD)
@@ -589,10 +656,6 @@ if(OPUS_BUILD_PROGRAMS)
add_executable(opus_compare ${opus_compare_sources})
target_include_directories(opus_compare PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(opus_compare PRIVATE opus ${OPUS_REQUIRED_LIBRARIES})
- if(MSVC)
- # move cosmetic warning to level 4 for opus_compare
- target_compile_options(opus_compare PRIVATE /w44244)
- endif()
endif()
if(BUILD_TESTING AND NOT BUILD_SHARED_LIBS)
@@ -636,11 +699,32 @@ if(BUILD_TESTING AND NOT BUILD_SHARED_LIBS)
add_executable(test_opus_encode ${test_opus_encode_sources})
target_include_directories(test_opus_encode
- PRIVATE ${CMAKE_CURRENT_BINARY_DIR} celt)
+ PRIVATE ${CMAKE_CURRENT_BINARY_DIR} celt dnn)
target_link_libraries(test_opus_encode PRIVATE opus)
target_compile_definitions(test_opus_encode PRIVATE OPUS_BUILD)
add_test(NAME test_opus_encode COMMAND ${CMAKE_COMMAND}
-DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_encode>
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
+
+ add_executable(test_opus_extensions ${test_opus_extensions_sources})
+ target_include_directories(test_opus_extensions
+ PRIVATE ${CMAKE_CURRENT_BINARY_DIR} celt dnn)
+ target_link_libraries(test_opus_extensions PRIVATE opus)
+ target_compile_definitions(test_opus_extensions PRIVATE OPUS_BUILD)
+ add_test(NAME test_opus_extensions COMMAND ${CMAKE_COMMAND}
+ -DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_extensions>
+ -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
+ -P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
+ if(OPUS_DRED)
+ add_executable(test_opus_dred ${test_opus_dred_sources})
+ target_include_directories(test_opus_dred
+ PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+ target_link_libraries(test_opus_dred PRIVATE opus)
+ target_compile_definitions(test_opus_dred PRIVATE OPUS_BUILD)
+ add_test(NAME test_opus_dred COMMAND ${CMAKE_COMMAND}
+ -DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_dred>
+ -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
+ -P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
+ endif()
endif()
diff --git a/COPYING b/COPYING
index 9c739c34..75711467 100644
--- a/COPYING
+++ b/COPYING
@@ -1,7 +1,7 @@
-Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
+Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
Jean-Marc Valin, Timothy B. Terriberry,
CSIRO, Gregory Maxwell, Mark Borgerding,
- Erik de Castro Lopo
+ Erik de Castro Lopo, Mozilla, Amazon
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
diff --git a/METADATA b/METADATA
index 962dc42d..e6cc7855 100644
--- a/METADATA
+++ b/METADATA
@@ -1,22 +1,22 @@
# This project was upgraded with external_updater.
-# Usage: tools/external_updater/updater.sh update libopus
-# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+# Usage: tools/external_updater/updater.sh update external/libopus
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
name: "libopus"
description: "Android fork of the opus library."
third_party {
- url {
- type: GIT
- value: "https://gitlab.xiph.org/xiph/opus.git"
- }
- version: "v1.4"
license_type: NOTICE
security {
tag: "NVD-CPE2.3:cpe:/a:opus-codec:opus:1.0.3"
}
last_upgrade_date {
- year: 2023
- month: 4
- day: 21
+ year: 2024
+ month: 3
+ day: 12
+ }
+ identifier {
+ type: "Git"
+ value: "https://gitlab.xiph.org/xiph/opus.git"
+ version: "v1.5"
}
}
diff --git a/Makefile.am b/Makefile.am
index 492fc09d..a20d0a39 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,12 +10,25 @@ lib_LTLIBRARIES = libopus.la
DIST_SUBDIRS = doc
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/celt -I$(top_srcdir)/silk \
- -I$(top_srcdir)/silk/float -I$(top_srcdir)/silk/fixed $(NE10_CFLAGS)
+ -I$(top_srcdir)/silk/float -I$(top_srcdir)/silk/fixed $(NE10_CFLAGS) \
+ -I$(top_srcdir)/dnn
include celt_sources.mk
+include lpcnet_sources.mk
include silk_sources.mk
include opus_sources.mk
+LPCNET_SOURCES =
+if ENABLE_DEEP_PLC
+LPCNET_SOURCES += $(DEEP_PLC_SOURCES)
+endif
+if ENABLE_DRED
+LPCNET_SOURCES += $(DRED_SOURCES)
+endif
+if ENABLE_OSCE
+LPCNET_SOURCES += $(OSCE_SOURCES)
+endif
+
if FIXED_POINT
SILK_SOURCES += $(SILK_SOURCES_FIXED)
if HAVE_SSE4_1
@@ -29,6 +42,9 @@ SILK_SOURCES += $(SILK_SOURCES_FLOAT)
if HAVE_SSE4_1
SILK_SOURCES += $(SILK_SOURCES_SSE4_1)
endif
+if HAVE_AVX2
+SILK_SOURCES += $(SILK_SOURCES_FLOAT_AVX2)
+endif
endif
if DISABLE_FLOAT_API
@@ -40,15 +56,31 @@ if CPU_X86
if HAVE_RTCD
CELT_SOURCES += $(CELT_SOURCES_X86_RTCD)
SILK_SOURCES += $(SILK_SOURCES_X86_RTCD)
+if ENABLE_DEEP_PLC
+LPCNET_SOURCES += $(DNN_SOURCES_X86_RTCD)
+endif
endif
if HAVE_SSE
CELT_SOURCES += $(CELT_SOURCES_SSE)
endif
if HAVE_SSE2
CELT_SOURCES += $(CELT_SOURCES_SSE2)
+if ENABLE_DEEP_PLC
+LPCNET_SOURCES += $(DNN_SOURCES_SSE2)
+endif
endif
if HAVE_SSE4_1
CELT_SOURCES += $(CELT_SOURCES_SSE4_1)
+if ENABLE_DEEP_PLC
+LPCNET_SOURCES += $(DNN_SOURCES_SSE4_1)
+endif
+endif
+if HAVE_AVX2
+SILK_SOURCES += $(SILK_SOURCES_AVX2)
+CELT_SOURCES += $(CELT_SOURCES_AVX2)
+if ENABLE_DEEP_PLC
+LPCNET_SOURCES += $(DNN_SOURCES_AVX2)
+endif
endif
endif
@@ -56,6 +88,18 @@ if CPU_ARM
if HAVE_RTCD
CELT_SOURCES += $(CELT_SOURCES_ARM_RTCD)
SILK_SOURCES += $(SILK_SOURCES_ARM_RTCD)
+if ENABLE_DEEP_PLC
+LPCNET_SOURCES += $(DNN_SOURCES_ARM_RTCD)
+endif
+endif
+
+if ENABLE_DEEP_PLC
+if HAVE_ARM_DOTPROD
+LPCNET_SOURCES += $(DNN_SOURCES_DOTPROD)
+endif
+if HAVE_ARM_NEON_INTR
+LPCNET_SOURCES += $(DNN_SOURCES_NEON)
+endif
endif
if HAVE_ARM_NEON_INTR
@@ -80,10 +124,25 @@ CLEANFILES = $(CELT_SOURCES_ARM_ASM:.s=-gnu.S) \
$(CELT_AM_SOURCES_ARM_ASM:.s.in=-gnu.S)
include celt_headers.mk
+include lpcnet_headers.mk
include silk_headers.mk
include opus_headers.mk
-libopus_la_SOURCES = $(CELT_SOURCES) $(SILK_SOURCES) $(OPUS_SOURCES)
+LPCNET_HEAD =
+if ENABLE_DEEP_PLC
+LPCNET_HEAD += $(DEEP_PLC_HEAD)
+endif
+if ENABLE_DRED
+LPCNET_HEAD += $(DRED_HEAD)
+endif
+if ENABLE_OSCE
+LPCNET_HEAD += $(OSCE_HEAD)
+endif
+if ENABLE_LOSSGEN
+LPCNET_HEAD += $(LOSSGEN_HEAD)
+endif
+
+libopus_la_SOURCES = $(CELT_SOURCES) $(SILK_SOURCES) $(LPCNET_SOURCES) $(OPUS_SOURCES)
libopus_la_LDFLAGS = -no-undefined -version-info @OPUS_LT_CURRENT@:@OPUS_LT_REVISION@:@OPUS_LT_AGE@
libopus_la_LIBADD = $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
@@ -92,7 +151,7 @@ endif
pkginclude_HEADERS = include/opus.h include/opus_multistream.h include/opus_types.h include/opus_defines.h include/opus_projection.h
-noinst_HEADERS = $(OPUS_HEAD) $(SILK_HEAD) $(CELT_HEAD)
+noinst_HEADERS = $(OPUS_HEAD) $(SILK_HEAD) $(CELT_HEAD) $(LPCNET_HEAD)
if EXTRA_PROGRAMS
noinst_PROGRAMS = celt/tests/test_unit_cwrs32 \
@@ -109,7 +168,9 @@ noinst_PROGRAMS = celt/tests/test_unit_cwrs32 \
silk/tests/test_unit_LPC_inv_pred_gain \
tests/test_opus_api \
tests/test_opus_decode \
+ tests/test_opus_dred \
tests/test_opus_encode \
+ tests/test_opus_extensions \
tests/test_opus_padding \
tests/test_opus_projection \
trivial_example
@@ -126,10 +187,14 @@ TESTS = celt/tests/test_unit_cwrs32 \
tests/test_opus_api \
tests/test_opus_decode \
tests/test_opus_encode \
+ tests/test_opus_extensions \
tests/test_opus_padding \
tests/test_opus_projection
opus_demo_SOURCES = src/opus_demo.c
+if ENABLE_LOSSGEN
+opus_demo_SOURCES += $(LOSSGEN_SOURCES)
+endif
opus_demo_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
@@ -155,18 +220,28 @@ tests_test_opus_decode_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
tests_test_opus_padding_SOURCES = tests/test_opus_padding.c tests/test_opus_common.h
tests_test_opus_padding_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
+tests_test_opus_dred_SOURCES = tests/test_opus_dred.c tests/test_opus_common.h
+tests_test_opus_dred_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
+
CELT_OBJ = $(CELT_SOURCES:.c=.lo)
SILK_OBJ = $(SILK_SOURCES:.c=.lo)
+LPCNET_OBJ = $(LPCNET_SOURCES:.c=.lo)
OPUS_OBJ = $(OPUS_SOURCES:.c=.lo)
+tests_test_opus_extensions_SOURCES = tests/test_opus_extensions.c tests/test_opus_common.h
+tests_test_opus_extensions_LDADD = $(OPUS_OBJ) $(SILK_OBJ) $(LPCNET_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
+if OPUS_ARM_EXTERNAL_ASM
+tests_test_opus_extensions_LDADD += libarmasm.la
+endif
+
tests_test_opus_projection_SOURCES = tests/test_opus_projection.c tests/test_opus_common.h
-tests_test_opus_projection_LDADD = $(OPUS_OBJ) $(SILK_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
+tests_test_opus_projection_LDADD = $(OPUS_OBJ) $(SILK_OBJ) $(LPCNET_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
tests_test_opus_projection_LDADD += libarmasm.la
endif
silk_tests_test_unit_LPC_inv_pred_gain_SOURCES = silk/tests/test_unit_LPC_inv_pred_gain.c
-silk_tests_test_unit_LPC_inv_pred_gain_LDADD = $(SILK_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
+silk_tests_test_unit_LPC_inv_pred_gain_LDADD = $(SILK_OBJ) $(LPCNET_OBJ) $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
silk_tests_test_unit_LPC_inv_pred_gain_LDADD += libarmasm.la
endif
@@ -175,7 +250,7 @@ celt_tests_test_unit_cwrs32_SOURCES = celt/tests/test_unit_cwrs32.c
celt_tests_test_unit_cwrs32_LDADD = $(LIBM)
celt_tests_test_unit_dft_SOURCES = celt/tests/test_unit_dft.c
-celt_tests_test_unit_dft_LDADD = $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
+celt_tests_test_unit_dft_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
celt_tests_test_unit_dft_LDADD += libarmasm.la
endif
@@ -187,19 +262,19 @@ celt_tests_test_unit_laplace_SOURCES = celt/tests/test_unit_laplace.c
celt_tests_test_unit_laplace_LDADD = $(LIBM)
celt_tests_test_unit_mathops_SOURCES = celt/tests/test_unit_mathops.c
-celt_tests_test_unit_mathops_LDADD = $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
+celt_tests_test_unit_mathops_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
celt_tests_test_unit_mathops_LDADD += libarmasm.la
endif
celt_tests_test_unit_mdct_SOURCES = celt/tests/test_unit_mdct.c
-celt_tests_test_unit_mdct_LDADD = $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
+celt_tests_test_unit_mdct_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
celt_tests_test_unit_mdct_LDADD += libarmasm.la
endif
celt_tests_test_unit_rotation_SOURCES = celt/tests/test_unit_rotation.c
-celt_tests_test_unit_rotation_LDADD = $(CELT_OBJ) $(NE10_LIBS) $(LIBM)
+celt_tests_test_unit_rotation_LDADD = $(CELT_OBJ) $(LPCNET_OBJ) $(NE10_LIBS) $(LIBM)
if OPUS_ARM_EXTERNAL_ASM
celt_tests_test_unit_rotation_LDADD += libarmasm.la
endif
@@ -217,6 +292,32 @@ opus_custom_demo_LDADD = libopus.la $(LIBM)
endif
endif
+if EXTRA_PROGRAMS
+if ENABLE_DEEP_PLC
+noinst_PROGRAMS += lpcnet_demo dump_data dump_weights_blob
+lpcnet_demo_SOURCES = dnn/lpcnet_demo.c
+lpcnet_demo_LDADD = $(LPCNET_OBJ) $(CELT_OBJ) $(LIBM)
+
+dump_data_SOURCES = dnn/dump_data.c
+dump_data_LDADD = $(LPCNET_OBJ) $(CELT_OBJ) $(LIBM)
+
+dump_weights_blob_SOURCES = dnn/write_lpcnet_weights.c
+dump_weights_blob_LDADD = $(LIBM)
+dump_weights_blob_CFLAGS = $(AM_CFLAGS) -DDUMP_BINARY_WEIGHTS
+endif
+if ENABLE_DRED
+TESTS += tests/test_opus_dred
+endif
+
+if ENABLE_LOSSGEN
+noinst_PROGRAMS += lossgen_demo
+lossgen_demo_SOURCES = dnn/lossgen_demo.c $(LOSSGEN_SOURCES)
+lossgen_demo_LDADD = $(LIBM)
+endif
+
+endif
+
+
EXTRA_DIST = opus.pc.in \
opus-uninstalled.pc.in \
opus.m4 \
@@ -249,21 +350,7 @@ EXTRA_DIST = opus.pc.in \
doc/meson.build \
tests/run_vectors.sh \
celt/arm/arm2gnu.pl \
- celt/arm/celt_pitch_xcorr_arm.s \
- win32/VS2015/opus.vcxproj \
- win32/VS2015/test_opus_encode.vcxproj.filters \
- win32/VS2015/test_opus_encode.vcxproj \
- win32/VS2015/opus_demo.vcxproj \
- win32/VS2015/test_opus_api.vcxproj.filters \
- win32/VS2015/test_opus_api.vcxproj \
- win32/VS2015/test_opus_decode.vcxproj.filters \
- win32/VS2015/opus_demo.vcxproj.filters \
- win32/VS2015/opus.vcxproj.filters \
- win32/VS2015/test_opus_decode.vcxproj \
- win32/VS2015/opus.sln \
- win32/VS2015/common.props \
- win32/genversion.bat \
- win32/config.h
+ celt/arm/celt_pitch_xcorr_arm.s
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = opus.pc
@@ -362,21 +449,37 @@ $(SSE_OBJ): CFLAGS += $(OPUS_X86_SSE_CFLAGS)
endif
if HAVE_SSE2
-SSE2_OBJ = $(CELT_SOURCES_SSE2:.c=.lo)
+SSE2_OBJ = $(CELT_SOURCES_SSE2:.c=.lo) \
+ $(DNN_SOURCES_SSE2:.c=.lo)
$(SSE2_OBJ): CFLAGS += $(OPUS_X86_SSE2_CFLAGS)
endif
if HAVE_SSE4_1
SSE4_1_OBJ = $(CELT_SOURCES_SSE4_1:.c=.lo) \
+ $(DNN_SOURCES_SSE4_1:.c=.lo) \
$(SILK_SOURCES_SSE4_1:.c=.lo) \
$(SILK_SOURCES_FIXED_SSE4_1:.c=.lo)
$(SSE4_1_OBJ): CFLAGS += $(OPUS_X86_SSE4_1_CFLAGS)
endif
+if HAVE_AVX2
+AVX2_OBJ = $(CELT_SOURCES_AVX2:.c=.lo) \
+ $(SILK_SOURCES_AVX2:.c=.lo) \
+ $(SILK_SOURCES_FLOAT_AVX2:.c=.lo) \
+ $(DNN_SOURCES_AVX2:.c=.lo)
+$(AVX2_OBJ): CFLAGS += $(OPUS_X86_AVX2_CFLAGS)
+endif
+
if HAVE_ARM_NEON_INTR
ARM_NEON_INTR_OBJ = $(CELT_SOURCES_ARM_NEON_INTR:.c=.lo) \
$(SILK_SOURCES_ARM_NEON_INTR:.c=.lo) \
+ $(DNN_SOURCES_NEON:.c=.lo) \
$(SILK_SOURCES_FIXED_ARM_NEON_INTR:.c=.lo)
$(ARM_NEON_INTR_OBJ): CFLAGS += \
$(OPUS_ARM_NEON_INTR_CFLAGS) $(NE10_CFLAGS)
endif
+
+if HAVE_ARM_DOTPROD
+ARM_DOTPROD_OBJ = $(DNN_SOURCES_DOTPROD:.c=.lo)
+$(ARM_DOTPROD_OBJ): CFLAGS += $(ARM_DOTPROD_INTR_CFLAGS)
+endif
diff --git a/Makefile.mips b/Makefile.mips
index e9bfc22e..8e8f837c 100644
--- a/Makefile.mips
+++ b/Makefile.mips
@@ -102,13 +102,16 @@ TESTOPUSDECODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSDECODE_SRCS_C))
TESTOPUSENCODE_SRCS_C = tests/test_opus_encode.c tests/opus_encode_regressions.c
TESTOPUSENCODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSENCODE_SRCS_C))
+TESTOPUSEXTENSIONS_SRCS_C = tests/test_opus_extensions.c
+TESTOPUSEXTENSIONS_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSEXTENSIONS_SRCS_C))
+
TESTOPUSPADDING_SRCS_C = tests/test_opus_padding.c
TESTOPUSPADDING_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSPADDING_SRCS_C))
OPUSCOMPARE_SRCS_C = src/opus_compare.c
OPUSCOMPARE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSCOMPARE_SRCS_C))
-TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_padding
+TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_extensions test_opus_padding
# Rules
all: lib opus_demo opus_compare $(TESTS)
@@ -133,6 +136,9 @@ test_opus_decode$(EXESUFFIX): $(TESTOPUSDECODE_OBJS) $(TARGET)
test_opus_encode$(EXESUFFIX): $(TESTOPUSENCODE_OBJS) $(TARGET)
$(LINK.o.cmdline)
+test_opus_extensions$(EXESUFFIX): $(TESTOPUSEXTENSIONS_OBJS) $(TARGET)
+ $(LINK.o.cmdline)
+
test_opus_padding$(EXESUFFIX): $(TESTOPUSPADDING_OBJS) $(TARGET)
$(LINK.o.cmdline)
@@ -154,8 +160,10 @@ force:
clean:
rm -f opus_demo$(EXESUFFIX) opus_compare$(EXESUFFIX) $(TARGET) \
test_opus_api$(EXESUFFIX) test_opus_decode$(EXESUFFIX) \
- test_opus_encode$(EXESUFFIX) test_opus_padding$(EXESUFFIX) \
+ test_opus_encode$(EXESUFFIX) test_opus_extensions$(EXESUFFIX) \
+ test_opus_padding$(EXESUFFIX)
$(OBJS) $(OPUSDEMO_OBJS) $(OPUSCOMPARE_OBJS) $(TESTOPUSAPI_OBJS) \
- $(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) $(TESTOPUSPADDING_OBJS)
+ $(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) \
+ $(TESTOPUSEXTENSIONS_OBJS) $(TESTOPUSPADDING_OBJS)
.PHONY: all lib clean force check
diff --git a/Makefile.unix b/Makefile.unix
index 90a48f0c..5a09194f 100644
--- a/Makefile.unix
+++ b/Makefile.unix
@@ -100,13 +100,16 @@ TESTOPUSDECODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSDECODE_SRCS_C))
TESTOPUSENCODE_SRCS_C = tests/test_opus_encode.c tests/opus_encode_regressions.c
TESTOPUSENCODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSENCODE_SRCS_C))
+TESTOPUSEXTENSIONS_SRCS_C = tests/test_opus_extensions.c
+TESTOPUSEXTENSIONS_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSEXTENSIONS_SRCS_C))
+
TESTOPUSPADDING_SRCS_C = tests/test_opus_padding.c
TESTOPUSPADDING_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSPADDING_SRCS_C))
OPUSCOMPARE_SRCS_C = src/opus_compare.c
OPUSCOMPARE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSCOMPARE_SRCS_C))
-TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_padding
+TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_extensions test_opus_padding
# Rules
all: lib opus_demo opus_compare $(TESTS)
@@ -131,6 +134,9 @@ test_opus_decode$(EXESUFFIX): $(TESTOPUSDECODE_OBJS) $(TARGET)
test_opus_encode$(EXESUFFIX): $(TESTOPUSENCODE_OBJS) $(TARGET)
$(LINK.o.cmdline)
+test_opus_extensions$(EXESUFFIX): $(TESTOPUSEXTENSIONS_OBJS) $(TARGET)
+ $(LINK.o.cmdline)
+
test_opus_padding$(EXESUFFIX): $(TESTOPUSPADDING_OBJS) $(TARGET)
$(LINK.o.cmdline)
@@ -152,8 +158,10 @@ force:
clean:
rm -f opus_demo$(EXESUFFIX) opus_compare$(EXESUFFIX) $(TARGET) \
test_opus_api$(EXESUFFIX) test_opus_decode$(EXESUFFIX) \
- test_opus_encode$(EXESUFFIX) test_opus_padding$(EXESUFFIX) \
+ test_opus_encode$(EXESUFFIX) test_opus_extensions$(EXESUFFIX) \
+ test_opus_padding$(EXESUFFIX)
$(OBJS) $(OPUSDEMO_OBJS) $(OPUSCOMPARE_OBJS) $(TESTOPUSAPI_OBJS) \
- $(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) $(TESTOPUSPADDING_OBJS)
+ $(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) \
+ $(TESTOPUSEXTENSIONS_OBJS) $(TESTOPUSPADDING_OBJS)
.PHONY: all lib clean force check
diff --git a/README b/README
index 4b13076f..bcf2376d 100644
--- a/README
+++ b/README
@@ -22,7 +22,7 @@ This package implements a shared library for encoding and decoding raw Opus
bitstreams. Raw Opus bitstreams should be used over RTP according to
https://tools.ietf.org/html/rfc7587
-The package also includes a number of test tools used for testing the
+The package also includes a number of test tools used for testing the
correct operation of the library. The bitstreams read/written by these
tools should not be used for Opus file distribution: They include
additional debugging data and cannot support seeking.
@@ -39,6 +39,28 @@ Opus-tools can be found at:
or on the main Opus website:
https://opus-codec.org/
+== Deep Learning and Opus ==
+
+Lossy networks continue to be a challenge for real-time communications.
+While the original implementation of Opus provides an excellent packet loss
+concealment mechanism, the team has continued to advance the methodology used
+to improve audio quality in challenge network environments.
+
+In Opus 1.5, we added a deep learning based redundancy encoder that enhances
+audio in lossy networks by embedding one second of recovery data in the padding
+data of each packet. The underlying algorithm behind encoding and decoding the
+recovery data is called the deep redundancy (DRED) algorithm. By leveraging
+the padding data within the packet, Opus 1.5 is fully backward compatible with
+prior revisions of Opus. Please see the README under the "dnn" subdirectory to
+understand DRED.
+
+DRED was developed by a team that Amazon Web Services initially sponsored,
+who open-sourced the implementation as well as began the
+standardization process at the IETF:
+ https://datatracker.ietf.org/doc/draft-ietf-mlcodec-opus-extension/
+The license behind Opus or the intellectual property position of Opus does
+not change with Opus 1.5.
+
== Compiling libopus ==
To build from a distribution tarball, you only need to do the following:
@@ -77,6 +99,8 @@ On Apple macOS, install Xcode and brew.sh, then in the Terminal enter:
% ./configure
% make
+On x86, it's a good idea to use a -march= option that allows the use of AVX2.
+
3) Install the codec libraries (optional)
% sudo make install
@@ -133,6 +157,10 @@ To run compare the code to these test vectors:
% tar -zxf opus_testvectors-rfc8251.tar.gz
% ./tests/run_vectors.sh ./ opus_newvectors 48000
+== Compiling libopus for Windows and alternative build systems ==
+
+See cmake/README.md or meson/README.md.
+
== Portability notes ==
This implementation uses floating-point by default but can be compiled to
diff --git a/autogen.bat b/autogen.bat
new file mode 100644
index 00000000..b7c735b1
--- /dev/null
+++ b/autogen.bat
@@ -0,0 +1,13 @@
+@echo off
+REM Run this to set up the build system: configure, makefiles, etc.
+
+setlocal enabledelayedexpansion
+
+REM Parse the real autogen.sh script for version
+for /F "tokens=2 delims= " %%A in ('findstr "dnn/download_model.sh" autogen.sh') do (
+ set "model=%%A"
+)
+
+call dnn\download_model.bat %model%
+
+echo Updating build configuration files, please wait....
diff --git a/autogen.sh b/autogen.sh
index 380d1f3c..2b3c1e9e 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -9,6 +9,8 @@ set -e
srcdir=`dirname $0`
test -n "$srcdir" && cd "$srcdir"
+dnn/download_model.sh "735117b"
+
echo "Updating build configuration files, please wait...."
autoreconf -isf
diff --git a/celt/arm/arm_celt_map.c b/celt/arm/arm_celt_map.c
index ca988b66..cbaea495 100644
--- a/celt/arm/arm_celt_map.c
+++ b/celt/arm/arm_celt_map.c
@@ -40,7 +40,8 @@ opus_val32 (*const CELT_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, c
celt_inner_prod_c, /* ARMv4 */
celt_inner_prod_c, /* EDSP */
celt_inner_prod_c, /* Media */
- celt_inner_prod_neon /* NEON */
+ celt_inner_prod_neon,/* NEON */
+ celt_inner_prod_neon /* DOTPROD */
};
void (*const DUAL_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02,
@@ -48,7 +49,8 @@ void (*const DUAL_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, const o
dual_inner_prod_c, /* ARMv4 */
dual_inner_prod_c, /* EDSP */
dual_inner_prod_c, /* Media */
- dual_inner_prod_neon /* NEON */
+ dual_inner_prod_neon,/* NEON */
+ dual_inner_prod_neon /* DOTPROD */
};
# endif
@@ -61,7 +63,8 @@ opus_val32 (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *,
celt_pitch_xcorr_c, /* ARMv4 */
MAY_HAVE_EDSP(celt_pitch_xcorr), /* EDSP */
MAY_HAVE_MEDIA(celt_pitch_xcorr), /* Media */
- MAY_HAVE_NEON(celt_pitch_xcorr) /* NEON */
+ MAY_HAVE_NEON(celt_pitch_xcorr), /* NEON */
+ MAY_HAVE_NEON(celt_pitch_xcorr) /* DOTPROD */
};
# endif
@@ -72,7 +75,8 @@ void (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *,
celt_pitch_xcorr_c, /* ARMv4 */
celt_pitch_xcorr_c, /* EDSP */
celt_pitch_xcorr_c, /* Media */
- celt_pitch_xcorr_float_neon /* Neon */
+ celt_pitch_xcorr_float_neon, /* Neon */
+ celt_pitch_xcorr_float_neon /* DOTPROD */
};
# endif
# endif /* FIXED_POINT */
@@ -90,6 +94,7 @@ void (*const XCORR_KERNEL_IMPL[OPUS_ARCHMASK + 1])(
xcorr_kernel_c, /* EDSP */
xcorr_kernel_c, /* Media */
xcorr_kernel_neon_fixed, /* Neon */
+ xcorr_kernel_neon_fixed /* DOTPROD */
};
#endif
@@ -101,14 +106,16 @@ int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])(kiss_fft_state *st) = {
opus_fft_alloc_arch_c, /* ARMv4 */
opus_fft_alloc_arch_c, /* EDSP */
opus_fft_alloc_arch_c, /* Media */
- opus_fft_alloc_arm_neon /* Neon with NE10 library support */
+ opus_fft_alloc_arm_neon, /* Neon with NE10 library support */
+ opus_fft_alloc_arm_neon /* DOTPROD with NE10 library support */
};
void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])(kiss_fft_state *st) = {
opus_fft_free_arch_c, /* ARMv4 */
opus_fft_free_arch_c, /* EDSP */
opus_fft_free_arch_c, /* Media */
- opus_fft_free_arm_neon /* Neon with NE10 */
+ opus_fft_free_arm_neon, /* Neon with NE10 */
+ opus_fft_free_arm_neon /* DOTPROD with NE10 */
};
# endif /* CUSTOM_MODES */
@@ -118,7 +125,8 @@ void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
opus_fft_c, /* ARMv4 */
opus_fft_c, /* EDSP */
opus_fft_c, /* Media */
- opus_fft_neon /* Neon with NE10 */
+ opus_fft_neon, /* Neon with NE10 */
+ opus_fft_neon /* DOTPROD with NE10 */
};
void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
@@ -127,7 +135,8 @@ void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg,
opus_ifft_c, /* ARMv4 */
opus_ifft_c, /* EDSP */
opus_ifft_c, /* Media */
- opus_ifft_neon /* Neon with NE10 */
+ opus_ifft_neon, /* Neon with NE10 */
+ opus_ifft_neon /* DOTPROD with NE10 */
};
void (*const CLT_MDCT_FORWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l,
@@ -139,7 +148,8 @@ void (*const CLT_MDCT_FORWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l,
clt_mdct_forward_c, /* ARMv4 */
clt_mdct_forward_c, /* EDSP */
clt_mdct_forward_c, /* Media */
- clt_mdct_forward_neon /* Neon with NE10 */
+ clt_mdct_forward_neon, /* Neon with NE10 */
+ clt_mdct_forward_neon /* DOTPROD with NE10 */
};
void (*const CLT_MDCT_BACKWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l,
@@ -151,7 +161,8 @@ void (*const CLT_MDCT_BACKWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l,
clt_mdct_backward_c, /* ARMv4 */
clt_mdct_backward_c, /* EDSP */
clt_mdct_backward_c, /* Media */
- clt_mdct_backward_neon /* Neon with NE10 */
+ clt_mdct_backward_neon, /* Neon with NE10 */
+ clt_mdct_backward_neon /* DOTPROD with NE10 */
};
# endif /* HAVE_ARM_NE10 */
diff --git a/celt/arm/armcpu.c b/celt/arm/armcpu.c
index c7d16e6d..06a53435 100644
--- a/celt/arm/armcpu.c
+++ b/celt/arm/armcpu.c
@@ -43,6 +43,7 @@
#define OPUS_CPU_ARM_EDSP_FLAG (1<<OPUS_ARCH_ARM_EDSP)
#define OPUS_CPU_ARM_MEDIA_FLAG (1<<OPUS_ARCH_ARM_MEDIA)
#define OPUS_CPU_ARM_NEON_FLAG (1<<OPUS_ARCH_ARM_NEON)
+#define OPUS_CPU_ARM_DOTPROD_FLAG (1<<OPUS_ARCH_ARM_DOTPROD)
#if defined(_MSC_VER)
/*For GetExceptionCode() and EXCEPTION_ILLEGAL_INSTRUCTION.*/
@@ -126,6 +127,14 @@ opus_uint32 opus_cpu_capabilities(void)
p = strstr(buf, " neon");
if(p != NULL && (p[5] == ' ' || p[5] == '\n'))
flags |= OPUS_CPU_ARM_NEON_FLAG;
+ p = strstr(buf, " asimd");
+ if(p != NULL && (p[6] == ' ' || p[6] == '\n'))
+ flags |= OPUS_CPU_ARM_NEON_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_EDSP_FLAG;
+# endif
+# if defined(OPUS_ARM_MAY_HAVE_DOTPROD)
+ p = strstr(buf, " asimddp");
+ if(p != NULL && (p[8] == ' ' || p[8] == '\n'))
+ flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
# endif
}
# endif
@@ -144,10 +153,44 @@ opus_uint32 opus_cpu_capabilities(void)
# endif
}
+#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR)
+ flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG;
+# if defined(OPUS_ARM_PRESUME_DOTPROD)
+ flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
+# endif
+#endif
+
fclose(cpuinfo);
}
return flags;
}
+
+#elif defined(__APPLE__)
+#include <sys/types.h>
+#include <sys/sysctl.h>
+
+opus_uint32 opus_cpu_capabilities(void)
+{
+ opus_uint32 flags = 0;
+
+#if defined(OPUS_ARM_MAY_HAVE_DOTPROD)
+ size_t size = sizeof(uint32_t);
+ uint32_t value = 0;
+ if (!sysctlbyname("hw.optional.arm.FEAT_DotProd", &value, &size, NULL, 0) && value)
+ {
+ flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
+ }
+#endif
+
+#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR)
+ flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG;
+# if defined(OPUS_ARM_PRESUME_DOTPROD)
+ flags |= OPUS_CPU_ARM_DOTPROD_FLAG;
+# endif
+#endif
+ return flags;
+}
+
#else
/* The feature registers which can tell us what the processor supports are
* accessible in priveleged modes only, so we can't have a general user-space
@@ -180,7 +223,13 @@ static int opus_select_arch_impl(void)
}
arch++;
- celt_assert(arch == OPUS_ARCH_ARM_NEON);
+ if(!(flags & OPUS_CPU_ARM_DOTPROD_FLAG)) {
+ celt_assert(arch == OPUS_ARCH_ARM_NEON);
+ return arch;
+ }
+ arch++;
+
+ celt_assert(arch == OPUS_ARCH_ARM_DOTPROD);
return arch;
}
diff --git a/celt/arm/armcpu.h b/celt/arm/armcpu.h
index 820262ff..6d5803d8 100644
--- a/celt/arm/armcpu.h
+++ b/celt/arm/armcpu.h
@@ -46,6 +46,12 @@
# define MAY_HAVE_NEON(name) MAY_HAVE_MEDIA(name)
# endif
+# if defined(OPUS_ARM_MAY_HAVE_DOTPROD)
+# define MAY_HAVE_DOTPROD(name) name ## _dotprod
+# else
+# define MAY_HAVE_DOTPROD(name) MAY_HAVE_NEON(name)
+# endif
+
# if defined(OPUS_ARM_PRESUME_EDSP)
# define PRESUME_EDSP(name) name ## _edsp
# else
@@ -64,6 +70,12 @@
# define PRESUME_NEON(name) PRESUME_MEDIA(name)
# endif
+# if defined(OPUS_ARM_PRESUME_DOTPROD)
+# define PRESUME_DOTPROD(name) name ## _dotprod
+# else
+# define PRESUME_DOTPROD(name) PRESUME_NEON(name)
+# endif
+
# if defined(OPUS_HAVE_RTCD)
int opus_select_arch(void);
@@ -71,6 +83,7 @@ int opus_select_arch(void);
#define OPUS_ARCH_ARM_EDSP (1)
#define OPUS_ARCH_ARM_MEDIA (2)
#define OPUS_ARCH_ARM_NEON (3)
+#define OPUS_ARCH_ARM_DOTPROD (4)
# endif
diff --git a/celt/arm/celt_neon_intr.c b/celt/arm/celt_neon_intr.c
index effda769..250f8362 100644
--- a/celt/arm/celt_neon_intr.c
+++ b/celt/arm/celt_neon_intr.c
@@ -38,6 +38,8 @@
#include "../pitch.h"
#if defined(FIXED_POINT)
+#include <string.h>
+
void xcorr_kernel_neon_fixed(const opus_val16 * x, const opus_val16 * y, opus_val32 sum[4], int len)
{
int j;
@@ -47,7 +49,10 @@ void xcorr_kernel_neon_fixed(const opus_val16 * x, const opus_val16 * y, opus_va
int16x4_t y0 = vld1_s16(y);
y += 4;
- for (j = 0; j + 8 <= len; j += 8)
+ /* This loop loads one y value more than we actually need.
+ Therefore we have to stop as soon as there are 8 or fewer samples left
+ (instead of 7), to avoid reading past the end of the array. */
+ for (j = 0; j + 8 < len; j += 8)
{
/* Load x[0...7] */
int16x8_t xx = vld1q_s16(x);
@@ -80,23 +85,79 @@ void xcorr_kernel_neon_fixed(const opus_val16 * x, const opus_val16 * y, opus_va
x += 8;
y += 8;
}
-
- for (; j < len; j++)
- {
- int16x4_t x0 = vld1_dup_s16(x); /* load next x */
+ if (j + 4 < len) {
+ /* Load x[0...3] */
+ int16x4_t x0 = vld1_s16(x);
+ /* Load y[4...7] */
+ int16x4_t y4 = vld1_s16(y);
+ int32x4_t a0 = vmlal_lane_s16(a, y0, x0, 0);
+ int16x4_t y1 = vext_s16(y0, y4, 1);
+ int32x4_t a1 = vmlal_lane_s16(a0, y1, x0, 1);
+ int16x4_t y2 = vext_s16(y0, y4, 2);
+ int32x4_t a2 = vmlal_lane_s16(a1, y2, x0, 2);
+ int16x4_t y3 = vext_s16(y0, y4, 3);
+ int32x4_t a3 = vmlal_lane_s16(a2, y3, x0, 3);
+ y0 = y4;
+ a = a3;
+ x += 4;
+ y += 4;
+ j += 4;
+ }
+ if (j + 2 < len) {
+ /* Load x[0...1] */
+ int16x4x2_t xx = vld2_dup_s16(x);
+ int16x4_t x0 = xx.val[0];
+ int16x4_t x1 = xx.val[1];
+ /* Load y[4...5].
+ We would like to use vld1_dup_s32(), but casting the pointer would
+ break strict aliasing rules and potentially have alignment issues.
+ Fortunately the compiler seems capable of translating this memcpy()
+ and vdup_n_s32() into the equivalent vld1_dup_s32().*/
+ int32_t yy;
+ memcpy(&yy, y, sizeof(yy));
+ int16x4_t y4 = vreinterpret_s16_s32(vdup_n_s32(yy));
int32x4_t a0 = vmlal_s16(a, y0, x0);
-
- int16x4_t y4 = vld1_dup_s16(y); /* load next y */
- y0 = vext_s16(y0, y4, 1);
+ int16x4_t y1 = vext_s16(y0, y4, 1);
+ /* Replace bottom copy of {y[5], y[4]} in y4 with {y[3], y[2]} from y0,
+ using VSRI instead of VEXT, since it's a data-processing
+ instruction. */
+ y0 = vreinterpret_s16_s64(vsri_n_s64(vreinterpret_s64_s16(y4),
+ vreinterpret_s64_s16(y0), 32));
+ int32x4_t a1 = vmlal_s16(a0, y1, x1);
+ a = a1;
+ x += 2;
+ y += 2;
+ j += 2;
+ }
+ if (j + 1 < len) {
+ /* Load next x. */
+ int16x4_t x0 = vld1_dup_s16(x);
+ int32x4_t a0 = vmlal_s16(a, y0, x0);
+ /* Load last y. */
+ int16x4_t y4 = vld1_dup_s16(y);
+ y0 = vreinterpret_s16_s64(vsri_n_s64(vreinterpret_s64_s16(y4),
+ vreinterpret_s64_s16(y0), 16));
a = a0;
x++;
- y++;
}
-
- vst1q_s32(sum, a);
+ /* Load last x. */
+ int16x4_t x0 = vld1_dup_s16(x);
+ int32x4_t a0 = vmlal_s16(a, y0, x0);
+ vst1q_s32(sum, a0);
}
#else
+
+#if defined(__ARM_FEATURE_FMA) && defined(__ARM_ARCH_ISA_A64)
+/* If we can, force the compiler to use an FMA instruction rather than break
+ * vmlaq_f32() into fmul/fadd. */
+#ifdef vmlaq_lane_f32
+#undef vmlaq_lane_f32
+#endif
+#define vmlaq_lane_f32(a,b,c,lane) vfmaq_lane_f32(a,b,c,lane)
+#endif
+
+
/*
* Function: xcorr_kernel_neon_float
* ---------------------------------
diff --git a/celt/arm/pitch_neon_intr.c b/celt/arm/pitch_neon_intr.c
index 35cc46e2..43885f52 100644
--- a/celt/arm/pitch_neon_intr.c
+++ b/celt/arm/pitch_neon_intr.c
@@ -130,6 +130,13 @@ void dual_inner_prod_neon(const opus_val16 *x, const opus_val16 *y01, const opus
/* ========================================================================== */
+#ifdef __ARM_FEATURE_FMA
+/* If we can, force the compiler to use an FMA instruction rather than break
+ vmlaq_f32() into fmul/fadd. */
+#define vmlaq_f32(a,b,c) vfmaq_f32(a,b,c)
+#endif
+
+
#ifdef OPUS_CHECK_ASM
/* This part of code simulates floating-point NEON operations. */
diff --git a/celt/bands.c b/celt/bands.c
index 5320ffab..6785e08e 100644
--- a/celt/bands.c
+++ b/celt/bands.c
@@ -1450,7 +1450,7 @@ void quant_all_bands(int encode, const CELTMode *m, int start, int end,
if (encode && resynth)
lowband_scratch = _lowband_scratch;
else
- lowband_scratch = X_+M*eBands[m->nbEBands-1];
+ lowband_scratch = X_+M*eBands[m->effEBands-1];
ALLOC(X_save, resynth_alloc, celt_norm);
ALLOC(Y_save, resynth_alloc, celt_norm);
ALLOC(X_save2, resynth_alloc, celt_norm);
diff --git a/celt/celt.h b/celt/celt.h
index 24b6b2b5..2f501951 100644
--- a/celt/celt.h
+++ b/celt/celt.h
@@ -42,6 +42,10 @@
#include "entdec.h"
#include "arch.h"
+#ifdef ENABLE_DEEP_PLC
+#include "lpcnet.h"
+#endif
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -149,6 +153,13 @@ int celt_decoder_get_size(int channels);
int celt_decoder_init(CELTDecoder *st, opus_int32 sampling_rate, int channels);
+int celt_decode_with_ec_dred(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data,
+ int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum
+#ifdef ENABLE_DEEP_PLC
+ ,LPCNetPLCState *lpcnet
+#endif
+ );
+
int celt_decode_with_ec(OpusCustomDecoder * OPUS_RESTRICT st, const unsigned char *data,
int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum);
@@ -225,23 +236,13 @@ void comb_filter(opus_val32 *y, opus_val32 *x, int T0, int T1, int N,
opus_val16 g0, opus_val16 g1, int tapset0, int tapset1,
const opus_val16 *window, int overlap, int arch);
-#ifdef NON_STATIC_COMB_FILTER_CONST_C
-void comb_filter_const_c(opus_val32 *y, opus_val32 *x, int T, int N,
- opus_val16 g10, opus_val16 g11, opus_val16 g12);
-#endif
-
-#ifndef OVERRIDE_COMB_FILTER_CONST
-# define comb_filter_const(y, x, T, N, g10, g11, g12, arch) \
- ((void)(arch),comb_filter_const_c(y, x, T, N, g10, g11, g12))
-#endif
-
void init_caps(const CELTMode *m,int *cap,int LM,int C);
#ifdef RESYNTH
-void deemphasis(celt_sig *in[], opus_val16 *pcm, int N, int C, int downsample, const opus_val16 *coef, celt_sig *mem);
+void deemphasis(celt_sig *in[], opus_val16 *pcm, int N, int C, int downsample, const opus_val16 *coef, celt_sig *mem, int accum);
void celt_synthesis(const CELTMode *mode, celt_norm *X, celt_sig * out_syn[],
opus_val16 *oldBandE, int start, int effEnd, int C, int CC, int isTransient,
- int LM, int downsample, int silence);
+ int LM, int downsample, int silence, int arch);
#endif
#ifdef __cplusplus
diff --git a/celt/celt_decoder.c b/celt/celt_decoder.c
index 883dae15..743c2031 100644
--- a/celt/celt_decoder.c
+++ b/celt/celt_decoder.c
@@ -51,6 +51,11 @@
#include "celt_lpc.h"
#include "vq.h"
+#ifdef ENABLE_DEEP_PLC
+#include "lpcnet.h"
+#include "lpcnet_private.h"
+#endif
+
/* The maximum pitch lag to allow in the pitch-based PLC. It's possible to save
CPU time in the PLC pitch search by making this smaller than MAX_PERIOD. The
current value corresponds to a pitch of 66.67 Hz. */
@@ -59,9 +64,6 @@
pitch of 480 Hz. */
#define PLC_PITCH_LAG_MIN (100)
-#if defined(SMALL_FOOTPRINT) && defined(FIXED_POINT)
-#define NORM_ALIASING_HACK
-#endif
/**********************************************************************/
/* */
/* DECODER */
@@ -69,6 +71,9 @@
/**********************************************************************/
#define DECODE_BUFFER_SIZE 2048
+#define PLC_UPDATE_FRAMES 4
+#define PLC_UPDATE_SAMPLES (PLC_UPDATE_FRAMES*FRAME_SIZE)
+
/** Decoder state
@brief Decoder state
*/
@@ -82,6 +87,7 @@ struct OpusCustomDecoder {
int start, end;
int signalling;
int disable_inv;
+ int complexity;
int arch;
/* Everything beyond this point gets cleared on a reset */
@@ -98,11 +104,18 @@ struct OpusCustomDecoder {
opus_val16 postfilter_gain_old;
int postfilter_tapset;
int postfilter_tapset_old;
+ int prefilter_and_fold;
celt_sig preemph_memD[2];
+#ifdef ENABLE_DEEP_PLC
+ opus_int16 plc_pcm[PLC_UPDATE_SAMPLES];
+ int plc_fill;
+ float plc_preemphasis_mem;
+#endif
+
celt_sig _decode_mem[1]; /* Size = channels*(DECODE_BUFFER_SIZE+mode->overlap) */
- /* opus_val16 lpc[], Size = channels*LPC_ORDER */
+ /* opus_val16 lpc[], Size = channels*CELT_LPC_ORDER */
/* opus_val16 oldEBands[], Size = 2*mode->nbEBands */
/* opus_val16 oldLogE[], Size = 2*mode->nbEBands */
/* opus_val16 oldLogE2[], Size = 2*mode->nbEBands */
@@ -157,7 +170,7 @@ OPUS_CUSTOM_NOSTATIC int opus_custom_decoder_get_size(const CELTMode *mode, int
{
int size = sizeof(struct CELTDecoder)
+ (channels*(DECODE_BUFFER_SIZE+mode->overlap)-1)*sizeof(celt_sig)
- + channels*LPC_ORDER*sizeof(opus_val16)
+ + channels*CELT_LPC_ORDER*sizeof(opus_val16)
+ 4*2*mode->nbEBands*sizeof(opus_val16);
return size;
}
@@ -499,7 +512,100 @@ static int celt_plc_pitch_search(celt_sig *decode_mem[2], int C, int arch)
return pitch_index;
}
-static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
+static void prefilter_and_fold(CELTDecoder * OPUS_RESTRICT st, int N)
+{
+ int c;
+ int CC;
+ int i;
+ int overlap;
+ celt_sig *decode_mem[2];
+ const OpusCustomMode *mode;
+ VARDECL(opus_val32, etmp);
+ mode = st->mode;
+ overlap = st->overlap;
+ CC = st->channels;
+ ALLOC(etmp, overlap, opus_val32);
+ c=0; do {
+ decode_mem[c] = st->_decode_mem + c*(DECODE_BUFFER_SIZE+overlap);
+ } while (++c<CC);
+
+ c=0; do {
+ /* Apply the pre-filter to the MDCT overlap for the next frame because
+ the post-filter will be re-applied in the decoder after the MDCT
+ overlap. */
+ comb_filter(etmp, decode_mem[c]+DECODE_BUFFER_SIZE-N,
+ st->postfilter_period_old, st->postfilter_period, overlap,
+ -st->postfilter_gain_old, -st->postfilter_gain,
+ st->postfilter_tapset_old, st->postfilter_tapset, NULL, 0, st->arch);
+
+ /* Simulate TDAC on the concealed audio so that it blends with the
+ MDCT of the next frame. */
+ for (i=0;i<overlap/2;i++)
+ {
+ decode_mem[c][DECODE_BUFFER_SIZE-N+i] =
+ MULT16_32_Q15(mode->window[i], etmp[overlap-1-i])
+ + MULT16_32_Q15(mode->window[overlap-i-1], etmp[i]);
+ }
+ } while (++c<CC);
+}
+
+#ifdef ENABLE_DEEP_PLC
+
+#define SINC_ORDER 48
+/* h=cos(pi/2*abs(sin([-24:24]/48*pi*23./24)).^2);
+ b=sinc([-24:24]/3*1.02).*h;
+ b=b/sum(b); */
+static const float sinc_filter[SINC_ORDER+1] = {
+ 4.2931e-05f, -0.000190293f, -0.000816132f, -0.000637162f, 0.00141662f, 0.00354764f, 0.00184368f, -0.00428274f,
+ -0.00856105f, -0.0034003f, 0.00930201f, 0.0159616f, 0.00489785f, -0.0169649f, -0.0259484f, -0.00596856f,
+ 0.0286551f, 0.0405872f, 0.00649994f, -0.0509284f, -0.0716655f, -0.00665212f, 0.134336f, 0.278927f,
+ 0.339995f, 0.278927f, 0.134336f, -0.00665212f, -0.0716655f, -0.0509284f, 0.00649994f, 0.0405872f,
+ 0.0286551f, -0.00596856f, -0.0259484f, -0.0169649f, 0.00489785f, 0.0159616f, 0.00930201f, -0.0034003f,
+ -0.00856105f, -0.00428274f, 0.00184368f, 0.00354764f, 0.00141662f, -0.000637162f, -0.000816132f, -0.000190293f,
+ 4.2931e-05f
+};
+
+void update_plc_state(LPCNetPLCState *lpcnet, celt_sig *decode_mem[2], float *plc_preemphasis_mem, int CC)
+{
+ int i;
+ int tmp_read_post, tmp_fec_skip;
+ int offset;
+ celt_sig buf48k[DECODE_BUFFER_SIZE];
+ opus_int16 buf16k[PLC_UPDATE_SAMPLES];
+ if (CC == 1) OPUS_COPY(buf48k, decode_mem[0], DECODE_BUFFER_SIZE);
+ else {
+ for (i=0;i<DECODE_BUFFER_SIZE;i++) {
+ buf48k[i] = .5*(decode_mem[0][i] + decode_mem[1][i]);
+ }
+ }
+ /* Down-sample the last 40 ms. */
+ for (i=1;i<DECODE_BUFFER_SIZE;i++) buf48k[i] += PREEMPHASIS*buf48k[i-1];
+ *plc_preemphasis_mem = buf48k[DECODE_BUFFER_SIZE-1];
+ offset = DECODE_BUFFER_SIZE-SINC_ORDER-1 - 3*(PLC_UPDATE_SAMPLES-1);
+ celt_assert(3*(PLC_UPDATE_SAMPLES-1) + SINC_ORDER + offset == DECODE_BUFFER_SIZE-1);
+ for (i=0;i<PLC_UPDATE_SAMPLES;i++) {
+ int j;
+ float sum = 0;
+ for (j=0;j<SINC_ORDER+1;j++) {
+ sum += buf48k[3*i + j + offset]*sinc_filter[j];
+ }
+ buf16k[i] = float2int(MIN32(32767.f, MAX32(-32767.f, sum)));
+ }
+ tmp_read_post = lpcnet->fec_read_pos;
+ tmp_fec_skip = lpcnet->fec_skip;
+ for (i=0;i<PLC_UPDATE_FRAMES;i++) {
+ lpcnet_plc_update(lpcnet, &buf16k[FRAME_SIZE*i]);
+ }
+ lpcnet->fec_read_pos = tmp_read_post;
+ lpcnet->fec_skip = tmp_fec_skip;
+}
+#endif
+
+static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM
+#ifdef ENABLE_DEEP_PLC
+ ,LPCNetPLCState *lpcnet
+#endif
+ )
{
int c;
int i;
@@ -527,22 +633,22 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
out_syn[c] = decode_mem[c]+DECODE_BUFFER_SIZE-N;
} while (++c<C);
lpc = (opus_val16*)(st->_decode_mem+(DECODE_BUFFER_SIZE+overlap)*C);
- oldBandE = lpc+C*LPC_ORDER;
+ oldBandE = lpc+C*CELT_LPC_ORDER;
oldLogE = oldBandE + 2*nbEBands;
oldLogE2 = oldLogE + 2*nbEBands;
backgroundLogE = oldLogE2 + 2*nbEBands;
loss_duration = st->loss_duration;
start = st->start;
+#ifdef ENABLE_DEEP_PLC
+ noise_based = start != 0 || (lpcnet->fec_fill_pos == 0 && (st->skip_plc || loss_duration >= 80));
+#else
noise_based = loss_duration >= 40 || start != 0 || st->skip_plc;
+#endif
if (noise_based)
{
/* Noise-based PLC/CNG */
-#ifdef NORM_ALIASING_HACK
- celt_norm *X;
-#else
VARDECL(celt_norm, X);
-#endif
opus_uint32 seed;
int end;
int effEnd;
@@ -550,18 +656,16 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
end = st->end;
effEnd = IMAX(start, IMIN(end, mode->effEBands));
-#ifdef NORM_ALIASING_HACK
- /* This is an ugly hack that breaks aliasing rules and would be easily broken,
- but it saves almost 4kB of stack. */
- X = (celt_norm*)(out_syn[C-1]+overlap/2);
-#else
ALLOC(X, C*N, celt_norm); /**< Interleaved normalised MDCTs */
-#endif
c=0; do {
OPUS_MOVE(decode_mem[c], decode_mem[c]+N,
- DECODE_BUFFER_SIZE-N+(overlap>>1));
+ DECODE_BUFFER_SIZE-N+overlap);
} while (++c<C);
+ if (st->prefilter_and_fold) {
+ prefilter_and_fold(st, N);
+ }
+
/* Energy decay */
decay = loss_duration==0 ? QCONST16(1.5f, DB_SHIFT) : QCONST16(.5f, DB_SHIFT);
c=0; do
@@ -590,6 +694,9 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
st->rng = seed;
celt_synthesis(mode, X, out_syn, oldBandE, start, effEnd, C, C, 0, LM, st->downsample, 0, st->arch);
+ st->prefilter_and_fold = 0;
+ /* Skip regular PLC until we get two consecutive packets. */
+ st->skip_plc = 1;
} else {
int exc_length;
/* Pitch-based PLC */
@@ -597,12 +704,14 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
opus_val16 *exc;
opus_val16 fade = Q15ONE;
int pitch_index;
- VARDECL(opus_val32, etmp);
VARDECL(opus_val16, _exc);
VARDECL(opus_val16, fir_tmp);
if (loss_duration == 0)
{
+#ifdef ENABLE_DEEP_PLC
+ if (lpcnet->loaded) update_plc_state(lpcnet, decode_mem, &st->plc_preemphasis_mem, C);
+#endif
st->last_pitch_index = pitch_index = celt_plc_pitch_search(decode_mem, C, st->arch);
} else {
pitch_index = st->last_pitch_index;
@@ -613,10 +722,9 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
decaying signal, but we can't get more than MAX_PERIOD. */
exc_length = IMIN(2*pitch_index, MAX_PERIOD);
- ALLOC(etmp, overlap, opus_val32);
- ALLOC(_exc, MAX_PERIOD+LPC_ORDER, opus_val16);
+ ALLOC(_exc, MAX_PERIOD+CELT_LPC_ORDER, opus_val16);
ALLOC(fir_tmp, exc_length, opus_val16);
- exc = _exc+LPC_ORDER;
+ exc = _exc+CELT_LPC_ORDER;
window = mode->window;
c=0; do {
opus_val16 decay;
@@ -628,16 +736,16 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
int j;
buf = decode_mem[c];
- for (i=0;i<MAX_PERIOD+LPC_ORDER;i++)
- exc[i-LPC_ORDER] = SROUND16(buf[DECODE_BUFFER_SIZE-MAX_PERIOD-LPC_ORDER+i], SIG_SHIFT);
+ for (i=0;i<MAX_PERIOD+CELT_LPC_ORDER;i++)
+ exc[i-CELT_LPC_ORDER] = SROUND16(buf[DECODE_BUFFER_SIZE-MAX_PERIOD-CELT_LPC_ORDER+i], SIG_SHIFT);
if (loss_duration == 0)
{
- opus_val32 ac[LPC_ORDER+1];
+ opus_val32 ac[CELT_LPC_ORDER+1];
/* Compute LPC coefficients for the last MAX_PERIOD samples before
the first loss so we can work in the excitation-filter domain. */
_celt_autocorr(exc, ac, window, overlap,
- LPC_ORDER, MAX_PERIOD, st->arch);
+ CELT_LPC_ORDER, MAX_PERIOD, st->arch);
/* Add a noise floor of -40 dB. */
#ifdef FIXED_POINT
ac[0] += SHR32(ac[0],13);
@@ -645,7 +753,7 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
ac[0] *= 1.0001f;
#endif
/* Use lag windowing to stabilize the Levinson-Durbin recursion. */
- for (i=1;i<=LPC_ORDER;i++)
+ for (i=1;i<=CELT_LPC_ORDER;i++)
{
/*ac[i] *= exp(-.5*(2*M_PI*.002*i)*(2*M_PI*.002*i));*/
#ifdef FIXED_POINT
@@ -654,7 +762,7 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
ac[i] -= ac[i]*(0.008f*0.008f)*i*i;
#endif
}
- _celt_lpc(lpc+c*LPC_ORDER, ac, LPC_ORDER);
+ _celt_lpc(lpc+c*CELT_LPC_ORDER, ac, CELT_LPC_ORDER);
#ifdef FIXED_POINT
/* For fixed-point, apply bandwidth expansion until we can guarantee that
no overflow can happen in the IIR filter. This means:
@@ -662,13 +770,13 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
while (1) {
opus_val16 tmp=Q15ONE;
opus_val32 sum=QCONST16(1., SIG_SHIFT);
- for (i=0;i<LPC_ORDER;i++)
- sum += ABS16(lpc[c*LPC_ORDER+i]);
+ for (i=0;i<CELT_LPC_ORDER;i++)
+ sum += ABS16(lpc[c*CELT_LPC_ORDER+i]);
if (sum < 65535) break;
- for (i=0;i<LPC_ORDER;i++)
+ for (i=0;i<CELT_LPC_ORDER;i++)
{
tmp = MULT16_16_Q15(QCONST16(.99f,15), tmp);
- lpc[c*LPC_ORDER+i] = MULT16_16_Q15(lpc[c*LPC_ORDER+i], tmp);
+ lpc[c*CELT_LPC_ORDER+i] = MULT16_16_Q15(lpc[c*CELT_LPC_ORDER+i], tmp);
}
}
#endif
@@ -678,8 +786,8 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
{
/* Compute the excitation for exc_length samples before the loss. We need the copy
because celt_fir() cannot filter in-place. */
- celt_fir(exc+MAX_PERIOD-exc_length, lpc+c*LPC_ORDER,
- fir_tmp, exc_length, LPC_ORDER, st->arch);
+ celt_fir(exc+MAX_PERIOD-exc_length, lpc+c*CELT_LPC_ORDER,
+ fir_tmp, exc_length, CELT_LPC_ORDER, st->arch);
OPUS_COPY(exc+MAX_PERIOD-exc_length, fir_tmp, exc_length);
}
@@ -737,15 +845,15 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
S1 += SHR32(MULT16_16(tmp, tmp), 10);
}
{
- opus_val16 lpc_mem[LPC_ORDER];
+ opus_val16 lpc_mem[CELT_LPC_ORDER];
/* Copy the last decoded samples (prior to the overlap region) to
synthesis filter memory so we can have a continuous signal. */
- for (i=0;i<LPC_ORDER;i++)
+ for (i=0;i<CELT_LPC_ORDER;i++)
lpc_mem[i] = SROUND16(buf[DECODE_BUFFER_SIZE-N-1-i], SIG_SHIFT);
/* Apply the synthesis filter to convert the excitation back into
the signal domain. */
- celt_iir(buf+DECODE_BUFFER_SIZE-N, lpc+c*LPC_ORDER,
- buf+DECODE_BUFFER_SIZE-N, extrapolation_len, LPC_ORDER,
+ celt_iir(buf+DECODE_BUFFER_SIZE-N, lpc+c*CELT_LPC_ORDER,
+ buf+DECODE_BUFFER_SIZE-N, extrapolation_len, CELT_LPC_ORDER,
lpc_mem, st->arch);
#ifdef FIXED_POINT
for (i=0; i < extrapolation_len; i++)
@@ -792,23 +900,65 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
}
}
- /* Apply the pre-filter to the MDCT overlap for the next frame because
- the post-filter will be re-applied in the decoder after the MDCT
- overlap. */
- comb_filter(etmp, buf+DECODE_BUFFER_SIZE,
- st->postfilter_period, st->postfilter_period, overlap,
- -st->postfilter_gain, -st->postfilter_gain,
- st->postfilter_tapset, st->postfilter_tapset, NULL, 0, st->arch);
-
- /* Simulate TDAC on the concealed audio so that it blends with the
- MDCT of the next frame. */
- for (i=0;i<overlap/2;i++)
- {
- buf[DECODE_BUFFER_SIZE+i] =
- MULT16_32_Q15(window[i], etmp[overlap-1-i])
- + MULT16_32_Q15(window[overlap-i-1], etmp[i]);
- }
} while (++c<C);
+
+#ifdef ENABLE_DEEP_PLC
+ if (lpcnet->loaded && (st->complexity >= 5 || lpcnet->fec_fill_pos > 0)) {
+ float overlap_mem;
+ int samples_needed16k;
+ celt_sig *buf;
+ VARDECL(float, buf_copy);
+ buf = decode_mem[0];
+ ALLOC(buf_copy, C*overlap, float);
+ c=0; do {
+ OPUS_COPY(buf_copy+c*overlap, &decode_mem[c][DECODE_BUFFER_SIZE-N], overlap);
+ } while (++c<C);
+
+ /* Need enough samples from the PLC to cover the frame size, resampling delay,
+ and the overlap at the end. */
+ samples_needed16k = (N+SINC_ORDER+overlap)/3;
+ if (loss_duration == 0) {
+ st->plc_fill = 0;
+ }
+ while (st->plc_fill < samples_needed16k) {
+ lpcnet_plc_conceal(lpcnet, &st->plc_pcm[st->plc_fill]);
+ st->plc_fill += FRAME_SIZE;
+ }
+ /* Resample to 48 kHz. */
+ for (i=0;i<(N+overlap)/3;i++) {
+ int j;
+ float sum;
+ for (sum=0, j=0;j<17;j++) sum += 3*st->plc_pcm[i+j]*sinc_filter[3*j];
+ buf[DECODE_BUFFER_SIZE-N+3*i] = sum;
+ for (sum=0, j=0;j<16;j++) sum += 3*st->plc_pcm[i+j+1]*sinc_filter[3*j+2];
+ buf[DECODE_BUFFER_SIZE-N+3*i+1] = sum;
+ for (sum=0, j=0;j<16;j++) sum += 3*st->plc_pcm[i+j+1]*sinc_filter[3*j+1];
+ buf[DECODE_BUFFER_SIZE-N+3*i+2] = sum;
+ }
+ OPUS_MOVE(st->plc_pcm, &st->plc_pcm[N/3], st->plc_fill-N/3);
+ st->plc_fill -= N/3;
+ for (i=0;i<N;i++) {
+ float tmp = buf[DECODE_BUFFER_SIZE-N+i];
+ buf[DECODE_BUFFER_SIZE-N+i] -= PREEMPHASIS*st->plc_preemphasis_mem;
+ st->plc_preemphasis_mem = tmp;
+ }
+ overlap_mem = st->plc_preemphasis_mem;
+ for (i=0;i<overlap;i++) {
+ float tmp = buf[DECODE_BUFFER_SIZE+i];
+ buf[DECODE_BUFFER_SIZE+i] -= PREEMPHASIS*overlap_mem;
+ overlap_mem = tmp;
+ }
+ /* For now, we just do mono PLC. */
+ if (C==2) OPUS_COPY(decode_mem[1], decode_mem[0], DECODE_BUFFER_SIZE+overlap);
+ c=0; do {
+ /* Cross-fade with 48-kHz non-neural PLC for the first 2.5 ms to avoid a discontinuity. */
+ if (loss_duration == 0) {
+ for (i=0;i<overlap;i++) decode_mem[c][DECODE_BUFFER_SIZE-N+i] = (1-window[i])*buf_copy[c*overlap+i] + (window[i])*decode_mem[c][DECODE_BUFFER_SIZE-N+i];
+ }
+ } while (++c<C);
+ }
+#endif
+ st->prefilter_and_fold = 1;
}
/* Saturate to soemthing large to avoid wrap-around. */
@@ -817,18 +967,18 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM)
RESTORE_STACK;
}
-int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data,
- int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum)
+int celt_decode_with_ec_dred(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data,
+ int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum
+#ifdef ENABLE_DEEP_PLC
+ ,LPCNetPLCState *lpcnet
+#endif
+ )
{
int c, i, N;
int spread_decision;
opus_int32 bits;
ec_dec _dec;
-#ifdef NORM_ALIASING_HACK
- celt_norm *X;
-#else
VARDECL(celt_norm, X);
-#endif
VARDECL(int, fine_quant);
VARDECL(int, pulses);
VARDECL(int, cap);
@@ -881,7 +1031,7 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat
frame_size *= st->downsample;
lpc = (opus_val16*)(st->_decode_mem+(DECODE_BUFFER_SIZE+overlap)*CC);
- oldBandE = lpc+CC*LPC_ORDER;
+ oldBandE = lpc+CC*CELT_LPC_ORDER;
oldLogE = oldBandE + 2*nbEBands;
oldLogE2 = oldLogE + 2*nbEBands;
backgroundLogE = oldLogE2 + 2*nbEBands;
@@ -935,15 +1085,25 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat
if (data == NULL || len<=1)
{
- celt_decode_lost(st, N, LM);
+ celt_decode_lost(st, N, LM
+#ifdef ENABLE_DEEP_PLC
+ , lpcnet
+#endif
+ );
deemphasis(out_syn, pcm, N, CC, st->downsample, mode->preemph, st->preemph_memD, accum);
RESTORE_STACK;
return frame_size/st->downsample;
}
+#ifdef ENABLE_DEEP_PLC
+ else {
+ /* FIXME: This is a bit of a hack just to make sure opus_decode_native() knows we're no longer in PLC. */
+ if (lpcnet) lpcnet->blend = 0;
+ }
+#endif
/* Check if there are at least two packets received consecutively before
* turning on the pitch-based PLC */
- st->skip_plc = st->loss_duration != 0;
+ if (st->loss_duration == 0) st->skip_plc = 0;
if (dec == NULL)
{
@@ -1006,6 +1166,36 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat
/* Decode the global flags (first symbols in the stream) */
intra_ener = tell+3<=total_bits ? ec_dec_bit_logp(dec, 3) : 0;
+ /* If recovering from packet loss, make sure we make the energy prediction safe to reduce the
+ risk of getting loud artifacts. */
+ if (!intra_ener && st->loss_duration != 0) {
+ c=0; do
+ {
+ opus_val16 safety = 0;
+ int missing = IMIN(10, st->loss_duration>>LM);
+ if (LM==0) safety = QCONST16(1.5f,DB_SHIFT);
+ else if (LM==1) safety = QCONST16(.5f,DB_SHIFT);
+ for (i=start;i<end;i++)
+ {
+ if (oldBandE[c*nbEBands+i] < MAX16(oldLogE[c*nbEBands+i], oldLogE2[c*nbEBands+i])) {
+ /* If energy is going down already, continue the trend. */
+ opus_val32 slope;
+ opus_val32 E0, E1, E2;
+ E0 = oldBandE[c*nbEBands+i];
+ E1 = oldLogE[c*nbEBands+i];
+ E2 = oldLogE2[c*nbEBands+i];
+ slope = MAX32(E1 - E0, HALF32(E2 - E0));
+ E0 -= MAX32(0, (1+missing)*slope);
+ oldBandE[c*nbEBands+i] = MAX32(-QCONST16(20.f,DB_SHIFT), E0);
+ } else {
+ /* Otherwise take the min of the last frames. */
+ oldBandE[c*nbEBands+i] = MIN16(MIN16(oldBandE[c*nbEBands+i], oldLogE[c*nbEBands+i]), oldLogE2[c*nbEBands+i]);
+ }
+ /* Shorter frames have more natural fluctuations -- play it safe. */
+ oldBandE[c*nbEBands+i] -= safety;
+ }
+ } while (++c<2);
+ }
/* Get band energies */
unquant_coarse_energy(mode, start, end, oldBandE,
intra_ener, dec, C, LM);
@@ -1073,19 +1263,13 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat
unquant_fine_energy(mode, start, end, oldBandE, fine_quant, dec, C);
c=0; do {
- OPUS_MOVE(decode_mem[c], decode_mem[c]+N, DECODE_BUFFER_SIZE-N+overlap/2);
+ OPUS_MOVE(decode_mem[c], decode_mem[c]+N, DECODE_BUFFER_SIZE-N+overlap);
} while (++c<CC);
/* Decode fixed codebook */
ALLOC(collapse_masks, C*nbEBands, unsigned char);
-#ifdef NORM_ALIASING_HACK
- /* This is an ugly hack that breaks aliasing rules and would be easily broken,
- but it saves almost 4kB of stack. */
- X = (celt_norm*)(out_syn[CC-1]+overlap/2);
-#else
ALLOC(X, C*N, celt_norm); /**< Interleaved normalised MDCTs */
-#endif
quant_all_bands(0, mode, start, end, X, C==2 ? X+N : NULL, collapse_masks,
NULL, pulses, shortBlocks, spread_decision, dual_stereo, intensity, tf_res,
@@ -1109,7 +1293,9 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat
for (i=0;i<C*nbEBands;i++)
oldBandE[i] = -QCONST16(28.f,DB_SHIFT);
}
-
+ if (st->prefilter_and_fold) {
+ prefilter_and_fold(st, N);
+ }
celt_synthesis(mode, X, out_syn, oldBandE, start, effEnd,
C, CC, isTransient, LM, st->downsample, silence, st->arch);
@@ -1173,6 +1359,7 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat
deemphasis(out_syn, pcm, N, CC, st->downsample, mode->preemph, st->preemph_memD, accum);
st->loss_duration = 0;
+ st->prefilter_and_fold = 0;
RESTORE_STACK;
if (ec_tell(dec) > 8*len)
return OPUS_INTERNAL_ERROR;
@@ -1181,6 +1368,15 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat
return frame_size/st->downsample;
}
+int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data,
+ int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum)
+{
+ return celt_decode_with_ec_dred(st, data, len, pcm, frame_size, dec, accum
+#ifdef ENABLE_DEEP_PLC
+ , NULL
+#endif
+ );
+}
#ifdef CUSTOM_MODES
@@ -1254,6 +1450,26 @@ int opus_custom_decoder_ctl(CELTDecoder * OPUS_RESTRICT st, int request, ...)
va_start(ap, request);
switch (request)
{
+ case OPUS_SET_COMPLEXITY_REQUEST:
+ {
+ opus_int32 value = va_arg(ap, opus_int32);
+ if(value<0 || value>10)
+ {
+ goto bad_arg;
+ }
+ st->complexity = value;
+ }
+ break;
+ case OPUS_GET_COMPLEXITY_REQUEST:
+ {
+ opus_int32 *value = va_arg(ap, opus_int32*);
+ if (!value)
+ {
+ goto bad_arg;
+ }
+ *value = st->complexity;
+ }
+ break;
case CELT_SET_START_BAND_REQUEST:
{
opus_int32 value = va_arg(ap, opus_int32);
@@ -1300,7 +1516,7 @@ int opus_custom_decoder_ctl(CELTDecoder * OPUS_RESTRICT st, int request, ...)
int i;
opus_val16 *lpc, *oldBandE, *oldLogE, *oldLogE2;
lpc = (opus_val16*)(st->_decode_mem+(DECODE_BUFFER_SIZE+st->overlap)*st->channels);
- oldBandE = lpc+st->channels*LPC_ORDER;
+ oldBandE = lpc+st->channels*CELT_LPC_ORDER;
oldLogE = oldBandE + 2*st->mode->nbEBands;
oldLogE2 = oldLogE + 2*st->mode->nbEBands;
OPUS_CLEAR((char*)&st->DECODER_RESET_START,
diff --git a/celt/celt_encoder.c b/celt/celt_encoder.c
index 637d442c..7f32a801 100644
--- a/celt/celt_encoder.c
+++ b/celt/celt_encoder.c
@@ -281,6 +281,9 @@ static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int
/* High-pass filter: (1 - 2*z^-1 + z^-2) / (1 - z^-1 + .5*z^-2) */
for (i=0;i<len;i++)
{
+#ifndef FIXED_POINT
+ float mem00;
+#endif
opus_val32 x,y;
x = SHR32(in[i+c*len],SIG_SHIFT);
y = ADD32(mem0, x);
@@ -288,8 +291,13 @@ static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int
mem0 = mem1 + y - SHL32(x,1);
mem1 = x - SHR32(y,1);
#else
+ /* Original code:
mem0 = mem1 + y - 2*x;
mem1 = x - .5f*y;
+ Modified code to shorten dependency chains: */
+ mem00=mem0;
+ mem0 = mem0 - x + .5f*mem1;
+ mem1 = x - mem00;
#endif
tmp[i] = SROUND16(y, 2);
/*printf("%f ", tmp[i]);*/
@@ -322,10 +330,11 @@ static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int
#ifdef FIXED_POINT
/* FIXME: Use PSHR16() instead */
tmp[i] = mem0 + PSHR32(x2-mem0,forward_shift);
+ mem0 = tmp[i];
#else
- tmp[i] = mem0 + MULT16_16_P15(forward_decay,x2-mem0);
+ mem0 = x2 + (1.f-forward_decay)*mem0;
+ tmp[i] = forward_decay*mem0;
#endif
- mem0 = tmp[i];
}
mem0=0;
@@ -337,11 +346,13 @@ static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int
#ifdef FIXED_POINT
/* FIXME: Use PSHR16() instead */
tmp[i] = mem0 + PSHR32(tmp[i]-mem0,3);
-#else
- tmp[i] = mem0 + MULT16_16_P15(QCONST16(0.125f,15),tmp[i]-mem0);
-#endif
mem0 = tmp[i];
maxE = MAX16(maxE, mem0);
+#else
+ mem0 = tmp[i] + 0.875f*mem0;
+ tmp[i] = 0.125f*mem0;
+ maxE = MAX16(maxE, 0.125f*mem0);
+#endif
}
/*for (i=0;i<len2;i++)printf("%f ", tmp[i]/mean);printf("\n");*/
@@ -967,7 +978,7 @@ static opus_val16 median_of_3(const opus_val16 *x)
return t0;
}
-static opus_val16 dynalloc_analysis(const opus_val16 *bandLogE, const opus_val16 *bandLogE2,
+static opus_val16 dynalloc_analysis(const opus_val16 *bandLogE, const opus_val16 *bandLogE2, const opus_val16 *oldBandE,
int nbEBands, int start, int end, int C, int *offsets, int lsb_depth, const opus_int16 *logN,
int isTransient, int vbr, int constrained_vbr, const opus_int16 *eBands, int LM,
int effectiveBytes, opus_int32 *tot_boost_, int lfe, opus_val16 *surround_dynalloc,
@@ -978,9 +989,11 @@ static opus_val16 dynalloc_analysis(const opus_val16 *bandLogE, const opus_val16
opus_val16 maxDepth;
VARDECL(opus_val16, follower);
VARDECL(opus_val16, noise_floor);
+ VARDECL(opus_val16, bandLogE3);
SAVE_STACK;
ALLOC(follower, C*nbEBands, opus_val16);
ALLOC(noise_floor, C*nbEBands, opus_val16);
+ ALLOC(bandLogE3, nbEBands, opus_val16);
OPUS_CLEAR(offsets, nbEBands);
/* Dynamic allocation code */
maxDepth=-QCONST16(31.9f, DB_SHIFT);
@@ -1033,8 +1046,10 @@ static opus_val16 dynalloc_analysis(const opus_val16 *bandLogE, const opus_val16
printf("%d ", spread_weight[i]);
printf("\n");*/
}
- /* Make sure that dynamic allocation can't make us bust the budget */
- if (effectiveBytes > 50 && LM>=1 && !lfe)
+ /* Make sure that dynamic allocation can't make us bust the budget.
+ We enable the feature starting at 24 kb/s for 20-ms frames
+ and 96 kb/s for 2.5 ms frames. */
+ if (effectiveBytes >= (30 + 5*LM) && !lfe)
{
int last=0;
c=0;do
@@ -1042,30 +1057,38 @@ static opus_val16 dynalloc_analysis(const opus_val16 *bandLogE, const opus_val16
opus_val16 offset;
opus_val16 tmp;
opus_val16 *f;
+ OPUS_COPY(bandLogE3, &bandLogE2[c*nbEBands], end);
+ if (LM==0) {
+ /* For 2.5 ms frames, the first 8 bands have just one bin, so the
+ energy is highly unreliable (high variance). For that reason,
+ we take the max with the previous energy so that at least 2 bins
+ are getting used. */
+ for (i=0;i<IMIN(8,end);i++) bandLogE3[i] = MAX16(bandLogE2[c*nbEBands+i], oldBandE[c*nbEBands+i]);
+ }
f = &follower[c*nbEBands];
- f[0] = bandLogE2[c*nbEBands];
+ f[0] = bandLogE3[0];
for (i=1;i<end;i++)
{
/* The last band to be at least 3 dB higher than the previous one
is the last we'll consider. Otherwise, we run into problems on
bandlimited signals. */
- if (bandLogE2[c*nbEBands+i] > bandLogE2[c*nbEBands+i-1]+QCONST16(.5f,DB_SHIFT))
+ if (bandLogE3[i] > bandLogE3[i-1]+QCONST16(.5f,DB_SHIFT))
last=i;
- f[i] = MIN16(f[i-1]+QCONST16(1.5f,DB_SHIFT), bandLogE2[c*nbEBands+i]);
+ f[i] = MIN16(f[i-1]+QCONST16(1.5f,DB_SHIFT), bandLogE3[i]);
}
for (i=last-1;i>=0;i--)
- f[i] = MIN16(f[i], MIN16(f[i+1]+QCONST16(2.f,DB_SHIFT), bandLogE2[c*nbEBands+i]));
+ f[i] = MIN16(f[i], MIN16(f[i+1]+QCONST16(2.f,DB_SHIFT), bandLogE3[i]));
/* Combine with a median filter to avoid dynalloc triggering unnecessarily.
The "offset" value controls how conservative we are -- a higher offset
reduces the impact of the median filter and makes dynalloc use more bits. */
offset = QCONST16(1.f, DB_SHIFT);
for (i=2;i<end-2;i++)
- f[i] = MAX16(f[i], median_of_5(&bandLogE2[c*nbEBands+i-2])-offset);
- tmp = median_of_3(&bandLogE2[c*nbEBands])-offset;
+ f[i] = MAX16(f[i], median_of_5(&bandLogE3[i-2])-offset);
+ tmp = median_of_3(&bandLogE3[0])-offset;
f[0] = MAX16(f[0], tmp);
f[1] = MAX16(f[1], tmp);
- tmp = median_of_3(&bandLogE2[c*nbEBands+end-3])-offset;
+ tmp = median_of_3(&bandLogE3[end-3])-offset;
f[end-2] = MAX16(f[end-2], tmp);
f[end-1] = MAX16(f[end-1], tmp);
@@ -1565,10 +1588,13 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm,
vbr_rate = 0;
tmp = st->bitrate*frame_size;
if (tell>1)
- tmp += tell;
+ tmp += tell*mode->Fs;
if (st->bitrate!=OPUS_BITRATE_MAX)
+ {
nbCompressedBytes = IMAX(2, IMIN(nbCompressedBytes,
(tmp+4*mode->Fs)/(8*mode->Fs)-!!st->signalling));
+ ec_enc_shrink(enc, nbCompressedBytes);
+ }
effectiveBytes = nbCompressedBytes - nbFilledBytes;
}
equiv_rate = ((opus_int32)nbCompressedBytes*8*50 << (3-LM)) - (40*C+20)*((400>>LM) - 50);
@@ -1882,7 +1908,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm,
ALLOC(importance, nbEBands, int);
ALLOC(spread_weight, nbEBands, int);
- maxDepth = dynalloc_analysis(bandLogE, bandLogE2, nbEBands, start, end, C, offsets,
+ maxDepth = dynalloc_analysis(bandLogE, bandLogE2, oldBandE, nbEBands, start, end, C, offsets,
st->lsb_depth, mode->logN, isTransient, st->vbr, st->constrained_vbr,
eBands, LM, effectiveBytes, &tot_boost, st->lfe, surround_dynalloc, &st->analysis, importance, spread_weight);
@@ -2246,7 +2272,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm,
if (anti_collapse_on)
{
anti_collapse(mode, X, collapse_masks, LM, C, N,
- start, end, oldBandE, oldLogE, oldLogE2, pulses, st->rng);
+ start, end, oldBandE, oldLogE, oldLogE2, pulses, st->rng, st->arch);
}
c=0; do {
@@ -2265,15 +2291,15 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm,
st->prefilter_period_old=IMAX(st->prefilter_period_old, COMBFILTER_MINPERIOD);
comb_filter(out_mem[c], out_mem[c], st->prefilter_period_old, st->prefilter_period, mode->shortMdctSize,
st->prefilter_gain_old, st->prefilter_gain, st->prefilter_tapset_old, st->prefilter_tapset,
- mode->window, overlap);
+ mode->window, overlap, st->arch);
if (LM!=0)
comb_filter(out_mem[c]+mode->shortMdctSize, out_mem[c]+mode->shortMdctSize, st->prefilter_period, pitch_index, N-mode->shortMdctSize,
st->prefilter_gain, gain1, st->prefilter_tapset, prefilter_tapset,
- mode->window, overlap);
+ mode->window, overlap, st->arch);
} while (++c<CC);
/* We reuse freq[] as scratch space for the de-emphasis */
- deemphasis(out_mem, (opus_val16*)pcm, N, CC, st->upsample, mode->preemph, st->preemph_memD);
+ deemphasis(out_mem, (opus_val16*)pcm, N, CC, st->upsample, mode->preemph, st->preemph_memD, 0);
st->prefilter_period_old = st->prefilter_period;
st->prefilter_gain_old = st->prefilter_gain;
st->prefilter_tapset_old = st->prefilter_tapset;
diff --git a/celt/celt_lpc.c b/celt/celt_lpc.c
index f91721bc..fabca65c 100644
--- a/celt/celt_lpc.c
+++ b/celt/celt_lpc.c
@@ -44,7 +44,7 @@ int p
opus_val32 r;
opus_val32 error = ac[0];
#ifdef FIXED_POINT
- opus_val32 lpc[LPC_ORDER];
+ opus_val32 lpc[CELT_LPC_ORDER];
#else
float *lpc = _lpc;
#endif
@@ -158,7 +158,17 @@ void celt_fir_c(
sum[1] = SHL32(EXTEND32(x[i+1]), SIG_SHIFT);
sum[2] = SHL32(EXTEND32(x[i+2]), SIG_SHIFT);
sum[3] = SHL32(EXTEND32(x[i+3]), SIG_SHIFT);
- xcorr_kernel(rnum, x+i-ord, sum, ord, arch);
+#if defined(OPUS_CHECK_ASM) && defined(FIXED_POINT)
+ {
+ opus_val32 sum_c[4];
+ memcpy(sum_c, sum, sizeof(sum_c));
+ xcorr_kernel_c(rnum, x+i-ord, sum_c, ord);
+#endif
+ xcorr_kernel(rnum, x+i-ord, sum, ord, arch);
+#if defined(OPUS_CHECK_ASM) && defined(FIXED_POINT)
+ celt_assert(memcmp(sum, sum_c, sizeof(sum)) == 0);
+ }
+#endif
y[i ] = SROUND16(sum[0], SIG_SHIFT);
y[i+1] = SROUND16(sum[1], SIG_SHIFT);
y[i+2] = SROUND16(sum[2], SIG_SHIFT);
@@ -222,8 +232,17 @@ void celt_iir(const opus_val32 *_x,
sum[1]=_x[i+1];
sum[2]=_x[i+2];
sum[3]=_x[i+3];
- xcorr_kernel(rden, y+i, sum, ord, arch);
-
+#if defined(OPUS_CHECK_ASM) && defined(FIXED_POINT)
+ {
+ opus_val32 sum_c[4];
+ memcpy(sum_c, sum, sizeof(sum_c));
+ xcorr_kernel_c(rden, y+i, sum_c, ord);
+#endif
+ xcorr_kernel(rden, y+i, sum, ord, arch);
+#if defined(OPUS_CHECK_ASM) && defined(FIXED_POINT)
+ celt_assert(memcmp(sum, sum_c, sizeof(sum)) == 0);
+ }
+#endif
/* Patch up the result to compensate for the fact that this is an IIR */
y[i+ord ] = -SROUND16(sum[0],SIG_SHIFT);
_y[i ] = sum[0];
diff --git a/celt/celt_lpc.h b/celt/celt_lpc.h
index a4c5fd6e..97dee82f 100644
--- a/celt/celt_lpc.h
+++ b/celt/celt_lpc.h
@@ -35,7 +35,7 @@
#include "x86/celt_lpc_sse.h"
#endif
-#define LPC_ORDER 24
+#define CELT_LPC_ORDER 24
void _celt_lpc(opus_val16 *_lpc, const opus_val32 *ac, int p);
diff --git a/celt/cpu_support.h b/celt/cpu_support.h
index 7b5c56ca..9f13d8ae 100644
--- a/celt/cpu_support.h
+++ b/celt/cpu_support.h
@@ -35,19 +35,20 @@
(defined(OPUS_ARM_ASM) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR))
#include "arm/armcpu.h"
-/* We currently support 4 ARM variants:
+/* We currently support 5 ARM variants:
* arch[0] -> ARMv4
* arch[1] -> ARMv5E
* arch[2] -> ARMv6
* arch[3] -> NEON
+ * arch[4] -> NEON+DOTPROD
*/
-#define OPUS_ARCHMASK 3
+#define OPUS_ARCHMASK 7
#elif defined(OPUS_HAVE_RTCD) && \
((defined(OPUS_X86_MAY_HAVE_SSE) && !defined(OPUS_X86_PRESUME_SSE)) || \
(defined(OPUS_X86_MAY_HAVE_SSE2) && !defined(OPUS_X86_PRESUME_SSE2)) || \
(defined(OPUS_X86_MAY_HAVE_SSE4_1) && !defined(OPUS_X86_PRESUME_SSE4_1)) || \
- (defined(OPUS_X86_MAY_HAVE_AVX) && !defined(OPUS_X86_PRESUME_AVX)))
+ (defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(OPUS_X86_PRESUME_AVX2)))
#include "x86/x86cpu.h"
/* We currently support 5 x86 variants:
diff --git a/celt/dump_modes/Makefile b/celt/dump_modes/Makefile
index 93f599fb..9bd13b07 100644
--- a/celt/dump_modes/Makefile
+++ b/celt/dump_modes/Makefile
@@ -11,6 +11,7 @@ SOURCES = dump_modes.c \
../entdec.c \
../mathops.c \
../mdct.c \
+ ../celt.c \
../kiss_fft.c
ifdef HAVE_ARM_NE10
diff --git a/celt/entdec.c b/celt/entdec.c
index 0b3433ed..027aa24b 100644
--- a/celt/entdec.c
+++ b/celt/entdec.c
@@ -195,6 +195,27 @@ int ec_dec_icdf(ec_dec *_this,const unsigned char *_icdf,unsigned _ftb){
return ret;
}
+int ec_dec_icdf16(ec_dec *_this,const opus_uint16 *_icdf,unsigned _ftb){
+ opus_uint32 r;
+ opus_uint32 d;
+ opus_uint32 s;
+ opus_uint32 t;
+ int ret;
+ s=_this->rng;
+ d=_this->val;
+ r=s>>_ftb;
+ ret=-1;
+ do{
+ t=s;
+ s=IMUL32(r,_icdf[++ret]);
+ }
+ while(d<s);
+ _this->val=d-s;
+ _this->rng=t-s;
+ ec_dec_normalize(_this);
+ return ret;
+}
+
opus_uint32 ec_dec_uint(ec_dec *_this,opus_uint32 _ft){
unsigned ft;
unsigned s;
diff --git a/celt/entdec.h b/celt/entdec.h
index 025fc187..c81f26fd 100644
--- a/celt/entdec.h
+++ b/celt/entdec.h
@@ -81,6 +81,16 @@ int ec_dec_bit_logp(ec_dec *_this,unsigned _logp);
Return: The decoded symbol s.*/
int ec_dec_icdf(ec_dec *_this,const unsigned char *_icdf,unsigned _ftb);
+/*Decodes a symbol given an "inverse" CDF table.
+ No call to ec_dec_update() is necessary after this call.
+ _icdf: The "inverse" CDF, such that symbol s falls in the range
+ [s>0?ft-_icdf[s-1]:0,ft-_icdf[s]), where ft=1<<_ftb.
+ The values must be monotonically non-increasing, and the last value
+ must be 0.
+ _ftb: The number of bits of precision in the cumulative distribution.
+ Return: The decoded symbol s.*/
+int ec_dec_icdf16(ec_dec *_this,const opus_uint16 *_icdf,unsigned _ftb);
+
/*Extracts a raw unsigned integer with a non-power-of-2 range from the stream.
The bits must have been encoded with ec_enc_uint().
No call to ec_dec_update() is necessary after this call.
diff --git a/celt/entenc.c b/celt/entenc.c
index f1750d25..69c6f835 100644
--- a/celt/entenc.c
+++ b/celt/entenc.c
@@ -172,6 +172,17 @@ void ec_enc_icdf(ec_enc *_this,int _s,const unsigned char *_icdf,unsigned _ftb){
ec_enc_normalize(_this);
}
+void ec_enc_icdf16(ec_enc *_this,int _s,const opus_uint16 *_icdf,unsigned _ftb){
+ opus_uint32 r;
+ r=_this->rng>>_ftb;
+ if(_s>0){
+ _this->val+=_this->rng-IMUL32(r,_icdf[_s-1]);
+ _this->rng=IMUL32(r,_icdf[_s-1]-_icdf[_s]);
+ }
+ else _this->rng-=IMUL32(r,_icdf[_s]);
+ ec_enc_normalize(_this);
+}
+
void ec_enc_uint(ec_enc *_this,opus_uint32 _fl,opus_uint32 _ft){
unsigned ft;
unsigned fl;
diff --git a/celt/entenc.h b/celt/entenc.h
index f502eaf6..010874bb 100644
--- a/celt/entenc.h
+++ b/celt/entenc.h
@@ -64,6 +64,15 @@ void ec_enc_bit_logp(ec_enc *_this,int _val,unsigned _logp);
_ftb: The number of bits of precision in the cumulative distribution.*/
void ec_enc_icdf(ec_enc *_this,int _s,const unsigned char *_icdf,unsigned _ftb);
+/*Encodes a symbol given an "inverse" CDF table.
+ _s: The index of the symbol to encode.
+ _icdf: The "inverse" CDF, such that symbol _s falls in the range
+ [_s>0?ft-_icdf[_s-1]:0,ft-_icdf[_s]), where ft=1<<_ftb.
+ The values must be monotonically non-increasing, and the last value
+ must be 0.
+ _ftb: The number of bits of precision in the cumulative distribution.*/
+void ec_enc_icdf16(ec_enc *_this,int _s,const opus_uint16 *_icdf,unsigned _ftb);
+
/*Encodes a raw unsigned integer in the stream.
_fl: The integer to encode.
_ft: The number of integers that can be encoded (one more than the max).
diff --git a/celt/laplace.c b/celt/laplace.c
index a7bca874..21809666 100644
--- a/celt/laplace.c
+++ b/celt/laplace.c
@@ -132,3 +132,104 @@ int ec_laplace_decode(ec_dec *dec, unsigned fs, int decay)
ec_dec_update(dec, fl, IMIN(fl+fs,32768), 32768);
return val;
}
+
+void ec_laplace_encode_p0(ec_enc *enc, int value, opus_uint16 p0, opus_uint16 decay)
+{
+ int s;
+ opus_uint16 sign_icdf[3];
+ sign_icdf[0] = 32768-p0;
+ sign_icdf[1] = sign_icdf[0]/2;
+ sign_icdf[2] = 0;
+ s = value == 0 ? 0 : (value > 0 ? 1 : 2);
+ ec_enc_icdf16(enc, s, sign_icdf, 15);
+ value = abs(value);
+ if (value)
+ {
+ int i;
+ opus_uint16 icdf[8];
+ icdf[0] = IMAX(7, decay);
+ for (i=1;i<7;i++)
+ {
+ icdf[i] = IMAX(7-i, (icdf[i-1] * (opus_int32)decay) >> 15);
+ }
+ icdf[7] = 0;
+ value--;
+ do {
+ ec_enc_icdf16(enc, IMIN(value, 7), icdf, 15);
+ value -= 7;
+ } while (value >= 0);
+ }
+}
+
+int ec_laplace_decode_p0(ec_dec *dec, opus_uint16 p0, opus_uint16 decay)
+{
+ int s;
+ int value;
+ opus_uint16 sign_icdf[3];
+ sign_icdf[0] = 32768-p0;
+ sign_icdf[1] = sign_icdf[0]/2;
+ sign_icdf[2] = 0;
+ s = ec_dec_icdf16(dec, sign_icdf, 15);
+ if (s==2) s = -1;
+ if (s != 0)
+ {
+ int i;
+ int v;
+ opus_uint16 icdf[8];
+ icdf[0] = IMAX(7, decay);
+ for (i=1;i<7;i++)
+ {
+ icdf[i] = IMAX(7-i, (icdf[i-1] * (opus_int32)decay) >> 15);
+ }
+ icdf[7] = 0;
+ value = 1;
+ do {
+ v = ec_dec_icdf16(dec, icdf, 15);
+ value += v;
+ } while (v == 7);
+ return s*value;
+ } else return 0;
+}
+
+#if 0
+
+#include <stdio.h>
+#define NB_VALS 10
+#define DATA_SIZE 10000
+int main() {
+ ec_enc enc;
+ ec_dec dec;
+ unsigned char *ptr;
+ int i;
+ int decay, p0;
+ int val[NB_VALS] = {6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+ /*for (i=0;i<NB_VALS;i++) {
+ val[i] = -log(rand()/(float)RAND_MAX);
+ if (rand()%2) val[i] = -val[i];
+ }*/
+ p0 = 16000;
+ decay = 16000;
+ ptr = (unsigned char *)malloc(DATA_SIZE);
+ ec_enc_init(&enc,ptr,DATA_SIZE);
+ for (i=0;i<NB_VALS;i++) {
+ printf("%d ", val[i]);
+ }
+ printf("\n");
+ for (i=0;i<NB_VALS;i++) {
+ ec_laplace_encode_p0(&enc, val[i], p0, decay);
+ }
+
+ ec_enc_done(&enc);
+
+ ec_dec_init(&dec,ec_get_buffer(&enc),ec_range_bytes(&enc));
+
+ for (i=0;i<NB_VALS;i++) {
+ val[i] = ec_laplace_decode_p0(&dec, p0, decay);
+ }
+ for (i=0;i<NB_VALS;i++) {
+ printf("%d ", val[i]);
+ }
+ printf("\n");
+}
+
+#endif
diff --git a/celt/laplace.h b/celt/laplace.h
index 46c14b5d..8010ad97 100644
--- a/celt/laplace.h
+++ b/celt/laplace.h
@@ -26,6 +26,9 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#ifndef LAPLACE_H
+#define LAPLACE_H
+
#include "entenc.h"
#include "entdec.h"
@@ -46,3 +49,9 @@ void ec_laplace_encode(ec_enc *enc, int *value, unsigned fs, int decay);
@return Value decoded
*/
int ec_laplace_decode(ec_dec *dec, unsigned fs, int decay);
+
+
+int ec_laplace_decode_p0(ec_dec *dec, opus_uint16 p0, opus_uint16 decay);
+void ec_laplace_encode_p0(ec_enc *enc, int value, opus_uint16 p0, opus_uint16 decay);
+
+#endif
diff --git a/celt/mathops.h b/celt/mathops.h
index 478ac918..e2eece29 100644
--- a/celt/mathops.h
+++ b/celt/mathops.h
@@ -230,6 +230,12 @@ static OPUS_INLINE opus_val32 celt_exp2_frac(opus_val16 x)
frac = SHL16(x, 4);
return ADD16(D0, MULT16_16_Q15(frac, ADD16(D1, MULT16_16_Q15(frac, ADD16(D2 , MULT16_16_Q15(D3,frac))))));
}
+
+#undef D0
+#undef D1
+#undef D2
+#undef D3
+
/** Base-2 exponential approximation (2^x). (Q10 input, Q16 output) */
static OPUS_INLINE opus_val32 celt_exp2(opus_val16 x)
{
diff --git a/celt/meson.build b/celt/meson.build
index ad95d949..7852fc15 100644
--- a/celt/meson.build
+++ b/celt/meson.build
@@ -6,6 +6,8 @@ celt_sse2_sources = sources['CELT_SOURCES_SSE2']
celt_sse4_1_sources = sources['CELT_SOURCES_SSE4_1']
+celt_avx2_sources = sources['CELT_SOURCES_AVX2']
+
celt_neon_intr_sources = sources['CELT_SOURCES_ARM_NEON_INTR']
celt_static_libs = []
@@ -14,7 +16,7 @@ if host_cpu_family in ['x86', 'x86_64'] and opus_conf.has('OPUS_HAVE_RTCD')
celt_sources += sources['CELT_SOURCES_X86_RTCD']
endif
-foreach intr_name : ['sse', 'sse2', 'sse4_1', 'neon_intr']
+foreach intr_name : ['sse', 'sse2', 'sse4_1', 'avx2', 'neon_intr']
have_intr = get_variable('have_' + intr_name)
if not have_intr
continue
diff --git a/celt/mips/celt_mipsr1.h b/celt/mips/celt_mipsr1.h
index c332fe04..d1b25c20 100644
--- a/celt/mips/celt_mipsr1.h
+++ b/celt/mips/celt_mipsr1.h
@@ -27,8 +27,8 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef __CELT_MIPSR1_H__
-#define __CELT_MIPSR1_H__
+#ifndef CELT_MIPSR1_H__
+#define CELT_MIPSR1_H__
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -149,4 +149,4 @@ void comb_filter(opus_val32 *y, opus_val32 *x, int T0, int T1, int N,
}
}
-#endif /* __CELT_MIPSR1_H__ */
+#endif /* CELT_MIPSR1_H__ */
diff --git a/celt/mips/mdct_mipsr1.h b/celt/mips/mdct_mipsr1.h
index 2934dab7..7456c181 100644
--- a/celt/mips/mdct_mipsr1.h
+++ b/celt/mips/mdct_mipsr1.h
@@ -38,8 +38,8 @@
MDCT implementation in FFMPEG, but has differences in signs, ordering
and scaling in many places.
*/
-#ifndef __MDCT_MIPSR1_H__
-#define __MDCT_MIPSR1_H__
+#ifndef MDCT_MIPSR1_H__
+#define MDCT_MIPSR1_H__
#ifndef SKIP_CONFIG_H
#ifdef HAVE_CONFIG_H
@@ -285,4 +285,4 @@ void clt_mdct_backward(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scala
}
}
}
-#endif /* __MDCT_MIPSR1_H__ */
+#endif /* MDCT_MIPSR1_H__ */
diff --git a/celt/mips/vq_mipsr1.h b/celt/mips/vq_mipsr1.h
index f26a33e7..1621c562 100644
--- a/celt/mips/vq_mipsr1.h
+++ b/celt/mips/vq_mipsr1.h
@@ -26,8 +26,8 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef __VQ_MIPSR1_H__
-#define __VQ_MIPSR1_H__
+#ifndef VQ_MIPSR1_H__
+#define VQ_MIPSR1_H__
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -113,4 +113,4 @@ void renormalise_vector(celt_norm *X, int N, opus_val16 gain, int arch)
/*return celt_sqrt(E);*/
}
-#endif /* __VQ_MIPSR1_H__ */
+#endif /* VQ_MIPSR1_H__ */
diff --git a/celt/os_support.h b/celt/os_support.h
index 009bf861..7d2d3781 100644
--- a/celt/os_support.h
+++ b/celt/os_support.h
@@ -41,7 +41,7 @@
#include <string.h>
#include <stdlib.h>
-/** Opus wrapper for malloc(). To do your own dynamic allocation, all you need to do is replace this function and opus_free */
+/** Opus wrapper for malloc(). To do your own dynamic allocation replace this function, opus_realloc, and opus_free */
#ifndef OVERRIDE_OPUS_ALLOC
static OPUS_INLINE void *opus_alloc (size_t size)
{
@@ -49,7 +49,15 @@ static OPUS_INLINE void *opus_alloc (size_t size)
}
#endif
-/** Same as celt_alloc(), except that the area is only needed inside a CELT call (might cause problem with wideband though) */
+#ifndef OVERRIDE_OPUS_REALLOC
+static OPUS_INLINE void *opus_realloc (void *ptr, size_t size)
+{
+ return realloc(ptr, size);
+}
+#endif
+
+/** Used only for non-threadsafe pseudostack.
+ If desired, this can always return the same area of memory rather than allocating a new one every time. */
#ifndef OVERRIDE_OPUS_ALLOC_SCRATCH
static OPUS_INLINE void *opus_alloc_scratch (size_t size)
{
@@ -58,7 +66,7 @@ static OPUS_INLINE void *opus_alloc_scratch (size_t size)
}
#endif
-/** Opus wrapper for free(). To do your own dynamic allocation, all you need to do is replace this function and opus_alloc */
+/** Opus wrapper for free(). To do your own dynamic allocation replace this function, opus_realloc, and opus_free */
#ifndef OVERRIDE_OPUS_FREE
static OPUS_INLINE void opus_free (void *ptr)
{
diff --git a/celt/pitch.c b/celt/pitch.c
index 7998db41..e33c60a3 100644
--- a/celt/pitch.c
+++ b/celt/pitch.c
@@ -262,7 +262,16 @@ celt_pitch_xcorr_c(const opus_val16 *_x, const opus_val16 *_y,
for (i=0;i<max_pitch-3;i+=4)
{
opus_val32 sum[4]={0,0,0,0};
- xcorr_kernel(_x, _y+i, sum, len, arch);
+#if defined(OPUS_CHECK_ASM) && defined(FIXED_POINT)
+ {
+ opus_val32 sum_c[4]={0,0,0,0};
+ xcorr_kernel_c(_x, _y+i, sum_c, len);
+#endif
+ xcorr_kernel(_x, _y+i, sum, len, arch);
+#if defined(OPUS_CHECK_ASM) && defined(FIXED_POINT)
+ celt_assert(memcmp(sum, sum_c, sizeof(sum)) == 0);
+ }
+#endif
xcorr[i]=sum[0];
xcorr[i+1]=sum[1];
xcorr[i+2]=sum[2];
diff --git a/celt/pitch.h b/celt/pitch.h
index e425f56a..dd0e2beb 100644
--- a/celt/pitch.h
+++ b/celt/pitch.h
@@ -189,4 +189,15 @@ celt_pitch_xcorr_c(const opus_val16 *_x, const opus_val16 *_y,
# define celt_pitch_xcorr celt_pitch_xcorr_c
#endif
+#ifdef NON_STATIC_COMB_FILTER_CONST_C
+void comb_filter_const_c(opus_val32 *y, opus_val32 *x, int T, int N,
+ opus_val16 g10, opus_val16 g11, opus_val16 g12);
+#endif
+
+#ifndef OVERRIDE_COMB_FILTER_CONST
+# define comb_filter_const(y, x, T, N, g10, g11, g12, arch) \
+ ((void)(arch),comb_filter_const_c(y, x, T, N, g10, g11, g12))
+#endif
+
+
#endif
diff --git a/celt/stack_alloc.h b/celt/stack_alloc.h
index b289facd..b9ac0abf 100644
--- a/celt/stack_alloc.h
+++ b/celt/stack_alloc.h
@@ -155,7 +155,7 @@ extern char *global_stack_top;
#else
#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1))
-#define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)/sizeof(char)),(stack)+=(size)*(sizeof(type)/sizeof(char)),(type*)((stack)-(size)*(sizeof(type)/sizeof(char))))
+#define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)/(sizeof(char))),(stack)+=(size)*(sizeof(type)/(sizeof(char))),(type*)((stack)-(size)*(sizeof(type)/(sizeof(char)))))
#if 0 /* Set this to 1 to instrument pseudostack usage */
#define RESTORE_STACK (printf("%ld %s:%d\n", global_stack-scratch_ptr, __FILE__, __LINE__),global_stack = _saved_stack)
#else
diff --git a/celt/tests/test_unit_cwrs32.c b/celt/tests/test_unit_cwrs32.c
index 36dd8af5..f6b8ac4b 100644
--- a/celt/tests/test_unit_cwrs32.c
+++ b/celt/tests/test_unit_cwrs32.c
@@ -157,5 +157,6 @@ int main(void){
/*printf("\n");*/
}
}
+ RESTORE_STACK;
return 0;
}
diff --git a/celt/tests/test_unit_dft.c b/celt/tests/test_unit_dft.c
index ae9a7b56..ad6c60a0 100644
--- a/celt/tests/test_unit_dft.c
+++ b/celt/tests/test_unit_dft.c
@@ -176,5 +176,6 @@ int main(int argc,char ** argv)
test1d(480,1,arch);
#endif
}
+ RESTORE_STACK;
return ret;
}
diff --git a/celt/tests/test_unit_laplace.c b/celt/tests/test_unit_laplace.c
index 727bf012..9832e5e5 100644
--- a/celt/tests/test_unit_laplace.c
+++ b/celt/tests/test_unit_laplace.c
@@ -89,5 +89,6 @@ int main(void)
}
free(ptr);
+ RESTORE_STACK;
return ret;
}
diff --git a/celt/tests/test_unit_mdct.c b/celt/tests/test_unit_mdct.c
index 844c5b48..70dc042e 100644
--- a/celt/tests/test_unit_mdct.c
+++ b/celt/tests/test_unit_mdct.c
@@ -224,5 +224,6 @@ int main(int argc,char ** argv)
test1d(1920,1,arch);
#endif
}
+ RESTORE_STACK;
return ret;
}
diff --git a/celt/tests/test_unit_rotation.c b/celt/tests/test_unit_rotation.c
index 8a31b3f2..6d05499b 100644
--- a/celt/tests/test_unit_rotation.c
+++ b/celt/tests/test_unit_rotation.c
@@ -82,5 +82,6 @@ int main(void)
test_rotation(23, 5);
test_rotation(50, 3);
test_rotation(80, 1);
+ RESTORE_STACK;
return ret;
}
diff --git a/celt/x86/celt_lpc_sse4_1.c b/celt/x86/celt_lpc_sse4_1.c
index 54785688..daf59d24 100644
--- a/celt/x86/celt_lpc_sse4_1.c
+++ b/celt/x86/celt_lpc_sse4_1.c
@@ -64,9 +64,16 @@ void celt_fir_sse4_1(const opus_val16 *x,
{
opus_val32 sums[4] = {0};
__m128i vecSum, vecX;
-
- xcorr_kernel(rnum, x+i-ord, sums, ord, arch);
-
+#if defined(OPUS_CHECK_ASM)
+ {
+ opus_val32 sums_c[4] = {0};
+ xcorr_kernel_c(rnum, x+i-ord, sums_c, ord);
+#endif
+ xcorr_kernel(rnum, x+i-ord, sums, ord, arch);
+#if defined(OPUS_CHECK_ASM)
+ celt_assert(memcmp(sums, sums_c, sizeof(sums)) == 0);
+ }
+#endif
vecSum = _mm_loadu_si128((__m128i *)sums);
vecSum = _mm_add_epi32(vecSum, vecNoA);
vecSum = _mm_srai_epi32(vecSum, SIG_SHIFT);
diff --git a/celt/x86/pitch_avx.c b/celt/x86/pitch_avx.c
new file mode 100644
index 00000000..f731762d
--- /dev/null
+++ b/celt/x86/pitch_avx.c
@@ -0,0 +1,101 @@
+/* Copyright (c) 2023 Amazon */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#include <immintrin.h>
+#include "x86cpu.h"
+#include "pitch.h"
+
+#if defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(FIXED_POINT)
+
+/* Like the "regular" xcorr_kernel(), but computes 8 results at a time. */
+static void xcorr_kernel_avx(const float *x, const float *y, float sum[8], int len)
+{
+ __m256 xsum0, xsum1, xsum2, xsum3, xsum4, xsum5, xsum6, xsum7;
+ xsum7 = xsum6 = xsum5 = xsum4 = xsum3 = xsum2 = xsum1 = xsum0 = _mm256_setzero_ps();
+ int i;
+ __m256 x0;
+ /* Compute 8 inner products using partial sums. */
+ for (i=0;i<len-7;i+=8)
+ {
+ x0 = _mm256_loadu_ps(x+i);
+ xsum0 = _mm256_fmadd_ps(x0, _mm256_loadu_ps(y+i ), xsum0);
+ xsum1 = _mm256_fmadd_ps(x0, _mm256_loadu_ps(y+i+1), xsum1);
+ xsum2 = _mm256_fmadd_ps(x0, _mm256_loadu_ps(y+i+2), xsum2);
+ xsum3 = _mm256_fmadd_ps(x0, _mm256_loadu_ps(y+i+3), xsum3);
+ xsum4 = _mm256_fmadd_ps(x0, _mm256_loadu_ps(y+i+4), xsum4);
+ xsum5 = _mm256_fmadd_ps(x0, _mm256_loadu_ps(y+i+5), xsum5);
+ xsum6 = _mm256_fmadd_ps(x0, _mm256_loadu_ps(y+i+6), xsum6);
+ xsum7 = _mm256_fmadd_ps(x0, _mm256_loadu_ps(y+i+7), xsum7);
+ }
+ if (i != len) {
+ static const int mask[15] = {-1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0};
+ __m256i m;
+ m = _mm256_loadu_si256((__m256i*)(void*)(mask + 7+i-len));
+ x0 = _mm256_maskload_ps(x+i, m);
+ xsum0 = _mm256_fmadd_ps(x0, _mm256_maskload_ps(y+i , m), xsum0);
+ xsum1 = _mm256_fmadd_ps(x0, _mm256_maskload_ps(y+i+1, m), xsum1);
+ xsum2 = _mm256_fmadd_ps(x0, _mm256_maskload_ps(y+i+2, m), xsum2);
+ xsum3 = _mm256_fmadd_ps(x0, _mm256_maskload_ps(y+i+3, m), xsum3);
+ xsum4 = _mm256_fmadd_ps(x0, _mm256_maskload_ps(y+i+4, m), xsum4);
+ xsum5 = _mm256_fmadd_ps(x0, _mm256_maskload_ps(y+i+5, m), xsum5);
+ xsum6 = _mm256_fmadd_ps(x0, _mm256_maskload_ps(y+i+6, m), xsum6);
+ xsum7 = _mm256_fmadd_ps(x0, _mm256_maskload_ps(y+i+7, m), xsum7);
+ }
+ /* 8 horizontal adds. */
+ /* Compute [0 4] [1 5] [2 6] [3 7] */
+ xsum0 = _mm256_add_ps(_mm256_permute2f128_ps(xsum0, xsum4, 2<<4), _mm256_permute2f128_ps(xsum0, xsum4, 1 | (3<<4)));
+ xsum1 = _mm256_add_ps(_mm256_permute2f128_ps(xsum1, xsum5, 2<<4), _mm256_permute2f128_ps(xsum1, xsum5, 1 | (3<<4)));
+ xsum2 = _mm256_add_ps(_mm256_permute2f128_ps(xsum2, xsum6, 2<<4), _mm256_permute2f128_ps(xsum2, xsum6, 1 | (3<<4)));
+ xsum3 = _mm256_add_ps(_mm256_permute2f128_ps(xsum3, xsum7, 2<<4), _mm256_permute2f128_ps(xsum3, xsum7, 1 | (3<<4)));
+ /* Compute [0 1 4 5] [2 3 6 7] */
+ xsum0 = _mm256_hadd_ps(xsum0, xsum1);
+ xsum1 = _mm256_hadd_ps(xsum2, xsum3);
+ /* Compute [0 1 2 3 4 5 6 7] */
+ xsum0 = _mm256_hadd_ps(xsum0, xsum1);
+ _mm256_storeu_ps(sum, xsum0);
+}
+
+void celt_pitch_xcorr_avx2(const float *_x, const float *_y, float *xcorr, int len, int max_pitch, int arch)
+{
+ int i;
+ celt_assert(max_pitch>0);
+ (void)arch;
+ for (i=0;i<max_pitch-7;i+=8)
+ {
+ xcorr_kernel_avx(_x, _y+i, &xcorr[i], len);
+ }
+ for (;i<max_pitch;i++)
+ {
+ xcorr[i] = celt_inner_prod(_x, _y+i, len, arch);
+ }
+}
+
+#endif
diff --git a/celt/x86/pitch_sse.h b/celt/x86/pitch_sse.h
index 964aef50..127581f3 100644
--- a/celt/x86/pitch_sse.h
+++ b/celt/x86/pitch_sse.h
@@ -131,12 +131,6 @@ extern opus_val32 (*const CELT_INNER_PROD_IMPL[OPUS_ARCHMASK + 1])(
#if defined(OPUS_X86_MAY_HAVE_SSE) && !defined(FIXED_POINT)
-#define OVERRIDE_DUAL_INNER_PROD
-#define OVERRIDE_COMB_FILTER_CONST
-
-#undef dual_inner_prod
-#undef comb_filter_const
-
void dual_inner_prod_sse(const opus_val16 *x,
const opus_val16 *y01,
const opus_val16 *y02,
@@ -154,13 +148,17 @@ void comb_filter_const_sse(opus_val32 *y,
#if defined(OPUS_X86_PRESUME_SSE)
+#define OVERRIDE_DUAL_INNER_PROD
+#define OVERRIDE_COMB_FILTER_CONST
# define dual_inner_prod(x, y01, y02, N, xy1, xy2, arch) \
((void)(arch),dual_inner_prod_sse(x, y01, y02, N, xy1, xy2))
# define comb_filter_const(y, x, T, N, g10, g11, g12, arch) \
((void)(arch),comb_filter_const_sse(y, x, T, N, g10, g11, g12))
-#else
+#elif defined(OPUS_HAVE_RTCD)
+#define OVERRIDE_DUAL_INNER_PROD
+#define OVERRIDE_COMB_FILTER_CONST
extern void (*const DUAL_INNER_PROD_IMPL[OPUS_ARCHMASK + 1])(
const opus_val16 *x,
const opus_val16 *y01,
@@ -187,6 +185,32 @@ extern void (*const COMB_FILTER_CONST_IMPL[OPUS_ARCHMASK + 1])(
#define NON_STATIC_COMB_FILTER_CONST_C
#endif
-#endif
+
+void celt_pitch_xcorr_avx2(const float *_x, const float *_y, float *xcorr, int len, int max_pitch, int arch);
+
+#if defined(OPUS_X86_PRESUME_AVX2)
+
+#define OVERRIDE_PITCH_XCORR
+# define celt_pitch_xcorr celt_pitch_xcorr_avx2
+
+#elif defined(OPUS_HAVE_RTCD) && defined(OPUS_X86_MAY_HAVE_AVX2)
+
+#define OVERRIDE_PITCH_XCORR
+extern void (*const PITCH_XCORR_IMPL[OPUS_ARCHMASK + 1])(
+ const float *_x,
+ const float *_y,
+ float *xcorr,
+ int len,
+ int max_pitch,
+ int arch
+ );
+
+#define celt_pitch_xcorr(_x, _y, xcorr, len, max_pitch, arch) \
+ ((*PITCH_XCORR_IMPL[(arch) & OPUS_ARCHMASK])(_x, _y, xcorr, len, max_pitch, arch))
+
+
+#endif /* OPUS_X86_PRESUME_AVX2 && !OPUS_HAVE_RTCD */
+
+#endif /* OPUS_X86_MAY_HAVE_SSE && !FIXED_POINT */
#endif
diff --git a/celt/x86/vq_sse.h b/celt/x86/vq_sse.h
index b4efe8f2..444503b6 100644
--- a/celt/x86/vq_sse.h
+++ b/celt/x86/vq_sse.h
@@ -28,16 +28,18 @@
#define VQ_SSE_H
#if defined(OPUS_X86_MAY_HAVE_SSE2) && !defined(FIXED_POINT)
-#define OVERRIDE_OP_PVQ_SEARCH
opus_val16 op_pvq_search_sse2(celt_norm *_X, int *iy, int K, int N, int arch);
#if defined(OPUS_X86_PRESUME_SSE2)
+
+#define OVERRIDE_OP_PVQ_SEARCH
#define op_pvq_search(x, iy, K, N, arch) \
(op_pvq_search_sse2(x, iy, K, N, arch))
-#else
+#elif defined(OPUS_HAVE_RTCD)
+#define OVERRIDE_OP_PVQ_SEARCH
extern opus_val16 (*const OP_PVQ_SEARCH_IMPL[OPUS_ARCHMASK + 1])(
celt_norm *_X, int *iy, int K, int N, int arch);
diff --git a/celt/x86/vq_sse2.c b/celt/x86/vq_sse2.c
index 77504286..4c4ebf8e 100644
--- a/celt/x86/vq_sse2.c
+++ b/celt/x86/vq_sse2.c
@@ -75,7 +75,7 @@ opus_val16 op_pvq_search_sse2(celt_norm *_X, int *iy, int K, int N, int arch)
sums = _mm_add_ps(sums, x4);
/* Clear y and iy in case we don't do the projection. */
_mm_storeu_ps(&y[j], _mm_setzero_ps());
- _mm_storeu_si128((__m128i*)&iy[j], _mm_setzero_si128());
+ _mm_storeu_si128((__m128i*)(void*)&iy[j], _mm_setzero_si128());
_mm_storeu_ps(&X[j], x4);
_mm_storeu_ps(&signy[j], s4);
}
@@ -116,7 +116,7 @@ opus_val16 op_pvq_search_sse2(celt_norm *_X, int *iy, int K, int N, int arch)
rx4 = _mm_mul_ps(x4, rcp4);
iy4 = _mm_cvttps_epi32(rx4);
pulses_sum = _mm_add_epi32(pulses_sum, iy4);
- _mm_storeu_si128((__m128i*)&iy[j], iy4);
+ _mm_storeu_si128((__m128i*)(void*)&iy[j], iy4);
y4 = _mm_cvtepi32_ps(iy4);
xy4 = _mm_add_ps(xy4, _mm_mul_ps(x4, y4));
yy4 = _mm_add_ps(yy4, _mm_mul_ps(y4, y4));
@@ -205,10 +205,10 @@ opus_val16 op_pvq_search_sse2(celt_norm *_X, int *iy, int K, int N, int arch)
{
__m128i y4;
__m128i s4;
- y4 = _mm_loadu_si128((__m128i*)&iy[j]);
+ y4 = _mm_loadu_si128((__m128i*)(void*)&iy[j]);
s4 = _mm_castps_si128(_mm_loadu_ps(&signy[j]));
y4 = _mm_xor_si128(_mm_add_epi32(y4, s4), s4);
- _mm_storeu_si128((__m128i*)&iy[j], y4);
+ _mm_storeu_si128((__m128i*)(void*)&iy[j], y4);
}
RESTORE_STACK;
return yy;
diff --git a/celt/x86/x86_arch_macros.h b/celt/x86/x86_arch_macros.h
new file mode 100644
index 00000000..975b443e
--- /dev/null
+++ b/celt/x86/x86_arch_macros.h
@@ -0,0 +1,47 @@
+/* Copyright (c) 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef _MSC_VER
+
+# ifdef OPUS_X86_MAY_HAVE_SSE
+# ifndef __SSE__
+# define __SSE__
+# endif
+# endif
+
+# ifdef OPUS_X86_MAY_HAVE_SSE2
+# ifndef __SSE2__
+# define __SSE2__
+# endif
+# endif
+
+# ifdef OPUS_X86_MAY_HAVE_SSE4_1
+# ifndef __SSE4_1__
+# define __SSE4_1__
+# endif
+# endif
+
+#endif
diff --git a/celt/x86/x86_celt_map.c b/celt/x86/x86_celt_map.c
index d39d88ed..ba8eafe6 100644
--- a/celt/x86/x86_celt_map.c
+++ b/celt/x86/x86_celt_map.c
@@ -90,6 +90,26 @@ opus_val32 (*const CELT_INNER_PROD_IMPL[OPUS_ARCHMASK + 1])(
# else
+#if defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(OPUS_X86_PRESUME_AVX2)
+
+void (*const PITCH_XCORR_IMPL[OPUS_ARCHMASK + 1])(
+ const float *_x,
+ const float *_y,
+ float *xcorr,
+ int len,
+ int max_pitch,
+ int arch
+) = {
+ celt_pitch_xcorr_c, /* non-sse */
+ celt_pitch_xcorr_c,
+ celt_pitch_xcorr_c,
+ celt_pitch_xcorr_c,
+ MAY_HAVE_AVX2(celt_pitch_xcorr)
+};
+
+#endif
+
+
#if defined(OPUS_X86_MAY_HAVE_SSE) && !defined(OPUS_X86_PRESUME_SSE)
void (*const XCORR_KERNEL_IMPL[OPUS_ARCHMASK + 1])(
diff --git a/celt/x86/x86cpu.c b/celt/x86/x86cpu.c
index 6a1914de..2e7c32ae 100644
--- a/celt/x86/x86cpu.c
+++ b/celt/x86/x86cpu.c
@@ -39,7 +39,7 @@
((defined(OPUS_X86_MAY_HAVE_SSE) && !defined(OPUS_X86_PRESUME_SSE)) || \
(defined(OPUS_X86_MAY_HAVE_SSE2) && !defined(OPUS_X86_PRESUME_SSE2)) || \
(defined(OPUS_X86_MAY_HAVE_SSE4_1) && !defined(OPUS_X86_PRESUME_SSE4_1)) || \
- (defined(OPUS_X86_MAY_HAVE_AVX) && !defined(OPUS_X86_PRESUME_AVX)))
+ (defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(OPUS_X86_PRESUME_AVX2)))
#if defined(_MSC_VER)
@@ -105,7 +105,7 @@ typedef struct CPU_Feature{
int HW_SSE2;
int HW_SSE41;
/* SIMD: 256-bit */
- int HW_AVX;
+ int HW_AVX2;
} CPU_Feature;
static void opus_cpu_feature_check(CPU_Feature *cpu_feature)
@@ -121,13 +121,19 @@ static void opus_cpu_feature_check(CPU_Feature *cpu_feature)
cpu_feature->HW_SSE = (info[3] & (1 << 25)) != 0;
cpu_feature->HW_SSE2 = (info[3] & (1 << 26)) != 0;
cpu_feature->HW_SSE41 = (info[2] & (1 << 19)) != 0;
- cpu_feature->HW_AVX = (info[2] & (1 << 28)) != 0;
+ cpu_feature->HW_AVX2 = (info[2] & (1 << 28)) != 0 && (info[2] & (1 << 12)) != 0;
+ if (cpu_feature->HW_AVX2 && nIds >= 7) {
+ cpuid(info, 7);
+ cpu_feature->HW_AVX2 = cpu_feature->HW_AVX2 && (info[1] & (1 << 5)) != 0;
+ } else {
+ cpu_feature->HW_AVX2 = 0;
+ }
}
else {
cpu_feature->HW_SSE = 0;
cpu_feature->HW_SSE2 = 0;
cpu_feature->HW_SSE41 = 0;
- cpu_feature->HW_AVX = 0;
+ cpu_feature->HW_AVX2 = 0;
}
}
@@ -157,7 +163,7 @@ static int opus_select_arch_impl(void)
}
arch++;
- if (!cpu_feature.HW_AVX)
+ if (!cpu_feature.HW_AVX2)
{
return arch;
}
diff --git a/celt/x86/x86cpu.h b/celt/x86/x86cpu.h
index 04e80489..8ae9be8d 100644
--- a/celt/x86/x86cpu.h
+++ b/celt/x86/x86cpu.h
@@ -46,28 +46,53 @@
# define MAY_HAVE_SSE4_1(name) name ## _c
# endif
-# if defined(OPUS_X86_MAY_HAVE_AVX)
-# define MAY_HAVE_AVX(name) name ## _avx
+# if defined(OPUS_X86_MAY_HAVE_AVX2)
+# define MAY_HAVE_AVX2(name) name ## _avx2
# else
-# define MAY_HAVE_AVX(name) name ## _c
+# define MAY_HAVE_AVX2(name) name ## _c
# endif
-# if defined(OPUS_HAVE_RTCD)
+# if defined(OPUS_HAVE_RTCD) && \
+ ((defined(OPUS_X86_MAY_HAVE_SSE) && !defined(OPUS_X86_PRESUME_SSE)) || \
+ (defined(OPUS_X86_MAY_HAVE_SSE2) && !defined(OPUS_X86_PRESUME_SSE2)) || \
+ (defined(OPUS_X86_MAY_HAVE_SSE4_1) && !defined(OPUS_X86_PRESUME_SSE4_1)) || \
+ (defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(OPUS_X86_PRESUME_AVX2)))
int opus_select_arch(void);
# endif
+# if defined(OPUS_X86_MAY_HAVE_SSE2)
+# include "opus_defines.h"
+
/*MOVD should not impose any alignment restrictions, but the C standard does,
and UBSan will report errors if we actually make unaligned accesses.
Use this to work around those restrictions (which should hopefully all get
- optimized to a single MOVD instruction).*/
-#define OP_LOADU_EPI32(x) \
- (int)((*(unsigned char *)(x) | *((unsigned char *)(x) + 1) << 8U |\
- *((unsigned char *)(x) + 2) << 16U | (opus_uint32)*((unsigned char *)(x) + 3) << 24U))
+ optimized to a single MOVD instruction).
+ GCC implemented _mm_loadu_si32() since GCC 11; HOWEVER, there is a bug!
+ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99754 */
+# if !defined(_MSC_VER) && !OPUS_GNUC_PREREQ(11,3) && !(defined(__clang__) && (__clang_major__ >= 8))
+# include <string.h>
+# include <emmintrin.h>
+
+# ifdef _mm_loadu_si32
+# undef _mm_loadu_si32
+# endif
+# define _mm_loadu_si32 WORKAROUND_mm_loadu_si32
+static inline __m128i WORKAROUND_mm_loadu_si32(void const* mem_addr) {
+ int val;
+ memcpy(&val, mem_addr, sizeof(val));
+ return _mm_cvtsi32_si128(val);
+}
+# elif defined(_MSC_VER)
+ /* MSVC needs this for _mm_loadu_si32 */
+# include <immintrin.h>
+# endif
-#define OP_CVTEPI8_EPI32_M32(x) \
- (_mm_cvtepi8_epi32(_mm_cvtsi32_si128(OP_LOADU_EPI32(x))))
+# define OP_CVTEPI8_EPI32_M32(x) \
+ (_mm_cvtepi8_epi32(_mm_loadu_si32(x)))
-#define OP_CVTEPI16_EPI32_M64(x) \
- (_mm_cvtepi16_epi32(_mm_loadl_epi64((__m128i *)(x))))
+# define OP_CVTEPI16_EPI32_M64(x) \
+ (_mm_cvtepi16_epi32(_mm_loadl_epi64((__m128i *)(void*)(x))))
+
+# endif
#endif
diff --git a/celt_headers.mk b/celt_headers.mk
index 706185da..94a65573 100644
--- a/celt_headers.mk
+++ b/celt_headers.mk
@@ -50,4 +50,5 @@ celt/mips/pitch_mipsr1.h \
celt/mips/vq_mipsr1.h \
celt/x86/pitch_sse.h \
celt/x86/vq_sse.h \
+celt/x86/x86_arch_macros.h \
celt/x86/x86cpu.h
diff --git a/celt_sources.mk b/celt_sources.mk
index d6b6765b..6b73d48e 100644
--- a/celt_sources.mk
+++ b/celt_sources.mk
@@ -33,6 +33,9 @@ CELT_SOURCES_SSE4_1 = \
celt/x86/celt_lpc_sse4_1.c \
celt/x86/pitch_sse4_1.c
+CELT_SOURCES_AVX2 = \
+celt/x86/pitch_avx.c
+
CELT_SOURCES_ARM_RTCD = \
celt/arm/armcpu.c \
celt/arm/arm_celt_map.c
diff --git a/cmake/OpusConfig.cmake b/cmake/OpusConfig.cmake
index b82307a1..9fd814cb 100644
--- a/cmake/OpusConfig.cmake
+++ b/cmake/OpusConfig.cmake
@@ -102,7 +102,10 @@ if(MINGW)
endif()
endif()
-if(NOT MSVC)
+if(MSVC)
+ # move cosmetic warnings to level 4
+ add_compile_options(/w44244 /w44305 /w44267)
+else()
set(WARNING_LIST -Wall -W -Wstrict-prototypes -Wextra -Wcast-align -Wnested-externs -Wshadow)
include(CheckCCompilerFlag)
foreach(WARNING_FLAG ${WARNING_LIST})
diff --git a/cmake/OpusFunctions.cmake b/cmake/OpusFunctions.cmake
index 3f22ad81..a265c47f 100644
--- a/cmake/OpusFunctions.cmake
+++ b/cmake/OpusFunctions.cmake
@@ -47,10 +47,12 @@ function(check_flag NAME FLAG)
endfunction()
include(CheckIncludeFile)
-# function to check if compiler supports SSE, SSE2, SSE4.1 and AVX if target
-# systems may not have SSE support then use OPUS_MAY_HAVE_SSE option if target
-# system is guaranteed to have SSE support then OPUS_PRESUME_SSE can be used to
-# skip SSE runtime check
+
+# This function determines if the compiler has support for SSE, SSE2, SSE4.1, AVX,
+# AVX2 and FMA. Should the target systems potentially lack SSE support, the
+# OPUS_MAY_HAVE_SSE option is recommended for use. If, however, the target system is
+# assured to support SSE, the OPUS_PRESUME_SSE option can be employed, thus
+# eliminating the necessity for an SSE runtime check.
function(opus_detect_sse COMPILER_SUPPORT_SIMD)
message(STATUS "Check SIMD support by compiler")
check_include_file(xmmintrin.h HAVE_XMMINTRIN_H) # SSE1
@@ -111,20 +113,20 @@ function(opus_detect_sse COMPILER_SUPPORT_SIMD)
PARENT_SCOPE)
endif()
- check_include_file(immintrin.h HAVE_IMMINTRIN_H) # AVX
+ check_include_file(immintrin.h HAVE_IMMINTRIN_H) # AVX2
if(HAVE_IMMINTRIN_H)
if(MSVC)
- check_flag(AVX /arch:AVX)
+ check_flag(AVX2 /arch:AVX2)
else()
- check_flag(AVX -mavx)
+ check_flag(AVX2 -mavx2 -mfma -mavx)
endif()
else()
- set(AVX_SUPPORTED
+ set(AVX2_SUPPORTED
0
PARENT_SCOPE)
endif()
- if(SSE1_SUPPORTED OR SSE2_SUPPORTED OR SSE4_1_SUPPORTED OR AVX_SUPPORTED)
+ if(SSE1_SUPPORTED OR SSE2_SUPPORTED OR SSE4_1_SUPPORTED OR AVX2_SUPPORTED)
set(COMPILER_SUPPORT_SIMD 1 PARENT_SCOPE)
else()
message(STATUS "No SIMD support in compiler")
@@ -215,7 +217,7 @@ function(get_opus_sources SOURCE_GROUP MAKE_FILE SOURCES)
if(${list_length} LESS 1)
message(
FATAL_ERROR
- "No files parsed succesfully from ${SOURCE_GROUP} in ${MAKE_FILE}")
+ "No files parsed successfully from ${SOURCE_GROUP} in ${MAKE_FILE}")
endif()
# remove trailing whitespaces
diff --git a/cmake/OpusSources.cmake b/cmake/OpusSources.cmake
index b47f8c69..0cf24557 100644
--- a/cmake/OpusSources.cmake
+++ b/cmake/OpusSources.cmake
@@ -13,6 +13,8 @@ get_opus_sources(SILK_SOURCES_X86_RTCD silk_sources.mk silk_sources_x86_rtcd)
get_opus_sources(SILK_SOURCES_SSE4_1 silk_sources.mk silk_sources_sse4_1)
get_opus_sources(SILK_SOURCES_FIXED_SSE4_1 silk_sources.mk
silk_sources_fixed_sse4_1)
+get_opus_sources(SILK_SOURCES_AVX2 silk_sources.mk silk_sources_avx2)
+get_opus_sources(SILK_SOURCES_FLOAT_AVX2 silk_sources.mk silk_sources_float_avx2)
get_opus_sources(SILK_SOURCES_ARM_RTCD silk_sources.mk silk_sources_arm_rtcd)
get_opus_sources(SILK_SOURCES_ARM_NEON_INTR silk_sources.mk
silk_sources_arm_neon_intr)
@@ -29,6 +31,7 @@ get_opus_sources(CELT_SOURCES_X86_RTCD celt_sources.mk celt_sources_x86_rtcd)
get_opus_sources(CELT_SOURCES_SSE celt_sources.mk celt_sources_sse)
get_opus_sources(CELT_SOURCES_SSE2 celt_sources.mk celt_sources_sse2)
get_opus_sources(CELT_SOURCES_SSE4_1 celt_sources.mk celt_sources_sse4_1)
+get_opus_sources(CELT_SOURCES_AVX2 celt_sources.mk celt_sources_avx2)
get_opus_sources(CELT_SOURCES_ARM_RTCD celt_sources.mk celt_sources_arm_rtcd)
get_opus_sources(CELT_SOURCES_ARM_ASM celt_sources.mk celt_sources_arm_asm)
get_opus_sources(CELT_AM_SOURCES_ARM_ASM celt_sources.mk
@@ -37,13 +40,30 @@ get_opus_sources(CELT_SOURCES_ARM_NEON_INTR celt_sources.mk
celt_sources_arm_neon_intr)
get_opus_sources(CELT_SOURCES_ARM_NE10 celt_sources.mk celt_sources_arm_ne10)
+get_opus_sources(DEEP_PLC_HEAD lpcnet_headers.mk deep_plc_headers)
+get_opus_sources(DRED_HEAD lpcnet_headers.mk dred_headers)
+get_opus_sources(OSCE_HEAD lpcnet_headers.mk osce_headers)
+get_opus_sources(DEEP_PLC_SOURCES lpcnet_sources.mk deep_plc_sources)
+get_opus_sources(DRED_SOURCES lpcnet_sources.mk dred_sources)
+get_opus_sources(OSCE_SOURCES lpcnet_sources.mk osce_sources)
+get_opus_sources(DNN_SOURCES_X86_RTCD lpcnet_sources.mk dnn_sources_x86_rtcd)
+get_opus_sources(DNN_SOURCES_SSE2 lpcnet_sources.mk dnn_sources_sse2)
+get_opus_sources(DNN_SOURCES_SSE4_1 lpcnet_sources.mk dnn_sources_sse4_1)
+get_opus_sources(DNN_SOURCES_AVX2 lpcnet_sources.mk dnn_sources_avx2)
+get_opus_sources(DNN_SOURCES_NEON lpcnet_sources.mk dnn_sources_arm_neon)
+get_opus_sources(DNN_SOURCES_DOTPROD lpcnet_sources.mk dnn_sources_arm_dotprod)
+
get_opus_sources(opus_demo_SOURCES Makefile.am opus_demo_sources)
get_opus_sources(opus_custom_demo_SOURCES Makefile.am opus_custom_demo_sources)
get_opus_sources(opus_compare_SOURCES Makefile.am opus_compare_sources)
get_opus_sources(tests_test_opus_api_SOURCES Makefile.am test_opus_api_sources)
get_opus_sources(tests_test_opus_encode_SOURCES Makefile.am
test_opus_encode_sources)
+get_opus_sources(tests_test_opus_extensions_SOURCES Makefile.am
+ test_opus_extensions_sources)
get_opus_sources(tests_test_opus_decode_SOURCES Makefile.am
test_opus_decode_sources)
get_opus_sources(tests_test_opus_padding_SOURCES Makefile.am
test_opus_padding_sources)
+get_opus_sources(tests_test_opus_dred_SOURCES Makefile.am
+ test_opus_dred_sources)
diff --git a/cmake/README.md b/cmake/README.md
new file mode 100644
index 00000000..d5f03375
--- /dev/null
+++ b/cmake/README.md
@@ -0,0 +1,132 @@
+# Using CMake for the Opus Project
+
+This guide provides instructions for using CMake to build the Opus project with various configuration options. CMake is a widely used build system generator that helps manage the build process across different platforms.
+
+Note: Please keep in mind that software documentation can sometimes go out of date as new versions are released. It is always recommended to refer to the official CMake documentation for the most up-to-date and accurate information. You can find the official CMake documentation at [cmake.org/documentation](https://cmake.org/documentation/).
+
+## Prerequisites
+
+Before proceeding, make sure you have the following prerequisites installed:
+
+- CMake
+- Git (optional, but recommended for version control integration)
+- Working C compiler
+
+## Build Instructions
+
+Follow the steps below to build the Opus project using CMake:
+
+1. Clone the Opus repository using Git:
+
+ ```shell
+ git clone https://gitlab.xiph.org/xiph/opus
+ ```
+
+2. Create a build directory within the Opus repository:
+
+ ```shell
+ cd opus
+ mkdir build
+ cd build
+ ```
+
+3. Configure the build with CMake. You can set the desired configuration options using CMake's `-D` flag. Here are some available options:
+
+ - `OPUS_BUILD_SHARED_LIBRARY`: build shared library.
+ - `OPUS_BUILD_TESTING`: build tests.
+ - `OPUS_BUILD_PROGRAMS`: build programs.
+ - `OPUS_CUSTOM_MODES`, enable non-Opus modes, e.g. 44.1 kHz & 2^n frames.
+
+ For example, to enable the custom modes and build programs, use the following command:
+
+ ```shell
+ cmake .. -DOPUS_BUILD_PROGRAMS=ON -DOPUS_BUILD_TESTING=ON
+ ```
+
+4. Build the Opus project:
+
+ ```shell
+ cmake --build .
+ ```
+
+5. After a successful build, you can find the compiled Opus library and associated files in the build directory.
+
+## Testing with CTest
+
+Opus provides a comprehensive test suite to ensure the functionality and correctness of the project. You can execute the tests using CTest, a part of the CMake build system. CTest allows for automated testing and provides useful features for managing and evaluating the test results.
+
+To run the Opus tests using CTest, follow these steps:
+
+1. Navigate to the build directory after configuring and building the project with CMake:
+
+ ```shell
+ cd build
+ ```
+
+2. Execute the tests using CTest:
+
+ ```shell
+ ctest
+ ```
+
+Note: For Windows you need to specify which configuration to test
+
+```shell
+ctest -C Debug
+```
+
+## Platform Support and Bug Reporting
+
+CMake aims to provide broad platform support, allowing the Opus project to be built and used on major operating systems and platforms. The supported platforms include:
+
+- Windows
+- macOS
+- Linux
+- Android
+- iOS
+
+CMake achieves platform support by generating platform-specific build files (e.g., Makefiles, Visual Studio projects) based on the target platform. This allows developers to build and configure the Opus project consistently across different operating systems and environments.
+
+While CMake strives to ensure compatibility and stability across platforms, bugs or issues may still arise in specific configurations. If you encounter any problems during the configuration process or while building the Opus project, we encourage you to file an issue in the [project's issue tracker](https://gitlab.xiph.org/xiph/opus/-/issues).
+
+When reporting an issue, please provide the following information to help us understand and reproduce the configuration problem effectively:
+
+1. Detailed description of the issue, including any error messages or unexpected behavior observed.
+2. Steps to reproduce the problem, including the CMake command and any specific configuration options used.
+3. Operating system and version (e.g., Windows 10, macOS Big Sur, Ubuntu 20.04).
+4. CMake version (e.g., CMake 3.21.1).
+5. Any relevant information about the platform, toolchain, or dependencies used.
+6. Additional context or details that might assist in troubleshooting the issue.
+
+By providing thorough information when reporting configuration issues, you contribute to improving the Opus project's compatibility and reliability across different platforms.
+
+We appreciate your help in identifying and addressing any configuration-related problems, ensuring a better experience for all users of the Opus project.
+
+## Platform Specific Examples
+
+Note: Examples can go out of date. Always refer to documentation for latest reference.
+
+### Cross compiling for Android
+
+```shell
+cmake .. -DCMAKE_TOOLCHAIN_FILE=${ANDROID_HOME}/ndk/25.2.9519653/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a
+```
+
+For more information about cross compiling for android, you can refer to the [Cross compiling for Android documentation](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-android).
+
+### Cross compiling for iOS
+
+```shell
+cmake .. -G "Unix Makefiles" -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64
+```
+
+For more information about cross compilation for iOS, you can refer to the [Cross compiling for iOS documentation](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-ios-tvos-or-watchos).
+
+
+### Windows Visual Studio
+
+```shell
+cmake .. -G "Visual Studio 17 2022" -A x64
+```
+
+For more information about the Visual Studio generator options and additional customization, you can refer to the [Visual Studio Generator documentation](https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html).
diff --git a/configure.ac b/configure.ac
index 999f1783..e4785a47 100644
--- a/configure.ac
+++ b/configure.ac
@@ -22,9 +22,9 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
# For libtool.
dnl Please update these for releases.
-OPUS_LT_CURRENT=9
+OPUS_LT_CURRENT=10
OPUS_LT_REVISION=0
-OPUS_LT_AGE=9
+OPUS_LT_AGE=10
AC_SUBST(OPUS_LT_CURRENT)
AC_SUBST(OPUS_LT_REVISION)
@@ -162,12 +162,39 @@ AS_IF([test "$enable_custom_modes" = "yes"],[
AM_CONDITIONAL([CUSTOM_MODES], [test "$enable_custom_modes" = "yes"])
+AC_ARG_ENABLE([dred],
+ [AS_HELP_STRING([--enable-dred], [Use Deep REDundancy (DRED)])],,
+ [enable_dred=no])
+
+AS_IF([test "$enable_dred" = "yes"],[
+ AC_DEFINE([ENABLE_DRED], [1], [DRED])
+])
+AM_CONDITIONAL([ENABLE_DRED], [test "$enable_dred" = "yes"])
+
+AC_ARG_ENABLE([deep-plc],
+ [AS_HELP_STRING([--enable-deep-plc], [Use deep PLC for SILK])],,
+ [enable_deep_plc=no])
+
+AS_IF([test "$enable_deep_plc" = "yes" || test "$enable_dred" = "yes" || test "$enable_osce" = "yes" || test "$enable_osce_training_data" = "yes"],[
+ AC_DEFINE([ENABLE_DEEP_PLC], [1], [Deep PLC])
+])
+AM_CONDITIONAL([ENABLE_DEEP_PLC], [test "$enable_deep_plc" = "yes" || test "$enable_dred" = "yes" || test "$enable_osce" = "yes" || test "$enable_osce_training_data" = "yes"])
+
+AC_ARG_ENABLE([lossgen],
+ [AS_HELP_STRING([--enable-lossgen], [Build opus_demo with packet loss simulator])],,
+ [enable_lossgen=no])
+
+AS_IF([test "$enable_lossgen" = "yes"],[
+ AC_DEFINE([ENABLE_LOSSGEN], [1], [LOSSGEN])
+])
+AM_CONDITIONAL([ENABLE_LOSSGEN], [test "$enable_lossgen" = "yes"])
+
has_float_approx=no
-#case "$host_cpu" in
-#i[[3456]]86 | x86_64 | powerpc64 | powerpc32 | ia64)
-# has_float_approx=yes
-# ;;
-#esac
+case "$host_cpu" in
+i[[3456]]86 | x86_64 | arm* | aarch64* | powerpc64 | powerpc32 | ia64)
+ has_float_approx=yes
+ ;;
+esac
AC_ARG_ENABLE([float-approx],
[AS_HELP_STRING([--enable-float-approx], [enable fast approximations for floating point])],
@@ -202,7 +229,7 @@ AS_IF([test x"${enable_asm}" = x"yes"],[
case $host_cpu in
arm*)
dnl Currently we only have asm for fixed-point
- AS_IF([test "$enable_float" != "yes"],[
+ #AS_IF([test "$enable_float" != "yes"],[
cpu_arm=yes
AC_DEFINE([OPUS_ARM_ASM], [], [Make use of ARM asm optimization])
AS_GCC_INLINE_ASSEMBLY(
@@ -316,6 +343,18 @@ AS_IF([test x"${enable_asm}" = x"yes"],[
)
])
AC_SUBST(OPUS_ARM_MAY_HAVE_NEON)
+ AS_IF([test x"$OPUS_ARM_MAY_HAVE_DOTPROD" = x"1"],[
+ AC_DEFINE(OPUS_ARM_MAY_HAVE_DOTPROD, 1,
+ [Define if compiler supports DOTPROD instructions])
+ AS_IF([test x"$OPUS_ARM_PRESUME_DOTPROD" = x"1"], [
+ AC_DEFINE(OPUS_ARM_PRESUME_DOTPROD, 1,
+ [Define if binary requires DOTPROD instruction support])
+ asm_optimization="$asm_optimization (DOTPROD)"
+ ],
+ [rtcd_support="$rtcd_support (DOTPROD)"]
+ )
+ ])
+ AC_SUBST(OPUS_ARM_MAY_HAVE_DOTPROD)
dnl Make sure turning on RTCD gets us at least one
dnl instruction set.
AS_IF([test x"$rtcd_support" != x""],
@@ -336,7 +375,7 @@ AS_IF([test x"${enable_asm}" = x"yes"],[
[*** ARM assembly requires perl -- disabling optimizations])
asm_optimization="(missing perl dependency for ARM)"
])
- ])
+ #])
;;
esac
],[
@@ -352,13 +391,14 @@ AM_CONDITIONAL([OPUS_ARM_EXTERNAL_ASM],
AM_CONDITIONAL([HAVE_SSE], [false])
AM_CONDITIONAL([HAVE_SSE2], [false])
AM_CONDITIONAL([HAVE_SSE4_1], [false])
-AM_CONDITIONAL([HAVE_AVX], [false])
+AM_CONDITIONAL([HAVE_AVX2], [false])
m4_define([DEFAULT_X86_SSE_CFLAGS], [-msse])
m4_define([DEFAULT_X86_SSE2_CFLAGS], [-msse2])
m4_define([DEFAULT_X86_SSE4_1_CFLAGS], [-msse4.1])
-m4_define([DEFAULT_X86_AVX_CFLAGS], [-mavx])
+m4_define([DEFAULT_X86_AVX2_CFLAGS], [-mavx -mfma -mavx2])
m4_define([DEFAULT_ARM_NEON_INTR_CFLAGS], [-mfpu=neon])
+m4_define([DEFAULT_ARM_DOTPROD_INTR_CFLAGS], ["-march=armv8.2-a+dotprod"])
# With GCC on ARM32 softfp architectures (e.g. Android, or older Ubuntu) you need to specify
# -mfloat-abi=softfp for -mfpu=neon to work. However, on ARM32 hardfp architectures (e.g. newer Ubuntu),
# this option will break things.
@@ -374,14 +414,16 @@ AS_CASE([$host],
AC_ARG_VAR([X86_SSE_CFLAGS], [C compiler flags to compile SSE intrinsics @<:@default=]DEFAULT_X86_SSE_CFLAGS[@:>@])
AC_ARG_VAR([X86_SSE2_CFLAGS], [C compiler flags to compile SSE2 intrinsics @<:@default=]DEFAULT_X86_SSE2_CFLAGS[@:>@])
AC_ARG_VAR([X86_SSE4_1_CFLAGS], [C compiler flags to compile SSE4.1 intrinsics @<:@default=]DEFAULT_X86_SSE4_1_CFLAGS[@:>@])
-AC_ARG_VAR([X86_AVX_CFLAGS], [C compiler flags to compile AVX intrinsics @<:@default=]DEFAULT_X86_AVX_CFLAGS[@:>@])
+AC_ARG_VAR([X86_AVX2_CFLAGS], [C compiler flags to compile AVX2 intrinsics @<:@default=]DEFAULT_X86_AVX2_CFLAGS[@:>@])
AC_ARG_VAR([ARM_NEON_INTR_CFLAGS], [C compiler flags to compile ARM NEON intrinsics @<:@default=]DEFAULT_ARM_NEON_INTR_CFLAGS / DEFAULT_ARM_NEON_SOFTFP_INTR_CFLAGS[@:>@])
+AC_ARG_VAR([ARM_DOTPROD_INTR_CFLAGS], [C compiler flags to compile ARM DOTPROD intrinsics @<:@default=]DEFAULT_ARM_DOTPROD_INTR_CFLAGS[@:>@])
AS_VAR_SET_IF([X86_SSE_CFLAGS], [], [AS_VAR_SET([X86_SSE_CFLAGS], "DEFAULT_X86_SSE_CFLAGS")])
AS_VAR_SET_IF([X86_SSE2_CFLAGS], [], [AS_VAR_SET([X86_SSE2_CFLAGS], "DEFAULT_X86_SSE2_CFLAGS")])
AS_VAR_SET_IF([X86_SSE4_1_CFLAGS], [], [AS_VAR_SET([X86_SSE4_1_CFLAGS], "DEFAULT_X86_SSE4_1_CFLAGS")])
-AS_VAR_SET_IF([X86_AVX_CFLAGS], [], [AS_VAR_SET([X86_AVX_CFLAGS], "DEFAULT_X86_AVX_CFLAGS")])
+AS_VAR_SET_IF([X86_AVX2_CFLAGS], [], [AS_VAR_SET([X86_AVX2_CFLAGS], "DEFAULT_X86_AVX2_CFLAGS")])
AS_VAR_SET_IF([ARM_NEON_INTR_CFLAGS], [], [AS_VAR_SET([ARM_NEON_INTR_CFLAGS], ["$RESOLVED_DEFAULT_ARM_NEON_INTR_CFLAGS"])])
+AS_VAR_SET_IF([ARM_DOTPROD_INTR_CFLAGS], [], [AS_VAR_SET([ARM_DOTPROD_INTR_CFLAGS], ["DEFAULT_ARM_DOTPROD_INTR_CFLAGS"])])
AC_DEFUN([OPUS_PATH_NE10],
[
@@ -525,6 +567,46 @@ AS_IF([test x"$enable_intrinsics" = x"yes"],[
intrinsics_support="$intrinsics_support (NEON [Aarch64])"
])
+ OPUS_CHECK_INTRINSICS(
+ [Aarch64 dotprod],
+ [$ARM_DOTPROD_INTR_CFLAGS],
+ [OPUS_ARM_MAY_HAVE_DOTPROD],
+ [OPUS_ARM_PRESUME_DOTPROD],
+ [[#include <arm_neon.h>
+ ]],
+ [[
+ static int32x4_t acc;
+ static int8x16_t a, b;
+ acc = vdotq_s32(acc, a, b);
+ ]]
+ )
+ AS_IF([test x"$OPUS_ARM_MAY_HAVE_DOTPROD" = x"1" && test x"$OPUS_ARM_PRESUME_DOTPROD" != x"1"],
+ [
+ OPUS_ARM_DOTPROD_INTR_CFLAGS="$ARM_NEON_DOTPROD_CFLAGS"
+ AC_SUBST([OPUS_ARM_DOTPROD_INTR_CFLAGS])
+ ]
+ )
+
+ AS_IF([test x"$OPUS_ARM_MAY_HAVE_DOTPROD" = x"1"],
+ [
+ AC_DEFINE([OPUS_ARM_MAY_HAVE_DOTPROD], 1, [Compiler supports Aarch64 DOTPROD Intrinsics])
+ intrinsics_support="$intrinsics_support (DOTPROD)"
+
+ AS_IF([test x"$OPUS_ARM_PRESUME_DOTPROD" = x"1"],
+ [
+ AC_DEFINE([OPUS_ARM_PRESUME_DOTPROD], 1, [Define if binary requires Aarch64 dotprod Intrinsics])
+ intrinsics_support="$intrinsics_support (DOTPROD [Aarch64])"
+ ])
+
+ AS_IF([test x"$enable_rtcd" != x"no" && test x"$OPUS_ARM_PRESUME_DOTPROD" != x"1"],
+ [AS_IF([test x"$rtcd_support" = x"no"],
+ [rtcd_support="ARM (DOTPROD Intrinsics)"],
+ [rtcd_support="$rtcd_support (DOTPROD Intrinsics)"])])
+
+ ]
+ )
+
+
AS_IF([test x"$intrinsics_support" = x""],
[intrinsics_support=no],
[intrinsics_support="ARM$intrinsics_support"])
@@ -601,24 +683,31 @@ AS_IF([test x"$enable_intrinsics" = x"yes"],[
]
)
OPUS_CHECK_INTRINSICS(
- [AVX],
- [$X86_AVX_CFLAGS],
- [OPUS_X86_MAY_HAVE_AVX],
- [OPUS_X86_PRESUME_AVX],
+ [AVX2],
+ [$X86_AVX2_CFLAGS],
+ [OPUS_X86_MAY_HAVE_AVX2],
+ [OPUS_X86_PRESUME_AVX2],
[[#include <immintrin.h>
#include <time.h>
]],
[[
+ unsigned char utest[[16]] = {1};
__m256 mtest;
+ __m256i mtest1;
+ __m256i mtest2;
mtest = _mm256_set1_ps((float)time(NULL));
- mtest = _mm256_addsub_ps(mtest, mtest);
- return _mm_cvtss_si32(_mm256_extractf128_ps(mtest, 0));
+ mtest = _mm256_fmadd_ps(mtest, mtest, mtest);
+ mtest1 = _mm256_set_m128i(_mm_loadu_si64(utest), _mm_loadu_si64(utest));
+ mtest2 =
+ _mm256_cvtepi16_epi32(_mm_loadu_si128((__m128i_u *)utest));
+ return _mm256_extract_epi16(_mm256_xor_si256(
+ _mm256_xor_si256(mtest1, mtest2), _mm256_cvttps_epi32(mtest)), 0);
]]
)
- AS_IF([test x"$OPUS_X86_MAY_HAVE_AVX" = x"1" && test x"$OPUS_X86_PRESUME_AVX" != x"1"],
+ AS_IF([test x"$OPUS_X86_MAY_HAVE_AVX2" = x"1" && test x"$OPUS_X86_PRESUME_AVX2" != x"1"],
[
- OPUS_X86_AVX_CFLAGS="$X86_AVX_CFLAGS"
- AC_SUBST([OPUS_X86_AVX_CFLAGS])
+ OPUS_X86_AVX2_CFLAGS="$X86_AVX2_CFLAGS"
+ AC_SUBST([OPUS_X86_AVX2_CFLAGS])
]
)
AS_IF([test x"$rtcd_support" = x"no"], [rtcd_support=""])
@@ -660,17 +749,17 @@ AS_IF([test x"$enable_intrinsics" = x"yes"],[
[
AC_MSG_WARN([Compiler does not support SSE4.1 intrinsics])
])
- AS_IF([test x"$OPUS_X86_MAY_HAVE_AVX" = x"1"],
+ AS_IF([test x"$OPUS_X86_MAY_HAVE_AVX2" = x"1"],
[
- AC_DEFINE([OPUS_X86_MAY_HAVE_AVX], 1, [Compiler supports X86 AVX Intrinsics])
- intrinsics_support="$intrinsics_support AVX"
+ AC_DEFINE([OPUS_X86_MAY_HAVE_AVX2], 1, [Compiler supports X86 AVX2 Intrinsics])
+ intrinsics_support="$intrinsics_support AVX2"
- AS_IF([test x"$OPUS_X86_PRESUME_AVX" = x"1"],
- [AC_DEFINE([OPUS_X86_PRESUME_AVX], 1, [Define if binary requires AVX intrinsics support])],
- [rtcd_support="$rtcd_support AVX"])
+ AS_IF([test x"$OPUS_X86_PRESUME_AVX2" = x"1"],
+ [AC_DEFINE([OPUS_X86_PRESUME_AVX2], 1, [Define if binary requires AVX2 intrinsics support])],
+ [rtcd_support="$rtcd_support AVX2"])
],
[
- AC_MSG_WARN([Compiler does not support AVX intrinsics])
+ AC_MSG_WARN([Compiler does not support AVX2 intrinsics])
])
AS_IF([test x"$intrinsics_support" = x""],
@@ -742,6 +831,8 @@ AS_IF([test x"$enable_intrinsics" = x"yes"],[
])
AM_CONDITIONAL([CPU_ARM], [test "$cpu_arm" = "yes"])
+AM_CONDITIONAL([HAVE_ARM_DOTPROD],
+ [test x"$OPUS_ARM_MAY_HAVE_DOTPROD" = x"1"])
AM_CONDITIONAL([HAVE_ARM_NEON_INTR],
[test x"$OPUS_ARM_MAY_HAVE_NEON_INTR" = x"1"])
AM_CONDITIONAL([HAVE_ARM_NE10],
@@ -753,8 +844,8 @@ AM_CONDITIONAL([HAVE_SSE2],
[test x"$OPUS_X86_MAY_HAVE_SSE2" = x"1"])
AM_CONDITIONAL([HAVE_SSE4_1],
[test x"$OPUS_X86_MAY_HAVE_SSE4_1" = x"1"])
-AM_CONDITIONAL([HAVE_AVX],
- [test x"$OPUS_X86_MAY_HAVE_AVX" = x"1"])
+AM_CONDITIONAL([HAVE_AVX2],
+ [test x"$OPUS_X86_MAY_HAVE_AVX2" = x"1"])
AM_CONDITIONAL([HAVE_RTCD],
[test x"$enable_rtcd" = x"yes" -a x"$rtcd_support" != x"no"])
@@ -813,6 +904,47 @@ AS_IF([test "$enable_doc" = "yes"], [
HAVE_DOXYGEN=no
])
+AC_ARG_ENABLE([dot-product],
+ AS_HELP_STRING([--disable-dot-product], [Disable dot product implementation]),,
+ enable_dot_product=yes)
+
+AS_IF([test "$enable_dot_product" = "no"], [
+ AC_DEFINE([DISABLE_DOT_PROD], [1], [Disable dot product instructions])
+])
+
+AC_ARG_ENABLE([dnn-debug-float],
+ AS_HELP_STRING([--enable-dnn-debug-float], [Use floating-point DNN computation everywhere]),,
+ enable_dnn_debug_float=no)
+
+AS_IF([test "$enable_dnn_debug_float" = "no"], [
+ AC_DEFINE([DISABLE_DEBUG_FLOAT], [1], [Disable DNN debug float])
+])
+
+AC_ARG_ENABLE([osce-training-data],
+ AS_HELP_STRING([--enable-osce-training-data], [enables feature output for SILK enhancement]),,
+ [enable_osc_training_data=no]
+)
+
+AS_IF([test "$enable_osce_training_data" = "yes"], [
+ AC_DEFINE([ENABLE_OSCE_TRAINING_DATA], [1], [Enable dumping of OSCE training data])
+])
+
+AC_MSG_CHECKING([argument osce training data])
+AS_IF([test "$enable_osce_training_data" = "yes"], [
+ AC_MSG_RESULT([yes])
+], [AC_MSG_RESULT([no])])
+
+AC_ARG_ENABLE([osce],
+ AS_HELP_STRING([--enable-osce], [enables speech coding enhancement]),,
+ [enable_osce=no]
+)
+
+AS_IF([test "$enable_osce" = "yes" || test "$enable_osce_training_data" = "yes"], [
+ AC_DEFINE([ENABLE_OSCE], [1], [Enable Opus Speech Coding Enhancement])
+])
+
+AM_CONDITIONAL([ENABLE_OSCE], [test "$enable_osce" = "yes" || test "$enable_osce_training_data" = "yes"])
+
AM_CONDITIONAL([HAVE_DOXYGEN], [test "$HAVE_DOXYGEN" = "yes"])
AC_ARG_ENABLE([extra-programs],
diff --git a/dnn/LPCNet.yml b/dnn/LPCNet.yml
new file mode 100644
index 00000000..3a726657
--- /dev/null
+++ b/dnn/LPCNet.yml
@@ -0,0 +1,24 @@
+#
+# install
+# conda env create -f=LPCNet.yml
+#
+# update
+# conda env update -f=LPCNet.yml
+#
+# activate
+# conda activate LPCNet
+#
+# remove
+# conda remove --name LPCNet --all
+#
+name: LPCNet
+channels:
+ - anaconda
+ - conda-forge
+dependencies:
+ - keras==2.2.4
+ - python>=3.6
+ - tensorflow-gpu==1.12.0
+ - cudatoolkit
+ - h5py
+ - numpy
diff --git a/dnn/README b/dnn/README
new file mode 100644
index 00000000..96dc92fb
--- /dev/null
+++ b/dnn/README
@@ -0,0 +1 @@
+See README.md
diff --git a/dnn/README.md b/dnn/README.md
new file mode 100644
index 00000000..ad4a6724
--- /dev/null
+++ b/dnn/README.md
@@ -0,0 +1,126 @@
+# LPCNet
+
+Low complexity implementation of the WaveRNN-based LPCNet algorithm, as described in:
+
+- J.-M. Valin, J. Skoglund, [LPCNet: Improving Neural Speech Synthesis Through Linear Prediction](https://jmvalin.ca/papers/lpcnet_icassp2019.pdf), *Proc. International Conference on Acoustics, Speech and Signal Processing (ICASSP)*, arXiv:1810.11846, 2019.
+- J.-M. Valin, U. Isik, P. Smaragdis, A. Krishnaswamy, [Neural Speech Synthesis on a Shoestring: Improving the Efficiency of LPCNet](https://jmvalin.ca/papers/improved_lpcnet.pdf), *Proc. ICASSP*, arxiv:2106.04129, 2022.
+- K. Subramani, J.-M. Valin, U. Isik, P. Smaragdis, A. Krishnaswamy, [End-to-end LPCNet: A Neural Vocoder With Fully-Differentiable LPC Estimation](https://jmvalin.ca/papers/lpcnet_end2end.pdf), *Proc. INTERSPEECH*, arxiv:2106.04129, 2022.
+
+For coding/PLC applications of LPCNet, see:
+
+- J.-M. Valin, J. Skoglund, [A Real-Time Wideband Neural Vocoder at 1.6 kb/s Using LPCNet](https://jmvalin.ca/papers/lpcnet_codec.pdf), *Proc. INTERSPEECH*, arxiv:1903.12087, 2019.
+- J. Skoglund, J.-M. Valin, [Improving Opus Low Bit Rate Quality with Neural Speech Synthesis](https://jmvalin.ca/papers/opusnet.pdf), *Proc. INTERSPEECH*, arxiv:1905.04628, 2020.
+- J.-M. Valin, A. Mustafa, C. Montgomery, T.B. Terriberry, M. Klingbeil, P. Smaragdis, A. Krishnaswamy, [Real-Time Packet Loss Concealment With Mixed Generative and Predictive Model](https://jmvalin.ca/papers/lpcnet_plc.pdf), *Proc. INTERSPEECH*, arxiv:2205.05785, 2022.
+- J.-M. Valin, J. Büthe, A. Mustafa, [Low-Bitrate Redundancy Coding of Speech Using a Rate-Distortion-Optimized Variational Autoencoder](https://jmvalin.ca/papers/valin_dred.pdf), *Proc. ICASSP*, arXiv:2212.04453, 2023. ([blog post](https://www.amazon.science/blog/neural-encoding-enables-more-efficient-recovery-of-lost-audio-packets))
+
+# Introduction
+
+Work in progress software for researching low CPU complexity algorithms for speech synthesis and compression by applying Linear Prediction techniques to WaveRNN. High quality speech can be synthesised on regular CPUs (around 3 GFLOP) with SIMD support (SSE2, SSSE3, AVX, AVX2/FMA, NEON currently supported). The code also supports very low bitrate compression at 1.6 kb/s.
+
+The BSD licensed software is written in C and Python/Keras. For training, a GTX 1080 Ti or better is recommended.
+
+This software is an open source starting point for LPCNet/WaveRNN-based speech synthesis and coding.
+
+# Using the existing software
+
+You can build the code using:
+
+```
+./autogen.sh
+./configure
+make
+```
+Note that the autogen.sh script is used when building from Git and will automatically download the latest model
+(models are too large to put in Git). By default, LPCNet will attempt to use 8-bit dot product instructions on AVX\*/Neon to
+speed up inference. To disable that (e.g. to avoid quantization effects when retraining), add --disable-dot-product to the
+configure script. LPCNet does not yet have a complete implementation for some of the integer operations on the ARMv7
+architecture so for now you will also need --disable-dot-product to successfully compile on 32-bit ARM.
+
+It is highly recommended to set the CFLAGS environment variable to enable AVX or NEON *prior* to running configure, otherwise
+no vectorization will take place and the code will be very slow. On a recent x86 CPU, something like
+```
+export CFLAGS='-Ofast -g -march=native'
+```
+should work. On ARM, you can enable Neon with:
+```
+export CFLAGS='-Ofast -g -mfpu=neon'
+```
+While not strictly required, the -Ofast flag will help with auto-vectorization, especially for dot products that
+cannot be optimized without -ffast-math (which -Ofast enables). Additionally, -falign-loops=32 has been shown to
+help on x86.
+
+You can test the capabilities of LPCNet using the lpcnet\_demo application. To encode a file:
+```
+./lpcnet_demo -encode input.pcm compressed.bin
+```
+where input.pcm is a 16-bit (machine endian) PCM file sampled at 16 kHz. The raw compressed data (no header)
+is written to compressed.bin and consists of 8 bytes per 40-ms packet.
+
+To decode:
+```
+./lpcnet_demo -decode compressed.bin output.pcm
+```
+where output.pcm is also 16-bit, 16 kHz PCM.
+
+Alternatively, you can run the uncompressed analysis/synthesis using -features
+instead of -encode and -synthesis instead of -decode.
+The same functionality is available in the form of a library. See include/lpcnet.h for the API.
+
+To try packet loss concealment (PLC), you first need a PLC model, which you can get with:
+```
+./download_model.sh plc-3b1eab4
+```
+or (for the PLC challenge submission):
+```
+./download_model.sh plc_challenge
+```
+PLC can be tested with:
+```
+./lpcnet_demo -plc_file noncausal_dc error_pattern.txt input.pcm output.pcm
+```
+where error_pattern.txt is a text file with one entry per 20-ms packet, with 1 meaning "packet lost" and 0 meaning "packet not lost".
+noncausal_dc is the non-causal (5-ms look-ahead) with special handling for DC offsets. It's also possible to use "noncausal", "causal",
+or "causal_dc".
+
+# Training a new model
+
+This codebase is also meant for research and it is possible to train new models. These are the steps to do that:
+
+1. Set up a Keras system with GPU.
+
+1. Generate training data:
+ ```
+ ./dump_data -train input.s16 features.f32 data.s16
+ ```
+ where the first file contains 16 kHz 16-bit raw PCM audio (no header) and the other files are output files. This program makes several passes over the data with different filters to generate a large amount of training data.
+
+1. Now that you have your files, train with:
+ ```
+ python3 training_tf2/train_lpcnet.py features.f32 data.s16 model_name
+ ```
+ and it will generate an h5 file for each iteration, with model\_name as prefix. If it stops with a
+ "Failed to allocate RNN reserve space" message try specifying a smaller --batch-size for train\_lpcnet.py.
+
+1. You can synthesise speech with Python and your GPU card (very slow):
+ ```
+ ./dump_data -test test_input.s16 test_features.f32
+ ./training_tf2/test_lpcnet.py lpcnet_model_name.h5 test_features.f32 test.s16
+ ```
+
+1. Or with C on a CPU (C inference is much faster):
+ First extract the model files nnet\_data.h and nnet\_data.c
+ ```
+ ./training_tf2/dump_lpcnet.py lpcnet_model_name.h5
+ ```
+ and move the generated nnet\_data.\* files to the src/ directory.
+ Then you just need to rebuild the software and use lpcnet\_demo as explained above.
+
+# Speech Material for Training
+
+Suitable training material can be obtained from [Open Speech and Language Resources](https://www.openslr.org/). See the datasets.txt file for details on suitable training data.
+
+# Reading Further
+
+1. [LPCNet: DSP-Boosted Neural Speech Synthesis](https://people.xiph.org/~jm/demo/lpcnet/)
+1. [A Real-Time Wideband Neural Vocoder at 1.6 kb/s Using LPCNet](https://people.xiph.org/~jm/demo/lpcnet_codec/)
+1. Sample model files (check compatibility): https://media.xiph.org/lpcnet/data/
diff --git a/dnn/adaconvtest.c b/dnn/adaconvtest.c
new file mode 100644
index 00000000..722e4aff
--- /dev/null
+++ b/dnn/adaconvtest.c
@@ -0,0 +1,449 @@
+#include "lace_data.h"
+#include "nolace_data.h"
+#include "osce.h"
+#include "nndsp.h"
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+
+extern const WeightArray lacelayers_arrays[];
+extern const WeightArray nolacelayers_arrays[];
+
+void adaconv_compare(
+ const char * prefix,
+ int num_frames,
+ AdaConvState* hAdaConv,
+ LinearLayer *kernel_layer,
+ LinearLayer *gain_layer,
+ int feature_dim,
+ int frame_size,
+ int overlap_size,
+ int in_channels,
+ int out_channels,
+ int kernel_size,
+ int left_padding,
+ float filter_gain_a,
+ float filter_gain_b,
+ float shape_gain
+)
+{
+ char feature_file[256];
+ char x_in_file[256];
+ char x_out_file[256];
+ char message[512];
+ int i_frame, i_sample;
+ float mse;
+ float features[512];
+ float x_in[512];
+ float x_out_ref[512];
+ float x_out[512];
+ float window[40];
+
+ init_adaconv_state(hAdaConv);
+ compute_overlap_window(window, 40);
+
+ FILE *f_features, *f_x_in, *f_x_out;
+
+ strcpy(feature_file, prefix);
+ strcat(feature_file, "_features.f32");
+ f_features = fopen(feature_file, "r");
+ if (f_features == NULL)
+ {
+ sprintf(message, "could not open file %s", feature_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(x_in_file, prefix);
+ strcat(x_in_file, "_x_in.f32");
+ f_x_in = fopen(x_in_file, "r");
+ if (f_x_in == NULL)
+ {
+ sprintf(message, "could not open file %s", x_in_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(x_out_file, prefix);
+ strcat(x_out_file, "_x_out.f32");
+ f_x_out = fopen(x_out_file, "r");
+ if (f_x_out == NULL)
+ {
+ sprintf(message, "could not open file %s", x_out_file);
+ perror(message);
+ exit(1);
+ }
+
+ for (i_frame = 0; i_frame < num_frames; i_frame ++)
+ {
+ if (fread(features, sizeof(float), feature_dim, f_features) != feature_dim)
+ {
+ fprintf(stderr, "could not read frame %d from %s\n", i_frame, feature_file);
+ exit(1);
+ }
+
+ if (fread(x_in, sizeof(float), frame_size * in_channels, f_x_in) != frame_size * in_channels)
+ {
+ fprintf(stderr, "could not read frame %d from %s\n", i_frame, x_in_file);
+ exit(1);
+ }
+
+ if (fread(x_out_ref, sizeof(float), frame_size * out_channels, f_x_out) != frame_size * out_channels)
+ {
+ fprintf(stderr, "could not read frame %d from %s\n", i_frame, x_out_file);
+ exit(1);
+ }
+
+ adaconv_process_frame(hAdaConv, x_out, x_in, features, kernel_layer, gain_layer, feature_dim,
+ frame_size, overlap_size, in_channels, out_channels, kernel_size, left_padding,
+ filter_gain_a, filter_gain_b, shape_gain, window, 0);
+
+ mse = 0;
+ for (i_sample = 0; i_sample < frame_size * out_channels; i_sample ++)
+ {
+ mse += pow(x_out_ref[i_sample] - x_out[i_sample], 2);
+ }
+ mse = sqrt(mse / (frame_size * out_channels));
+ printf("rmse[%d] %f\n", i_frame, mse);
+
+ }
+}
+
+
+void adacomb_compare(
+ const char * prefix,
+ int num_frames,
+ AdaCombState* hAdaComb,
+ LinearLayer *kernel_layer,
+ LinearLayer *gain_layer,
+ LinearLayer *global_gain_layer,
+ int feature_dim,
+ int frame_size,
+ int overlap_size,
+ int kernel_size,
+ int left_padding,
+ float filter_gain_a,
+ float filter_gain_b,
+ float log_gain_limit
+)
+{
+ char feature_file[256];
+ char x_in_file[256];
+ char p_in_file[256];
+ char x_out_file[256];
+ char message[512];
+ int i_frame, i_sample;
+ float mse;
+ float features[512];
+ float x_in[512];
+ float x_out_ref[512];
+ float x_out[512];
+ int pitch_lag;
+ float window[40];
+
+ init_adacomb_state(hAdaComb);
+ compute_overlap_window(window, 40);
+
+ FILE *f_features, *f_x_in, *f_p_in, *f_x_out;
+
+ strcpy(feature_file, prefix);
+ strcat(feature_file, "_features.f32");
+ f_features = fopen(feature_file, "r");
+ if (f_features == NULL)
+ {
+ sprintf(message, "could not open file %s", feature_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(x_in_file, prefix);
+ strcat(x_in_file, "_x_in.f32");
+ f_x_in = fopen(x_in_file, "r");
+ if (f_x_in == NULL)
+ {
+ sprintf(message, "could not open file %s", x_in_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(p_in_file, prefix);
+ strcat(p_in_file, "_p_in.s32");
+ f_p_in = fopen(p_in_file, "r");
+ if (f_p_in == NULL)
+ {
+ sprintf(message, "could not open file %s", p_in_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(x_out_file, prefix);
+ strcat(x_out_file, "_x_out.f32");
+ f_x_out = fopen(x_out_file, "r");
+ if (f_x_out == NULL)
+ {
+ sprintf(message, "could not open file %s", x_out_file);
+ perror(message);
+ exit(1);
+ }
+
+ for (i_frame = 0; i_frame < num_frames; i_frame ++)
+ {
+ if (fread(features, sizeof(float), feature_dim, f_features) != feature_dim)
+ {
+ fprintf(stderr, "could not read frame %d from %s\n", i_frame, feature_file);
+ exit(1);
+ }
+
+ if (fread(x_in, sizeof(float), frame_size, f_x_in) != frame_size)
+ {
+ fprintf(stderr, "could not read frame %d from %s\n", i_frame, x_in_file);
+ exit(1);
+ }
+
+ if (fread(&pitch_lag, sizeof(int), 1, f_p_in) != 1)
+ {
+ fprintf(stderr, "could not read frame %d from %s\n", i_frame, p_in_file);
+ exit(1);
+ }
+
+ if (fread(x_out_ref, sizeof(float), frame_size, f_x_out) != frame_size)
+ {
+ fprintf(stderr, "could not read frame %d from %s\n", i_frame, x_out_file);
+ exit(1);
+ }
+
+ adacomb_process_frame(hAdaComb, x_out, x_in, features, kernel_layer, gain_layer, global_gain_layer,
+ pitch_lag, feature_dim, frame_size, overlap_size, kernel_size, left_padding, filter_gain_a, filter_gain_b, log_gain_limit, window, 0);
+
+
+ mse = 0;
+ for (i_sample = 0; i_sample < frame_size; i_sample ++)
+ {
+ mse += pow(x_out_ref[i_sample] - x_out[i_sample], 2);
+ }
+ mse = sqrt(mse / (frame_size));
+ printf("rmse[%d] %f\n", i_frame, mse);
+
+ }
+}
+
+void adashape_compare(
+ const char * prefix,
+ int num_frames,
+ AdaShapeState* hAdaShape,
+ LinearLayer *alpha1,
+ LinearLayer *alpha2,
+ int feature_dim,
+ int frame_size,
+ int avg_pool_k
+)
+{
+ char feature_file[256];
+ char x_in_file[256];
+ char x_out_file[256];
+ char message[512];
+ int i_frame, i_sample;
+ float mse;
+ float features[512];
+ float x_in[512];
+ float x_out_ref[512];
+ float x_out[512];
+
+ init_adashape_state(hAdaShape);
+
+ FILE *f_features, *f_x_in, *f_x_out;
+
+ strcpy(feature_file, prefix);
+ strcat(feature_file, "_features.f32");
+ f_features = fopen(feature_file, "r");
+ if (f_features == NULL)
+ {
+ sprintf(message, "could not open file %s", feature_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(x_in_file, prefix);
+ strcat(x_in_file, "_x_in.f32");
+ f_x_in = fopen(x_in_file, "r");
+ if (f_x_in == NULL)
+ {
+ sprintf(message, "could not open file %s", x_in_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(x_out_file, prefix);
+ strcat(x_out_file, "_x_out.f32");
+ f_x_out = fopen(x_out_file, "r");
+ if (f_x_out == NULL)
+ {
+ sprintf(message, "could not open file %s", x_out_file);
+ perror(message);
+ exit(1);
+ }
+
+ for (i_frame = 0; i_frame < num_frames; i_frame ++)
+ {
+ if (fread(features, sizeof(float), feature_dim, f_features) != feature_dim)
+ {
+ fprintf(stderr, "could not read frame %d from %s\n", i_frame, feature_file);
+ exit(1);
+ }
+
+ if (fread(x_in, sizeof(float), frame_size, f_x_in) != frame_size)
+ {
+ fprintf(stderr, "could not read frame %d from %s\n", i_frame, x_in_file);
+ exit(1);
+ }
+
+ if (fread(x_out_ref, sizeof(float), frame_size, f_x_out) != frame_size)
+ {
+ fprintf(stderr, "could not read frame %d from %s\n", i_frame, x_out_file);
+ exit(1);
+ }
+
+ adashape_process_frame(hAdaShape, x_out, x_in, features, alpha1, alpha2, feature_dim,
+ frame_size, avg_pool_k, 0);
+
+ mse = 0;
+ for (i_sample = 0; i_sample < frame_size; i_sample ++)
+ {
+ mse += pow(x_out_ref[i_sample] - x_out[i_sample], 2);
+ }
+ mse = sqrt(mse / (frame_size));
+ printf("rmse[%d] %f\n", i_frame, mse);
+
+ }
+}
+
+
+int main()
+{
+ LACELayers hLACE;
+ NOLACELayers hNoLACE;
+
+ AdaConvState hAdaConv;
+ AdaCombState hAdaComb;
+ AdaShapeState hAdaShape;
+
+ init_adaconv_state(&hAdaConv);
+
+ init_lacelayers(&hLACE, lacelayers_arrays);
+ init_nolacelayers(&hNoLACE, nolacelayers_arrays);
+
+ printf("\ntesting lace.af1 (1 in, 1 out)...\n");
+ adaconv_compare(
+ "testvectors/lace_af1",
+ 5,
+ &hAdaConv,
+ &hLACE.lace_af1_kernel,
+ &hLACE.lace_af1_gain,
+ LACE_AF1_FEATURE_DIM,
+ LACE_AF1_FRAME_SIZE,
+ LACE_AF1_OVERLAP_SIZE,
+ LACE_AF1_IN_CHANNELS,
+ LACE_AF1_OUT_CHANNELS,
+ LACE_AF1_KERNEL_SIZE,
+ LACE_AF1_LEFT_PADDING,
+ LACE_AF1_FILTER_GAIN_A,
+ LACE_AF1_FILTER_GAIN_B,
+ LACE_AF1_SHAPE_GAIN
+ );
+
+
+ printf("\ntesting nolace.af1 (1 in, 2 out)...\n");
+ adaconv_compare(
+ "testvectors/nolace_af1",
+ 5,
+ &hAdaConv,
+ &hNoLACE.nolace_af1_kernel,
+ &hNoLACE.nolace_af1_gain,
+ NOLACE_AF1_FEATURE_DIM,
+ NOLACE_AF1_FRAME_SIZE,
+ NOLACE_AF1_OVERLAP_SIZE,
+ NOLACE_AF1_IN_CHANNELS,
+ NOLACE_AF1_OUT_CHANNELS,
+ NOLACE_AF1_KERNEL_SIZE,
+ NOLACE_AF1_LEFT_PADDING,
+ NOLACE_AF1_FILTER_GAIN_A,
+ NOLACE_AF1_FILTER_GAIN_B,
+ NOLACE_AF1_SHAPE_GAIN
+ );
+
+
+ printf("testing nolace.af4 (2 in, 1 out)...\n");
+ adaconv_compare(
+ "testvectors/nolace_af4",
+ 5,
+ &hAdaConv,
+ &hNoLACE.nolace_af4_kernel,
+ &hNoLACE.nolace_af4_gain,
+ NOLACE_AF4_FEATURE_DIM,
+ NOLACE_AF4_FRAME_SIZE,
+ NOLACE_AF4_OVERLAP_SIZE,
+ NOLACE_AF4_IN_CHANNELS,
+ NOLACE_AF4_OUT_CHANNELS,
+ NOLACE_AF4_KERNEL_SIZE,
+ NOLACE_AF4_LEFT_PADDING,
+ NOLACE_AF4_FILTER_GAIN_A,
+ NOLACE_AF4_FILTER_GAIN_B,
+ NOLACE_AF4_SHAPE_GAIN
+ );
+
+ printf("\ntesting nolace.af2 (2 in, 2 out)...\n");
+ adaconv_compare(
+ "testvectors/nolace_af2",
+ 5,
+ &hAdaConv,
+ &hNoLACE.nolace_af2_kernel,
+ &hNoLACE.nolace_af2_gain,
+ NOLACE_AF2_FEATURE_DIM,
+ NOLACE_AF2_FRAME_SIZE,
+ NOLACE_AF2_OVERLAP_SIZE,
+ NOLACE_AF2_IN_CHANNELS,
+ NOLACE_AF2_OUT_CHANNELS,
+ NOLACE_AF2_KERNEL_SIZE,
+ NOLACE_AF2_LEFT_PADDING,
+ NOLACE_AF2_FILTER_GAIN_A,
+ NOLACE_AF2_FILTER_GAIN_B,
+ NOLACE_AF2_SHAPE_GAIN
+ );
+
+ printf("\ntesting lace.cf1...\n");
+ adacomb_compare(
+ "testvectors/lace_cf1",
+ 5,
+ &hAdaComb,
+ &hLACE.lace_cf1_kernel,
+ &hLACE.lace_cf1_gain,
+ &hLACE.lace_cf1_global_gain,
+ LACE_CF1_FEATURE_DIM,
+ LACE_CF1_FRAME_SIZE,
+ LACE_CF1_OVERLAP_SIZE,
+ LACE_CF1_KERNEL_SIZE,
+ LACE_CF1_LEFT_PADDING,
+ LACE_CF1_FILTER_GAIN_A,
+ LACE_CF1_FILTER_GAIN_B,
+ LACE_CF1_LOG_GAIN_LIMIT
+ );
+
+ printf("\ntesting nolace.tdshape1...\n");
+ adashape_compare(
+ "testvectors/nolace_tdshape1",
+ 5,
+ &hAdaShape,
+ &hNoLACE.nolace_tdshape1_alpha1,
+ &hNoLACE.nolace_tdshape1_alpha2,
+ NOLACE_TDSHAPE1_FEATURE_DIM,
+ NOLACE_TDSHAPE1_FRAME_SIZE,
+ NOLACE_TDSHAPE1_AVG_POOL_K
+ );
+
+ return 0;
+}
+
+/* gcc -DVAR_ARRAYS -DENABLE_OSCE -I ../include -I ../silk -I . -I ../celt adaconvtest.c nndsp.c lace_data.c nolace_data.c nnet.c nnet_default.c ../celt/pitch.c ../celt/celt_lpc.c parse_lpcnet_weights.c -lm -o adaconvtest */ \ No newline at end of file
diff --git a/dnn/arm/arm_dnn_map.c b/dnn/arm/arm_dnn_map.c
new file mode 100644
index 00000000..98a2a312
--- /dev/null
+++ b/dnn/arm/arm_dnn_map.c
@@ -0,0 +1,88 @@
+/* Copyright (c) 2018-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "arm/armcpu.h"
+#include "nnet.h"
+
+#if defined(OPUS_HAVE_RTCD)
+
+#if (defined(OPUS_ARM_MAY_HAVE_DOTPROD) && !defined(OPUS_ARM_PRESUME_DOTPROD))
+
+void (*const DNN_COMPUTE_LINEAR_IMPL[OPUS_ARCHMASK + 1])(
+ const LinearLayer *linear,
+ float *out,
+ const float *in
+) = {
+ compute_linear_c, /* default */
+ compute_linear_c,
+ compute_linear_c,
+ MAY_HAVE_NEON(compute_linear), /* neon */
+ MAY_HAVE_DOTPROD(compute_linear) /* dotprod */
+};
+
+#endif
+
+#if (defined(OPUS_ARM_MAY_HAVE_DOTPROD) || defined(OPUS_ARM_MAY_HAVE_NEON)) && !defined(OPUS_ARM_PRESUME_NEON)
+
+void (*const DNN_COMPUTE_ACTIVATION_IMPL[OPUS_ARCHMASK + 1])(
+ float *output,
+ const float *input,
+ int N,
+ int activation
+) = {
+ compute_activation_c, /* default */
+ compute_activation_c,
+ compute_activation_c,
+ MAY_HAVE_NEON(compute_activation), /* neon */
+ MAY_HAVE_DOTPROD(compute_activation) /* dotprod */
+};
+
+void (*const DNN_COMPUTE_CONV2D_IMPL[OPUS_ARCHMASK + 1])(
+ const Conv2dLayer *conv,
+ float *out,
+ float *mem,
+ const float *in,
+ int height,
+ int hstride,
+ int activation
+) = {
+ compute_conv2d_c, /* default */
+ compute_conv2d_c,
+ compute_conv2d_c,
+ MAY_HAVE_NEON(compute_conv2d), /* neon */
+ MAY_HAVE_DOTPROD(compute_conv2d) /* dotprod */
+};
+
+
+#endif
+
+
+#endif
diff --git a/dnn/arm/dnn_arm.h b/dnn/arm/dnn_arm.h
new file mode 100644
index 00000000..d7ac7452
--- /dev/null
+++ b/dnn/arm/dnn_arm.h
@@ -0,0 +1,104 @@
+/* Copyright (c) 2011-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifndef DNN_ARM_H
+#define DNN_ARM_H
+
+#include "cpu_support.h"
+#include "opus_types.h"
+
+void compute_linear_dotprod(const LinearLayer *linear, float *out, const float *in);
+void compute_linear_neon(const LinearLayer *linear, float *out, const float *in);
+
+void compute_activation_neon(float *output, const float *input, int N, int activation);
+void compute_activation_dotprod(float *output, const float *input, int N, int activation);
+
+void compute_conv2d_neon(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation);
+void compute_conv2d_dotprod(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation);
+
+#if defined(OPUS_ARM_PRESUME_DOTPROD)
+
+#define OVERRIDE_COMPUTE_LINEAR
+#define compute_linear(linear, out, in, arch) ((void)(arch),compute_linear_dotprod(linear, out, in))
+
+#elif defined(OPUS_ARM_PRESUME_NEON_INTR) && !defined(OPUS_ARM_MAY_HAVE_DOTPROD)
+
+#define OVERRIDE_COMPUTE_LINEAR
+#define compute_linear(linear, out, in, arch) ((void)(arch),compute_linear_neon(linear, out, in))
+
+#elif defined(OPUS_HAVE_RTCD) && (defined(OPUS_ARM_MAY_HAVE_DOTPROD) || defined(OPUS_ARM_MAY_HAVE_NEON))
+
+extern void (*const DNN_COMPUTE_LINEAR_IMPL[OPUS_ARCHMASK + 1])(
+ const LinearLayer *linear,
+ float *out,
+ const float *in
+ );
+#define OVERRIDE_COMPUTE_LINEAR
+#define compute_linear(linear, out, in, arch) \
+ ((*DNN_COMPUTE_LINEAR_IMPL[(arch) & OPUS_ARCHMASK])(linear, out, in))
+
+
+#endif
+
+#if defined(OPUS_ARM_PRESUME_NEON)
+
+#define OVERRIDE_COMPUTE_ACTIVATION
+#define compute_activation(output, input, N, activation, arch) ((void)(arch),compute_activation_neon(output, input, N, activation))
+#define OVERRIDE_COMPUTE_CONV2D
+#define compute_conv2d(conv, out, mem, in, height, hstride, activation, arch) ((void)(arch),compute_conv2d_neon(conv, out, mem, in, height, hstride, activation))
+
+#elif defined(OPUS_HAVE_RTCD) && (defined(OPUS_ARM_MAY_HAVE_DOTPROD) || defined(OPUS_ARM_MAY_HAVE_NEON))
+
+extern void (*const DNN_COMPUTE_ACTIVATION_IMPL[OPUS_ARCHMASK + 1])(
+ float *output,
+ const float *input,
+ int N,
+ int activation
+ );
+#define OVERRIDE_COMPUTE_ACTIVATION
+#define compute_activation(output, input, N, activation, arch) \
+ ((*DNN_COMPUTE_ACTIVATION_IMPL[(arch) & OPUS_ARCHMASK])(output, input, N, activation))
+
+
+extern void (*const DNN_COMPUTE_CONV2D_IMPL[OPUS_ARCHMASK + 1])(
+ const Conv2dLayer *conv,
+ float *out,
+ float *mem,
+ const float *in,
+ int height,
+ int hstride,
+ int activation
+ );
+#define OVERRIDE_COMPUTE_CONV2D
+#define compute_conv2d(conv, out, mem, in, height, hstride, activation, arch) \
+ ((*DNN_COMPUTE_CONV2D_IMPL[(arch) & OPUS_ARCHMASK])(conv, out, mem, in, height, hstride, activation))
+
+
+#endif
+
+
+#endif /* DNN_ARM_H */
diff --git a/dnn/arm/nnet_dotprod.c b/dnn/arm/nnet_dotprod.c
new file mode 100644
index 00000000..1354ed33
--- /dev/null
+++ b/dnn/arm/nnet_dotprod.c
@@ -0,0 +1,38 @@
+/* Copyright (c) 2018-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef __ARM_FEATURE_DOTPROD
+#error nnet_dotprod.c is being compiled without DOTPROD enabled
+#endif
+
+#define RTCD_ARCH dotprod
+
+#include "nnet_arch.h"
diff --git a/dnn/arm/nnet_neon.c b/dnn/arm/nnet_neon.c
new file mode 100644
index 00000000..fb636f85
--- /dev/null
+++ b/dnn/arm/nnet_neon.c
@@ -0,0 +1,38 @@
+/* Copyright (c) 2018-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if !(defined(__ARM_NEON__) || defined(__ARM_NEON))
+#error nnet_neon.c is being compiled without Neon enabled
+#endif
+
+#define RTCD_ARCH neon
+
+#include "nnet_arch.h"
diff --git a/dnn/burg.c b/dnn/burg.c
new file mode 100644
index 00000000..3d6b23b0
--- /dev/null
+++ b/dnn/burg.c
@@ -0,0 +1,246 @@
+/***********************************************************************
+Copyright (c) 2006-2011, Skype Limited. 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 Internet Society, IETF or IETF Trust, nor the
+names of specific 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 THE COPYRIGHT OWNER 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.
+***********************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <string.h>
+#include <assert.h>
+
+#include "arch.h"
+#include "burg.h"
+
+#define MAX_FRAME_SIZE 384 /* subfr_length * nb_subfr = ( 0.005 * 16000 + 16 ) * 4 = 384*/
+#define SILK_MAX_ORDER_LPC 16
+#define FIND_LPC_COND_FAC 1e-5f
+
+/* sum of squares of a silk_float array, with result as double */
+static double silk_energy_FLP(
+ const float *data,
+ int dataSize
+)
+{
+ int i;
+ double result;
+
+ /* 4x unrolled loop */
+ result = 0.0;
+ for( i = 0; i < dataSize - 3; i += 4 ) {
+ result += data[ i + 0 ] * (double)data[ i + 0 ] +
+ data[ i + 1 ] * (double)data[ i + 1 ] +
+ data[ i + 2 ] * (double)data[ i + 2 ] +
+ data[ i + 3 ] * (double)data[ i + 3 ];
+ }
+
+ /* add any remaining products */
+ for( ; i < dataSize; i++ ) {
+ result += data[ i ] * (double)data[ i ];
+ }
+
+ assert( result >= 0.0 );
+ return result;
+}
+
+/* inner product of two silk_float arrays, with result as double */
+static double silk_inner_product_FLP(
+ const float *data1,
+ const float *data2,
+ int dataSize
+)
+{
+ int i;
+ double result;
+
+ /* 4x unrolled loop */
+ result = 0.0;
+ for( i = 0; i < dataSize - 3; i += 4 ) {
+ result += data1[ i + 0 ] * (double)data2[ i + 0 ] +
+ data1[ i + 1 ] * (double)data2[ i + 1 ] +
+ data1[ i + 2 ] * (double)data2[ i + 2 ] +
+ data1[ i + 3 ] * (double)data2[ i + 3 ];
+ }
+
+ /* add any remaining products */
+ for( ; i < dataSize; i++ ) {
+ result += data1[ i ] * (double)data2[ i ];
+ }
+
+ return result;
+}
+
+
+/* Compute reflection coefficients from input signal */
+float silk_burg_analysis( /* O returns residual energy */
+ float A[], /* O prediction coefficients (length order) */
+ const float x[], /* I input signal, length: nb_subfr*(D+L_sub) */
+ const float minInvGain, /* I minimum inverse prediction gain */
+ const int subfr_length, /* I input signal subframe length (incl. D preceding samples) */
+ const int nb_subfr, /* I number of subframes stacked in x */
+ const int D /* I order */
+)
+{
+ int k, n, s, reached_max_gain;
+ double C0, invGain, num, nrg_f, nrg_b, rc, Atmp, tmp1, tmp2;
+ const float *x_ptr;
+ double C_first_row[ SILK_MAX_ORDER_LPC ], C_last_row[ SILK_MAX_ORDER_LPC ];
+ double CAf[ SILK_MAX_ORDER_LPC + 1 ], CAb[ SILK_MAX_ORDER_LPC + 1 ];
+ double Af[ SILK_MAX_ORDER_LPC ];
+
+ assert( subfr_length * nb_subfr <= MAX_FRAME_SIZE );
+
+ /* Compute autocorrelations, added over subframes */
+ C0 = silk_energy_FLP( x, nb_subfr * subfr_length );
+ memset( C_first_row, 0, SILK_MAX_ORDER_LPC * sizeof( double ) );
+ for( s = 0; s < nb_subfr; s++ ) {
+ x_ptr = x + s * subfr_length;
+ for( n = 1; n < D + 1; n++ ) {
+ C_first_row[ n - 1 ] += silk_inner_product_FLP( x_ptr, x_ptr + n, subfr_length - n );
+ }
+ }
+ memcpy( C_last_row, C_first_row, SILK_MAX_ORDER_LPC * sizeof( double ) );
+
+ /* Initialize */
+ CAb[ 0 ] = CAf[ 0 ] = C0 + FIND_LPC_COND_FAC * C0 + 1e-9f;
+ invGain = 1.0f;
+ reached_max_gain = 0;
+ for( n = 0; n < D; n++ ) {
+ /* Update first row of correlation matrix (without first element) */
+ /* Update last row of correlation matrix (without last element, stored in reversed order) */
+ /* Update C * Af */
+ /* Update C * flipud(Af) (stored in reversed order) */
+ for( s = 0; s < nb_subfr; s++ ) {
+ x_ptr = x + s * subfr_length;
+ tmp1 = x_ptr[ n ];
+ tmp2 = x_ptr[ subfr_length - n - 1 ];
+ for( k = 0; k < n; k++ ) {
+ C_first_row[ k ] -= x_ptr[ n ] * x_ptr[ n - k - 1 ];
+ C_last_row[ k ] -= x_ptr[ subfr_length - n - 1 ] * x_ptr[ subfr_length - n + k ];
+ Atmp = Af[ k ];
+ tmp1 += x_ptr[ n - k - 1 ] * Atmp;
+ tmp2 += x_ptr[ subfr_length - n + k ] * Atmp;
+ }
+ for( k = 0; k <= n; k++ ) {
+ CAf[ k ] -= tmp1 * x_ptr[ n - k ];
+ CAb[ k ] -= tmp2 * x_ptr[ subfr_length - n + k - 1 ];
+ }
+ }
+ tmp1 = C_first_row[ n ];
+ tmp2 = C_last_row[ n ];
+ for( k = 0; k < n; k++ ) {
+ Atmp = Af[ k ];
+ tmp1 += C_last_row[ n - k - 1 ] * Atmp;
+ tmp2 += C_first_row[ n - k - 1 ] * Atmp;
+ }
+ CAf[ n + 1 ] = tmp1;
+ CAb[ n + 1 ] = tmp2;
+
+ /* Calculate nominator and denominator for the next order reflection (parcor) coefficient */
+ num = CAb[ n + 1 ];
+ nrg_b = CAb[ 0 ];
+ nrg_f = CAf[ 0 ];
+ for( k = 0; k < n; k++ ) {
+ Atmp = Af[ k ];
+ num += CAb[ n - k ] * Atmp;
+ nrg_b += CAb[ k + 1 ] * Atmp;
+ nrg_f += CAf[ k + 1 ] * Atmp;
+ }
+ assert( nrg_f > 0.0 );
+ assert( nrg_b > 0.0 );
+
+ /* Calculate the next order reflection (parcor) coefficient */
+ rc = -2.0 * num / ( nrg_f + nrg_b );
+ assert( rc > -1.0 && rc < 1.0 );
+
+ /* Update inverse prediction gain */
+ tmp1 = invGain * ( 1.0 - rc * rc );
+ if( tmp1 <= minInvGain ) {
+ /* Max prediction gain exceeded; set reflection coefficient such that max prediction gain is exactly hit */
+ rc = sqrt( 1.0 - minInvGain / invGain );
+ if( num > 0 ) {
+ /* Ensure adjusted reflection coefficients has the original sign */
+ rc = -rc;
+ }
+ invGain = minInvGain;
+ reached_max_gain = 1;
+ } else {
+ invGain = tmp1;
+ }
+
+ /* Update the AR coefficients */
+ for( k = 0; k < (n + 1) >> 1; k++ ) {
+ tmp1 = Af[ k ];
+ tmp2 = Af[ n - k - 1 ];
+ Af[ k ] = tmp1 + rc * tmp2;
+ Af[ n - k - 1 ] = tmp2 + rc * tmp1;
+ }
+ Af[ n ] = rc;
+
+ if( reached_max_gain ) {
+ /* Reached max prediction gain; set remaining coefficients to zero and exit loop */
+ for( k = n + 1; k < D; k++ ) {
+ Af[ k ] = 0.0;
+ }
+ break;
+ }
+
+ /* Update C * Af and C * Ab */
+ for( k = 0; k <= n + 1; k++ ) {
+ tmp1 = CAf[ k ];
+ CAf[ k ] += rc * CAb[ n - k + 1 ];
+ CAb[ n - k + 1 ] += rc * tmp1;
+ }
+ }
+
+ if( reached_max_gain ) {
+ /* Convert to float */
+ for( k = 0; k < D; k++ ) {
+ A[ k ] = (float)( -Af[ k ] );
+ }
+ /* Subtract energy of preceding samples from C0 */
+ for( s = 0; s < nb_subfr; s++ ) {
+ C0 -= silk_energy_FLP( x + s * subfr_length, D );
+ }
+ /* Approximate residual energy */
+ nrg_f = C0 * invGain;
+ } else {
+ /* Compute residual energy and store coefficients as float */
+ nrg_f = CAf[ 0 ];
+ tmp1 = 1.0;
+ for( k = 0; k < D; k++ ) {
+ Atmp = Af[ k ];
+ nrg_f += CAf[ k + 1 ] * Atmp;
+ tmp1 += Atmp * Atmp;
+ A[ k ] = (float)(-Atmp);
+ }
+ nrg_f -= FIND_LPC_COND_FAC * C0 * tmp1;
+ }
+
+ /* Return residual energy */
+ return MAX32(0, (float)nrg_f);
+}
diff --git a/win32/config.h b/dnn/burg.h
index 3e54bcbb..f5998d2f 100644
--- a/win32/config.h
+++ b/dnn/burg.h
@@ -1,5 +1,5 @@
/***********************************************************************
-Copyright (c) 2011, Skype Limited. All rights reserved.
+Copyright (c) 2006-2011, Skype Limited. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
@@ -25,40 +25,17 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
***********************************************************************/
-#ifndef CONFIG_H
-#define CONFIG_H
+#ifndef BURG_H
+#define BURG_H
-#define USE_ALLOCA 1
-/* Comment out the next line for floating-point code */
-/*#define FIXED_POINT 1 */
-
-#define OPUS_BUILD 1
-
-#if defined(_M_IX86) || defined(_M_X64)
-/* Can always compile SSE intrinsics (no special compiler flags necessary) */
-#define OPUS_X86_MAY_HAVE_SSE
-#define OPUS_X86_MAY_HAVE_SSE2
-#define OPUS_X86_MAY_HAVE_SSE4_1
-
-/* Presume SSE functions, if compiled to use SSE/SSE2/AVX (note that AMD64 implies SSE2, and AVX
- implies SSE4.1) */
-#if defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1)) || defined(__AVX__)
-#define OPUS_X86_PRESUME_SSE 1
-#endif
-#if defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 2)) || defined(__AVX__)
-#define OPUS_X86_PRESUME_SSE2 1
-#endif
-#if defined(__AVX__)
-#define OPUS_X86_PRESUME_SSE4_1 1
-#endif
-
-#if !defined(OPUS_X86_PRESUME_SSE4_1) || !defined(OPUS_X86_PRESUME_SSE2) || !defined(OPUS_X86_PRESUME_SSE)
-#define OPUS_HAVE_RTCD 1
-#endif
+float silk_burg_analysis( /* O returns residual energy */
+ float A[], /* O prediction coefficients (length order) */
+ const float x[], /* I input signal, length: nb_subfr*(D+L_sub) */
+ const float minInvGain, /* I minimum inverse prediction gain */
+ const int subfr_length, /* I input signal subframe length (incl. D preceding samples) */
+ const int nb_subfr, /* I number of subframes stacked in x */
+ const int D /* I order */
+);
#endif
-
-#include "version.h"
-
-#endif /* CONFIG_H */
diff --git a/dnn/common.h b/dnn/common.h
new file mode 100644
index 00000000..b3bec730
--- /dev/null
+++ b/dnn/common.h
@@ -0,0 +1,56 @@
+
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "opus_defines.h"
+
+#define LOG256 5.5451774445f
+static OPUS_INLINE float log2_approx(float x)
+{
+ int integer;
+ float frac;
+ union {
+ float f;
+ int i;
+ } in;
+ in.f = x;
+ integer = (in.i>>23)-127;
+ in.i -= integer<<23;
+ frac = in.f - 1.5f;
+ frac = -0.41445418f + frac*(0.95909232f
+ + frac*(-0.33951290f + frac*0.16541097f));
+ return 1+integer+frac;
+}
+
+#define log_approx(x) (0.69315f*log2_approx(x))
+
+static OPUS_INLINE float ulaw2lin(float u)
+{
+ float s;
+ float scale_1 = 32768.f/255.f;
+ u = u - 128.f;
+ s = u >= 0.f ? 1.f : -1.f;
+ u = fabs(u);
+ return s*scale_1*(exp(u/128.*LOG256)-1);
+}
+
+static OPUS_INLINE int lin2ulaw(float x)
+{
+ float u;
+ float scale = 255.f/32768.f;
+ int s = x >= 0 ? 1 : -1;
+ x = fabs(x);
+ u = (s*(128*log_approx(1+scale*x)/LOG256));
+ u = 128 + u;
+ if (u < 0) u = 0;
+ if (u > 255) u = 255;
+ return (int)floor(.5 + u);
+}
+
+
+
+#endif
diff --git a/dnn/datasets.txt b/dnn/datasets.txt
new file mode 100644
index 00000000..00445216
--- /dev/null
+++ b/dnn/datasets.txt
@@ -0,0 +1,173 @@
+The following datasets can be used to train a language-independent LPCNet model.
+A good choice is to include all the data from these datasets, except for
+hi_fi_tts for which only a small subset is recommended (since it's very large
+but has few speakers). Note that this data typically needs to be resampled
+before it can be used.
+
+https://www.openslr.org/resources/30/si_lk.tar.gz
+https://www.openslr.org/resources/32/af_za.tar.gz
+https://www.openslr.org/resources/32/st_za.tar.gz
+https://www.openslr.org/resources/32/tn_za.tar.gz
+https://www.openslr.org/resources/32/xh_za.tar.gz
+https://www.openslr.org/resources/37/bn_bd.zip
+https://www.openslr.org/resources/37/bn_in.zip
+https://www.openslr.org/resources/41/jv_id_female.zip
+https://www.openslr.org/resources/41/jv_id_male.zip
+https://www.openslr.org/resources/42/km_kh_male.zip
+https://www.openslr.org/resources/43/ne_np_female.zip
+https://www.openslr.org/resources/44/su_id_female.zip
+https://www.openslr.org/resources/44/su_id_male.zip
+https://www.openslr.org/resources/61/es_ar_female.zip
+https://www.openslr.org/resources/61/es_ar_male.zip
+https://www.openslr.org/resources/63/ml_in_female.zip
+https://www.openslr.org/resources/63/ml_in_male.zip
+https://www.openslr.org/resources/64/mr_in_female.zip
+https://www.openslr.org/resources/65/ta_in_female.zip
+https://www.openslr.org/resources/65/ta_in_male.zip
+https://www.openslr.org/resources/66/te_in_female.zip
+https://www.openslr.org/resources/66/te_in_male.zip
+https://www.openslr.org/resources/69/ca_es_female.zip
+https://www.openslr.org/resources/69/ca_es_male.zip
+https://www.openslr.org/resources/70/en_ng_female.zip
+https://www.openslr.org/resources/70/en_ng_male.zip
+https://www.openslr.org/resources/71/es_cl_female.zip
+https://www.openslr.org/resources/71/es_cl_male.zip
+https://www.openslr.org/resources/72/es_co_female.zip
+https://www.openslr.org/resources/72/es_co_male.zip
+https://www.openslr.org/resources/73/es_pe_female.zip
+https://www.openslr.org/resources/73/es_pe_male.zip
+https://www.openslr.org/resources/74/es_pr_female.zip
+https://www.openslr.org/resources/75/es_ve_female.zip
+https://www.openslr.org/resources/75/es_ve_male.zip
+https://www.openslr.org/resources/76/eu_es_female.zip
+https://www.openslr.org/resources/76/eu_es_male.zip
+https://www.openslr.org/resources/77/gl_es_female.zip
+https://www.openslr.org/resources/77/gl_es_male.zip
+https://www.openslr.org/resources/78/gu_in_female.zip
+https://www.openslr.org/resources/78/gu_in_male.zip
+https://www.openslr.org/resources/79/kn_in_female.zip
+https://www.openslr.org/resources/79/kn_in_male.zip
+https://www.openslr.org/resources/80/my_mm_female.zip
+https://www.openslr.org/resources/83/irish_english_male.zip
+https://www.openslr.org/resources/83/midlands_english_female.zip
+https://www.openslr.org/resources/83/midlands_english_male.zip
+https://www.openslr.org/resources/83/northern_english_female.zip
+https://www.openslr.org/resources/83/northern_english_male.zip
+https://www.openslr.org/resources/83/scottish_english_female.zip
+https://www.openslr.org/resources/83/scottish_english_male.zip
+https://www.openslr.org/resources/83/southern_english_female.zip
+https://www.openslr.org/resources/83/southern_english_male.zip
+https://www.openslr.org/resources/83/welsh_english_female.zip
+https://www.openslr.org/resources/83/welsh_english_male.zip
+https://www.openslr.org/resources/86/yo_ng_female.zip
+https://www.openslr.org/resources/86/yo_ng_male.zip
+https://www.openslr.org/resources/109/hi_fi_tts_v0.tar.gz
+
+The corresponding citations for all these datasets are:
+
+ @inproceedings{demirsahin-etal-2020-open,
+ title = {{Open-source Multi-speaker Corpora of the English Accents in the British Isles}},
+ author = {Demirsahin, Isin and Kjartansson, Oddur and Gutkin, Alexander and Rivera, Clara},
+ booktitle = {Proceedings of The 12th Language Resources and Evaluation Conference (LREC)},
+ month = may,
+ year = {2020},
+ pages = {6532--6541},
+ address = {Marseille, France},
+ publisher = {European Language Resources Association (ELRA)},
+ url = {https://www.aclweb.org/anthology/2020.lrec-1.804},
+ ISBN = {979-10-95546-34-4},
+ }
+ @inproceedings{kjartansson-etal-2020-open,
+ title = {{Open-Source High Quality Speech Datasets for Basque, Catalan and Galician}},
+ author = {Kjartansson, Oddur and Gutkin, Alexander and Butryna, Alena and Demirsahin, Isin and Rivera, Clara},
+ booktitle = {Proceedings of the 1st Joint Workshop on Spoken Language Technologies for Under-resourced languages (SLTU) and Collaboration and Computing for Under-Resourced Languages (CCURL)},
+ year = {2020},
+ pages = {21--27},
+ month = may,
+ address = {Marseille, France},
+ publisher = {European Language Resources association (ELRA)},
+ url = {https://www.aclweb.org/anthology/2020.sltu-1.3},
+ ISBN = {979-10-95546-35-1},
+ }
+
+
+ @inproceedings{guevara-rukoz-etal-2020-crowdsourcing,
+ title = {{Crowdsourcing Latin American Spanish for Low-Resource Text-to-Speech}},
+ author = {Guevara-Rukoz, Adriana and Demirsahin, Isin and He, Fei and Chu, Shan-Hui Cathy and Sarin, Supheakmungkol and Pipatsrisawat, Knot and Gutkin, Alexander and Butryna, Alena and Kjartansson, Oddur},
+ booktitle = {Proceedings of The 12th Language Resources and Evaluation Conference (LREC)},
+ year = {2020},
+ month = may,
+ address = {Marseille, France},
+ publisher = {European Language Resources Association (ELRA)},
+ url = {https://www.aclweb.org/anthology/2020.lrec-1.801},
+ pages = {6504--6513},
+ ISBN = {979-10-95546-34-4},
+ }
+ @inproceedings{he-etal-2020-open,
+ title = {{Open-source Multi-speaker Speech Corpora for Building Gujarati, Kannada, Malayalam, Marathi, Tamil and Telugu Speech Synthesis Systems}},
+ author = {He, Fei and Chu, Shan-Hui Cathy and Kjartansson, Oddur and Rivera, Clara and Katanova, Anna and Gutkin, Alexander and Demirsahin, Isin and Johny, Cibu and Jansche, Martin and Sarin, Supheakmungkol and Pipatsrisawat, Knot},
+ booktitle = {Proceedings of The 12th Language Resources and Evaluation Conference (LREC)},
+ month = may,
+ year = {2020},
+ address = {Marseille, France},
+ publisher = {European Language Resources Association (ELRA)},
+ pages = {6494--6503},
+ url = {https://www.aclweb.org/anthology/2020.lrec-1.800},
+ ISBN = "{979-10-95546-34-4}",
+ }
+
+
+ @inproceedings{kjartansson-etal-tts-sltu2018,
+ title = {{A Step-by-Step Process for Building TTS Voices Using Open Source Data and Framework for Bangla, Javanese, Khmer, Nepali, Sinhala, and Sundanese}},
+ author = {Keshan Sodimana and Knot Pipatsrisawat and Linne Ha and Martin Jansche and Oddur Kjartansson and Pasindu De Silva and Supheakmungkol Sarin},
+ booktitle = {Proc. The 6th Intl. Workshop on Spoken Language Technologies for Under-Resourced Languages (SLTU)},
+ year = {2018},
+ address = {Gurugram, India},
+ month = aug,
+ pages = {66--70},
+ URL = {http://dx.doi.org/10.21437/SLTU.2018-14}
+ }
+
+
+ @inproceedings{oo-etal-2020-burmese,
+ title = {{Burmese Speech Corpus, Finite-State Text Normalization and Pronunciation Grammars with an Application to Text-to-Speech}},
+ author = {Oo, Yin May and Wattanavekin, Theeraphol and Li, Chenfang and De Silva, Pasindu and Sarin, Supheakmungkol and Pipatsrisawat, Knot and Jansche, Martin and Kjartansson, Oddur and Gutkin, Alexander},
+ booktitle = {Proceedings of The 12th Language Resources and Evaluation Conference (LREC)},
+ month = may,
+ year = {2020},
+ pages = "6328--6339",
+ address = {Marseille, France},
+ publisher = {European Language Resources Association (ELRA)},
+ url = {https://www.aclweb.org/anthology/2020.lrec-1.777},
+ ISBN = {979-10-95546-34-4},
+ }
+ @inproceedings{van-niekerk-etal-2017,
+ title = {{Rapid development of TTS corpora for four South African languages}},
+ author = {Daniel van Niekerk and Charl van Heerden and Marelie Davel and Neil Kleynhans and Oddur Kjartansson and Martin Jansche and Linne Ha},
+ booktitle = {Proc. Interspeech 2017},
+ pages = {2178--2182},
+ address = {Stockholm, Sweden},
+ month = aug,
+ year = {2017},
+ URL = {http://dx.doi.org/10.21437/Interspeech.2017-1139}
+ }
+
+ @inproceedings{gutkin-et-al-yoruba2020,
+ title = {{Developing an Open-Source Corpus of Yoruba Speech}},
+ author = {Alexander Gutkin and I{\c{s}}{\i}n Demir{\c{s}}ahin and Oddur Kjartansson and Clara Rivera and K\d{\'o}lá Túb\d{\`o}sún},
+ booktitle = {Proceedings of Interspeech 2020},
+ pages = {404--408},
+ month = {October},
+ year = {2020},
+ address = {Shanghai, China},
+ publisher = {International Speech and Communication Association (ISCA)},
+ doi = {10.21437/Interspeech.2020-1096},
+ url = {http://dx.doi.org/10.21437/Interspeech.2020-1096},
+ }
+
+@article{bakhturina2021hi,
+ title={{Hi-Fi Multi-Speaker English TTS Dataset}},
+ author={Bakhturina, Evelina and Lavrukhin, Vitaly and Ginsburg, Boris and Zhang, Yang},
+ journal={arXiv preprint arXiv:2104.01497},
+ year={2021}
+}
diff --git a/dnn/download_model.bat b/dnn/download_model.bat
new file mode 100644
index 00000000..f49eaa59
--- /dev/null
+++ b/dnn/download_model.bat
@@ -0,0 +1,9 @@
+@echo off
+set model=opus_data-%1.tar.gz
+
+if not exist %model% (
+ echo Downloading latest model
+ powershell -Command "(New-Object System.Net.WebClient).DownloadFile('https://media.xiph.org/opus/models/%model%', '%model%')"
+)
+
+tar -xvzf %model%
diff --git a/dnn/download_model.sh b/dnn/download_model.sh
new file mode 100755
index 00000000..127441de
--- /dev/null
+++ b/dnn/download_model.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+set -e
+
+model=opus_data-$1.tar.gz
+
+if [ ! -f $model ]; then
+ echo "Downloading latest model"
+ wget https://media.xiph.org/opus/models/$model
+fi
+tar xvomf $model
diff --git a/dnn/dred_coding.c b/dnn/dred_coding.c
new file mode 100644
index 00000000..669ddc41
--- /dev/null
+++ b/dnn/dred_coding.c
@@ -0,0 +1,44 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jean-Marc Valin */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+
+#include "celt/entenc.h"
+#include "os_support.h"
+#include "dred_config.h"
+#include "dred_coding.h"
+
+int compute_quantizer(int q0, int dQ, int qmax, int i) {
+ int quant;
+ static const int dQ_table[8] = {0, 2, 3, 4, 6, 8, 12, 16};
+ quant = q0 + (dQ_table[dQ]*i + 8)/16;
+ return quant > qmax ? qmax : quant;
+}
diff --git a/dnn/dred_coding.h b/dnn/dred_coding.h
new file mode 100644
index 00000000..1ce040c2
--- /dev/null
+++ b/dnn/dred_coding.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef DRED_CODING_H
+#define DRED_CODING_H
+
+#include "opus_types.h"
+#include "entcode.h"
+
+int compute_quantizer(int q0, int dQ, int qmax, int i);
+
+#endif
diff --git a/dnn/dred_config.h b/dnn/dred_config.h
new file mode 100644
index 00000000..88b2261f
--- /dev/null
+++ b/dnn/dred_config.h
@@ -0,0 +1,54 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef DRED_CONFIG_H
+#define DRED_CONFIG_H
+
+/* Change this once DRED gets an extension number assigned. */
+#define DRED_EXTENSION_ID 126
+
+/* Remove these two completely once DRED gets an extension number assigned. */
+#define DRED_EXPERIMENTAL_VERSION 10
+#define DRED_EXPERIMENTAL_BYTES 2
+
+
+#define DRED_MIN_BYTES 8
+
+/* these are inpart duplicates to the values defined in dred_rdovae_constants.h */
+#define DRED_SILK_ENCODER_DELAY (79+12-80)
+#define DRED_FRAME_SIZE 160
+#define DRED_DFRAME_SIZE (2 * (DRED_FRAME_SIZE))
+#define DRED_MAX_DATA_SIZE 1000
+#define DRED_ENC_Q0 6
+#define DRED_ENC_Q1 15
+
+/* Covers 1.04 second so we can cover one second, after the lookahead. */
+#define DRED_MAX_LATENTS 26
+#define DRED_NUM_REDUNDANCY_FRAMES (2*DRED_MAX_LATENTS)
+#define DRED_MAX_FRAMES (4*DRED_MAX_LATENTS)
+
+#endif
diff --git a/dnn/dred_decoder.c b/dnn/dred_decoder.c
new file mode 100644
index 00000000..1b284330
--- /dev/null
+++ b/dnn/dred_decoder.c
@@ -0,0 +1,129 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "os_support.h"
+#include "dred_decoder.h"
+#include "dred_coding.h"
+#include "celt/entdec.h"
+#include "celt/laplace.h"
+#include "dred_rdovae_stats_data.h"
+#include "dred_rdovae_constants.h"
+
+static void dred_decode_latents(ec_dec *dec, float *x, const opus_uint8 *scale, const opus_uint8 *r, const opus_uint8 *p0, int dim) {
+ int i;
+ for (i=0;i<dim;i++) {
+ int q;
+ if (r[i] == 0 || p0[i] == 255) q = 0;
+ else q = ec_laplace_decode_p0(dec, p0[i]<<7, r[i]<<7);
+ x[i] = q*256.f/(scale[i] == 0 ? 1 : scale[i]);
+ }
+}
+
+int dred_ec_decode(OpusDRED *dec, const opus_uint8 *bytes, int num_bytes, int min_feature_frames, int dred_frame_offset)
+{
+ ec_dec ec;
+ int q_level;
+ int i;
+ int offset;
+ int q0;
+ int dQ;
+ int qmax;
+ int state_qoffset;
+ int extra_offset;
+
+ /* since features are decoded in quadruples, it makes no sense to go with an uneven number of redundancy frames */
+ celt_assert(DRED_NUM_REDUNDANCY_FRAMES % 2 == 0);
+
+ /* decode initial state and initialize RDOVAE decoder */
+ ec_dec_init(&ec, (unsigned char*)bytes, num_bytes);
+ q0 = ec_dec_uint(&ec, 16);
+ dQ = ec_dec_uint(&ec, 8);
+ if (ec_dec_uint(&ec, 2)) extra_offset = 32*ec_dec_uint(&ec, 256);
+ else extra_offset = 0;
+ /* Compute total offset, including DRED position in a multiframe packet. */
+ dec->dred_offset = 16 - ec_dec_uint(&ec, 32) - extra_offset + dred_frame_offset;
+ /*printf("%d %d %d\n", dred_offset, q0, dQ);*/
+ qmax = 15;
+ if (q0 < 14 && dQ > 0) {
+ int nvals;
+ int ft;
+ int s;
+ /* The distribution for the dQmax symbol is split evenly between zero
+ (which implies qmax == 15) and larger values, with the probability of
+ all larger values being uniform.
+ This is equivalent to coding 1 bit to decide if the maximum is less than
+ 15 followed by a uint to decide the actual value if it is less than
+ 15, but combined into a single symbol. */
+ nvals = 15 - (q0 + 1);
+ ft = 2*nvals;
+ s = ec_decode(&ec, ft);
+ if (s >= nvals) {
+ qmax = q0 + (s - nvals) + 1;
+ ec_dec_update(&ec, s, s + 1, ft);
+ }
+ else {
+ ec_dec_update(&ec, 0, nvals, ft);
+ }
+ }
+ state_qoffset = q0*DRED_STATE_DIM;
+ dred_decode_latents(
+ &ec,
+ dec->state,
+ dred_state_quant_scales_q8 + state_qoffset,
+ dred_state_r_q8 + state_qoffset,
+ dred_state_p0_q8 + state_qoffset,
+ DRED_STATE_DIM);
+
+ /* decode newest to oldest and store oldest to newest */
+ for (i = 0; i < IMIN(DRED_NUM_REDUNDANCY_FRAMES, (min_feature_frames+1)/2); i += 2)
+ {
+ /* FIXME: Figure out how to avoid missing a last frame that would take up < 8 bits. */
+ if (8*num_bytes - ec_tell(&ec) <= 7)
+ break;
+ q_level = compute_quantizer(q0, dQ, qmax, i/2);
+ offset = q_level*DRED_LATENT_DIM;
+ dred_decode_latents(
+ &ec,
+ &dec->latents[(i/2)*DRED_LATENT_DIM],
+ dred_latent_quant_scales_q8 + offset,
+ dred_latent_r_q8 + offset,
+ dred_latent_p0_q8 + offset,
+ DRED_LATENT_DIM
+ );
+
+ offset = 2 * i * DRED_NUM_FEATURES;
+ }
+ dec->process_stage = 1;
+ dec->nb_latents = i/2;
+ return i/2;
+}
diff --git a/dnn/dred_decoder.h b/dnn/dred_decoder.h
new file mode 100644
index 00000000..dcd8121d
--- /dev/null
+++ b/dnn/dred_decoder.h
@@ -0,0 +1,49 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef DRED_DECODER_H
+#define DRED_DECODER_H
+
+#include "opus.h"
+#include "dred_config.h"
+#include "dred_rdovae.h"
+#include "entcode.h"
+#include "dred_rdovae_constants.h"
+
+struct OpusDRED {
+ float fec_features[2*DRED_NUM_REDUNDANCY_FRAMES*DRED_NUM_FEATURES];
+ float state[DRED_STATE_DIM];
+ float latents[(DRED_NUM_REDUNDANCY_FRAMES/2)*DRED_LATENT_DIM];
+ int nb_latents;
+ int process_stage;
+ int dred_offset;
+};
+
+
+int dred_ec_decode(OpusDRED *dec, const opus_uint8 *bytes, int num_bytes, int min_feature_frames, int dred_frame_offset);
+
+#endif
diff --git a/dnn/dred_encoder.c b/dnn/dred_encoder.c
new file mode 100644
index 00000000..edb49cc2
--- /dev/null
+++ b/dnn/dred_encoder.c
@@ -0,0 +1,363 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#if 0
+#include <stdio.h>
+#include <math.h>
+#endif
+
+#include "dred_encoder.h"
+#include "dred_coding.h"
+#include "celt/entenc.h"
+
+#include "dred_decoder.h"
+#include "float_cast.h"
+#include "os_support.h"
+#include "celt/laplace.h"
+#include "dred_rdovae_stats_data.h"
+
+
+static void DRED_rdovae_init_encoder(RDOVAEEncState *enc_state)
+{
+ memset(enc_state, 0, sizeof(*enc_state));
+}
+
+int dred_encoder_load_model(DREDEnc* enc, const void *data, int len)
+{
+ WeightArray *list;
+ int ret;
+ parse_weights(&list, data, len);
+ ret = init_rdovaeenc(&enc->model, list);
+ opus_free(list);
+ if (ret == 0) {
+ ret = lpcnet_encoder_load_model(&enc->lpcnet_enc_state, data, len);
+ }
+ if (ret == 0) enc->loaded = 1;
+ return (ret == 0) ? OPUS_OK : OPUS_BAD_ARG;
+}
+
+void dred_encoder_reset(DREDEnc* enc)
+{
+ OPUS_CLEAR((char*)&enc->DREDENC_RESET_START,
+ sizeof(DREDEnc)-
+ ((char*)&enc->DREDENC_RESET_START - (char*)enc));
+ enc->input_buffer_fill = DRED_SILK_ENCODER_DELAY;
+ lpcnet_encoder_init(&enc->lpcnet_enc_state);
+ DRED_rdovae_init_encoder(&enc->rdovae_enc);
+}
+
+void dred_encoder_init(DREDEnc* enc, opus_int32 Fs, int channels)
+{
+ enc->Fs = Fs;
+ enc->channels = channels;
+ enc->loaded = 0;
+#ifndef USE_WEIGHTS_FILE
+ if (init_rdovaeenc(&enc->model, rdovaeenc_arrays) == 0) enc->loaded = 1;
+#endif
+ dred_encoder_reset(enc);
+}
+
+static void dred_process_frame(DREDEnc *enc, int arch)
+{
+ float feature_buffer[2 * 36];
+ float input_buffer[2*DRED_NUM_FEATURES] = {0};
+
+ celt_assert(enc->loaded);
+ /* shift latents buffer */
+ OPUS_MOVE(enc->latents_buffer + DRED_LATENT_DIM, enc->latents_buffer, (DRED_MAX_FRAMES - 1) * DRED_LATENT_DIM);
+ OPUS_MOVE(enc->state_buffer + DRED_STATE_DIM, enc->state_buffer, (DRED_MAX_FRAMES - 1) * DRED_STATE_DIM);
+
+ /* calculate LPCNet features */
+ lpcnet_compute_single_frame_features_float(&enc->lpcnet_enc_state, enc->input_buffer, feature_buffer, arch);
+ lpcnet_compute_single_frame_features_float(&enc->lpcnet_enc_state, enc->input_buffer + DRED_FRAME_SIZE, feature_buffer + 36, arch);
+
+ /* prepare input buffer (discard LPC coefficients) */
+ OPUS_COPY(input_buffer, feature_buffer, DRED_NUM_FEATURES);
+ OPUS_COPY(input_buffer + DRED_NUM_FEATURES, feature_buffer + 36, DRED_NUM_FEATURES);
+
+ /* run RDOVAE encoder */
+ dred_rdovae_encode_dframe(&enc->rdovae_enc, &enc->model, enc->latents_buffer, enc->state_buffer, input_buffer, arch);
+ enc->latents_buffer_fill = IMIN(enc->latents_buffer_fill+1, DRED_NUM_REDUNDANCY_FRAMES);
+}
+
+void filter_df2t(const float *in, float *out, int len, float b0, const float *b, const float *a, int order, float *mem)
+{
+ int i;
+ for (i=0;i<len;i++) {
+ int j;
+ float xi, yi, nyi;
+ xi = in[i];
+ yi = xi*b0 + mem[0];
+ nyi = -yi;
+ for (j=0;j<order;j++)
+ {
+ mem[j] = mem[j+1] + b[j]*xi + a[j]*nyi;
+ }
+ out[i] = yi;
+ /*fprintf(stdout, "%f\n", out[i]);*/
+ }
+}
+
+#define MAX_DOWNMIX_BUFFER (960*2)
+static void dred_convert_to_16k(DREDEnc *enc, const float *in, int in_len, float *out, int out_len)
+{
+ float downmix[MAX_DOWNMIX_BUFFER];
+ int i;
+ int up;
+ celt_assert(enc->channels*in_len <= MAX_DOWNMIX_BUFFER);
+ celt_assert(in_len * (opus_int32)16000 == out_len * enc->Fs);
+ switch(enc->Fs) {
+ case 8000:
+ up = 2;
+ break;
+ case 12000:
+ up = 4;
+ break;
+ case 16000:
+ up = 1;
+ break;
+ case 24000:
+ up = 2;
+ break;
+ case 48000:
+ up = 1;
+ break;
+ default:
+ celt_assert(0);
+ }
+ OPUS_CLEAR(downmix, up*in_len);
+ if (enc->channels == 1) {
+ for (i=0;i<in_len;i++) downmix[up*i] = FLOAT2INT16(up*in[i]);
+ } else {
+ for (i=0;i<in_len;i++) downmix[up*i] = FLOAT2INT16(.5*up*(in[2*i]+in[2*i+1]));
+ }
+ if (enc->Fs == 16000) {
+ OPUS_COPY(out, downmix, out_len);
+ } else if (enc->Fs == 48000 || enc->Fs == 24000) {
+ /* ellip(7, .2, 70, 7750/24000) */
+
+ static const float filter_b[8] = { 0.005873358047f, 0.012980854831f, 0.014531340042f, 0.014531340042f, 0.012980854831f, 0.005873358047f, 0.004523418224f, 0.f};
+ static const float filter_a[8] = {-3.878718597768f, 7.748834257468f, -9.653651699533f, 8.007342726666f, -4.379450178552f, 1.463182111810f, -0.231720677804f, 0.f};
+ float b0 = 0.004523418224f;
+ filter_df2t(downmix, downmix, up*in_len, b0, filter_b, filter_a, RESAMPLING_ORDER, enc->resample_mem);
+ for (i=0;i<out_len;i++) out[i] = downmix[3*i];
+ } else if (enc->Fs == 12000) {
+ /* ellip(7, .2, 70, 7750/24000) */
+ static const float filter_b[8] = {-0.001017101081f, 0.003673127243f, 0.001009165267f, 0.001009165267f, 0.003673127243f, -0.001017101081f, 0.002033596776f, 0.f};
+ static const float filter_a[8] = {-4.930414411612f, 11.291643096504f, -15.322037343815f, 13.216403930898f, -7.220409219553f, 2.310550142771f, -0.334338618782f, 0.f};
+ float b0 = 0.002033596776f;
+ filter_df2t(downmix, downmix, up*in_len, b0, filter_b, filter_a, RESAMPLING_ORDER, enc->resample_mem);
+ for (i=0;i<out_len;i++) out[i] = downmix[3*i];
+ } else if (enc->Fs == 8000) {
+ /* ellip(7, .2, 70, 3900/8000) */
+ static const float filter_b[8] = { 0.081670120929f, 0.180401598565f, 0.259391051971f, 0.259391051971f, 0.180401598565f, 0.081670120929f, 0.020109185709f, 0.f};
+ static const float filter_a[8] = {-1.393651933659f, 2.609789872676f, -2.403541968806f, 2.056814957331f, -1.148908574570f, 0.473001413788f, -0.110359852412f, 0.f};
+ float b0 = 0.020109185709f;
+ filter_df2t(downmix, out, out_len, b0, filter_b, filter_a, RESAMPLING_ORDER, enc->resample_mem);
+ } else {
+ celt_assert(0);
+ }
+}
+
+void dred_compute_latents(DREDEnc *enc, const float *pcm, int frame_size, int extra_delay, int arch)
+{
+ int curr_offset16k;
+ int frame_size16k = frame_size * 16000 / enc->Fs;
+ celt_assert(enc->loaded);
+ curr_offset16k = 40 + extra_delay*16000/enc->Fs - enc->input_buffer_fill;
+ enc->dred_offset = (int)floor((curr_offset16k+20.f)/40.f);
+ enc->latent_offset = 0;
+ while (frame_size16k > 0) {
+ int process_size16k;
+ int process_size;
+ process_size16k = IMIN(2*DRED_FRAME_SIZE, frame_size16k);
+ process_size = process_size16k * enc->Fs / 16000;
+ dred_convert_to_16k(enc, pcm, process_size, &enc->input_buffer[enc->input_buffer_fill], process_size16k);
+ enc->input_buffer_fill += process_size16k;
+ if (enc->input_buffer_fill >= 2*DRED_FRAME_SIZE)
+ {
+ curr_offset16k += 320;
+ dred_process_frame(enc, arch);
+ enc->input_buffer_fill -= 2*DRED_FRAME_SIZE;
+ OPUS_MOVE(&enc->input_buffer[0], &enc->input_buffer[2*DRED_FRAME_SIZE], enc->input_buffer_fill);
+ /* 15 ms (6*2.5 ms) is the ideal offset for DRED because it corresponds to our vocoder look-ahead. */
+ if (enc->dred_offset < 6) {
+ enc->dred_offset += 8;
+ } else {
+ enc->latent_offset++;
+ }
+ }
+
+ pcm += process_size;
+ frame_size16k -= process_size16k;
+ }
+}
+
+static void dred_encode_latents(ec_enc *enc, const float *x, const opus_uint8 *scale, const opus_uint8 *dzone, const opus_uint8 *r, const opus_uint8 *p0, int dim, int arch) {
+ int i;
+ int q[IMAX(DRED_LATENT_DIM,DRED_STATE_DIM)];
+ float xq[IMAX(DRED_LATENT_DIM,DRED_STATE_DIM)];
+ float delta[IMAX(DRED_LATENT_DIM,DRED_STATE_DIM)];
+ float deadzone[IMAX(DRED_LATENT_DIM,DRED_STATE_DIM)];
+ float eps = .1f;
+ /* This is split into multiple loops (with temporary arrays) so that the compiler
+ can vectorize all of it, and so we can call the vector tanh(). */
+ for (i=0;i<dim;i++) {
+ delta[i] = dzone[i]*(1.f/256.f);
+ xq[i] = x[i]*scale[i]*(1.f/256.f);
+ deadzone[i] = xq[i]/(delta[i]+eps);
+ }
+ compute_activation(deadzone, deadzone, dim, ACTIVATION_TANH, arch);
+ for (i=0;i<dim;i++) {
+ xq[i] = xq[i] - delta[i]*deadzone[i];
+ q[i] = (int)floor(.5f+xq[i]);
+ }
+ for (i=0;i<dim;i++) {
+ /* Make the impossible actually impossible. */
+ if (r[i] == 0 || p0[i] == 255) q[i] = 0;
+ else ec_laplace_encode_p0(enc, q[i], p0[i]<<7, r[i]<<7);
+ }
+}
+
+static int dred_voice_active(const unsigned char *activity_mem, int offset) {
+ int i;
+ for (i=0;i<16;i++) {
+ if (activity_mem[8*offset + i] == 1) return 1;
+ }
+ return 0;
+}
+
+int dred_encode_silk_frame(DREDEnc *enc, unsigned char *buf, int max_chunks, int max_bytes, int q0, int dQ, int qmax, unsigned char *activity_mem, int arch) {
+ ec_enc ec_encoder;
+
+ int q_level;
+ int i;
+ int offset;
+ int ec_buffer_fill;
+ int state_qoffset;
+ ec_enc ec_bak;
+ int prev_active=0;
+ int latent_offset;
+ int extra_dred_offset=0;
+ int dred_encoded=0;
+ int delayed_dred=0;
+ int total_offset;
+
+ latent_offset = enc->latent_offset;
+ /* Delaying new DRED data when just out of silence because we already have the
+ main Opus payload for that frame. */
+ if (activity_mem[0] && enc->last_extra_dred_offset>0) {
+ latent_offset = enc->last_extra_dred_offset;
+ delayed_dred = 1;
+ enc->last_extra_dred_offset = 0;
+ }
+ while (latent_offset < enc->latents_buffer_fill && !dred_voice_active(activity_mem, latent_offset)) {
+ latent_offset++;
+ extra_dred_offset++;
+ }
+ if (!delayed_dred) enc->last_extra_dred_offset = extra_dred_offset;
+
+ /* entropy coding of state and latents */
+ ec_enc_init(&ec_encoder, buf, max_bytes);
+ ec_enc_uint(&ec_encoder, q0, 16);
+ ec_enc_uint(&ec_encoder, dQ, 8);
+ total_offset = 16 - (enc->dred_offset - extra_dred_offset*8);
+ celt_assert(total_offset>=0);
+ if (total_offset > 31) {
+ ec_enc_uint(&ec_encoder, 1, 2);
+ ec_enc_uint(&ec_encoder, total_offset>>5, 256);
+ ec_enc_uint(&ec_encoder, total_offset&31, 32);
+ } else {
+ ec_enc_uint(&ec_encoder, 0, 2);
+ ec_enc_uint(&ec_encoder, total_offset, 32);
+ }
+ celt_assert(qmax >= q0);
+ if (q0 < 14 && dQ > 0) {
+ int nvals;
+ /* If you want to use qmax == q0, you should have set dQ = 0. */
+ celt_assert(qmax > q0);
+ nvals = 15 - (q0 + 1);
+ ec_encode(&ec_encoder, qmax >= 15 ? 0 : nvals + qmax - (q0 + 1),
+ qmax >= 15 ? nvals : nvals + qmax - q0, 2*nvals);
+ }
+ state_qoffset = q0*DRED_STATE_DIM;
+ dred_encode_latents(
+ &ec_encoder,
+ &enc->state_buffer[latent_offset*DRED_STATE_DIM],
+ dred_state_quant_scales_q8 + state_qoffset,
+ dred_state_dead_zone_q8 + state_qoffset,
+ dred_state_r_q8 + state_qoffset,
+ dred_state_p0_q8 + state_qoffset,
+ DRED_STATE_DIM,
+ arch);
+ if (ec_tell(&ec_encoder) > 8*max_bytes) {
+ return 0;
+ }
+ ec_bak = ec_encoder;
+ for (i = 0; i < IMIN(2*max_chunks, enc->latents_buffer_fill-latent_offset-1); i += 2)
+ {
+ int active;
+ q_level = compute_quantizer(q0, dQ, qmax, i/2);
+ offset = q_level * DRED_LATENT_DIM;
+
+ dred_encode_latents(
+ &ec_encoder,
+ enc->latents_buffer + (i+latent_offset) * DRED_LATENT_DIM,
+ dred_latent_quant_scales_q8 + offset,
+ dred_latent_dead_zone_q8 + offset,
+ dred_latent_r_q8 + offset,
+ dred_latent_p0_q8 + offset,
+ DRED_LATENT_DIM,
+ arch
+ );
+ if (ec_tell(&ec_encoder) > 8*max_bytes) {
+ /* If we haven't been able to code one chunk, give up on DRED completely. */
+ if (i==0) return 0;
+ break;
+ }
+ active = dred_voice_active(activity_mem, i+latent_offset);
+ if (active || prev_active) {
+ ec_bak = ec_encoder;
+ dred_encoded = i+2;
+ }
+ prev_active = active;
+ }
+ /* Avoid sending empty DRED packets. */
+ if (dred_encoded==0 || (dred_encoded<=2 && extra_dred_offset)) return 0;
+ ec_encoder = ec_bak;
+
+ ec_buffer_fill = (ec_tell(&ec_encoder)+7)/8;
+ ec_enc_shrink(&ec_encoder, ec_buffer_fill);
+ ec_enc_done(&ec_encoder);
+ return ec_buffer_fill;
+}
diff --git a/dnn/dred_encoder.h b/dnn/dred_encoder.h
new file mode 100644
index 00000000..6987222c
--- /dev/null
+++ b/dnn/dred_encoder.h
@@ -0,0 +1,71 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef DRED_ENCODER_H
+#define DRED_ENCODER_H
+
+#include "lpcnet.h"
+#include "dred_config.h"
+#include "dred_rdovae.h"
+#include "entcode.h"
+#include "lpcnet_private.h"
+#include "dred_rdovae_enc.h"
+#include "dred_rdovae_enc_data.h"
+
+#define RESAMPLING_ORDER 8
+
+typedef struct {
+ RDOVAEEnc model;
+ LPCNetEncState lpcnet_enc_state;
+ RDOVAEEncState rdovae_enc;
+ int loaded;
+ opus_int32 Fs;
+ int channels;
+
+#define DREDENC_RESET_START input_buffer
+ float input_buffer[2*DRED_DFRAME_SIZE];
+ int input_buffer_fill;
+ int dred_offset;
+ int latent_offset;
+ int last_extra_dred_offset;
+ float latents_buffer[DRED_MAX_FRAMES * DRED_LATENT_DIM];
+ int latents_buffer_fill;
+ float state_buffer[DRED_MAX_FRAMES * DRED_STATE_DIM];
+ float resample_mem[RESAMPLING_ORDER + 1];
+} DREDEnc;
+
+int dred_encoder_load_model(DREDEnc* enc, const void *data, int len);
+void dred_encoder_init(DREDEnc* enc, opus_int32 Fs, int channels);
+void dred_encoder_reset(DREDEnc* enc);
+
+void dred_deinit_encoder(DREDEnc *enc);
+
+void dred_compute_latents(DREDEnc *enc, const float *pcm, int frame_size, int extra_delay, int arch);
+
+int dred_encode_silk_frame(DREDEnc *enc, unsigned char *buf, int max_chunks, int max_bytes, int q0, int dQ, int qmax, unsigned char *activity_mem, int arch);
+
+#endif
diff --git a/dnn/dred_rdovae.h b/dnn/dred_rdovae.h
new file mode 100644
index 00000000..89ea39ac
--- /dev/null
+++ b/dnn/dred_rdovae.h
@@ -0,0 +1,42 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef DRED_RDOVAE_H
+#define DRED_RDOVAE_H
+
+#include <stdlib.h>
+
+#include "opus_types.h"
+
+typedef struct RDOVAEDec RDOVAEDec;
+typedef struct RDOVAEEnc RDOVAEEnc;
+typedef struct RDOVAEDecStruct RDOVAEDecState;
+typedef struct RDOVAEEncStruct RDOVAEEncState;
+
+
+
+#endif
diff --git a/dnn/dred_rdovae_dec.c b/dnn/dred_rdovae_dec.c
new file mode 100644
index 00000000..7797ee77
--- /dev/null
+++ b/dnn/dred_rdovae_dec.c
@@ -0,0 +1,139 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "dred_rdovae_dec.h"
+#include "dred_rdovae_constants.h"
+#include "os_support.h"
+
+static void conv1_cond_init(float *mem, int len, int dilation, int *init)
+{
+ if (!*init) {
+ int i;
+ for (i=0;i<dilation;i++) OPUS_CLEAR(&mem[i*len], len);
+ }
+ *init = 1;
+}
+
+void DRED_rdovae_decode_all(const RDOVAEDec *model, float *features, const float *state, const float *latents, int nb_latents, int arch)
+{
+ int i;
+ RDOVAEDecState dec;
+ memset(&dec, 0, sizeof(dec));
+ dred_rdovae_dec_init_states(&dec, model, state, arch);
+ for (i = 0; i < 2*nb_latents; i += 2)
+ {
+ dred_rdovae_decode_qframe(
+ &dec,
+ model,
+ &features[2*i*DRED_NUM_FEATURES],
+ &latents[(i/2)*DRED_LATENT_DIM],
+ arch);
+ }
+}
+
+void dred_rdovae_dec_init_states(
+ RDOVAEDecState *h, /* io: state buffer handle */
+ const RDOVAEDec *model,
+ const float *initial_state, /* i: initial state */
+ int arch
+ )
+{
+ float hidden[DEC_HIDDEN_INIT_OUT_SIZE];
+ float state_init[DEC_GRU1_STATE_SIZE+DEC_GRU2_STATE_SIZE+DEC_GRU3_STATE_SIZE+DEC_GRU4_STATE_SIZE+DEC_GRU5_STATE_SIZE];
+ int counter=0;
+ compute_generic_dense(&model->dec_hidden_init, hidden, initial_state, ACTIVATION_TANH, arch);
+ compute_generic_dense(&model->dec_gru_init, state_init, hidden, ACTIVATION_TANH, arch);
+ OPUS_COPY(h->gru1_state, state_init, DEC_GRU1_STATE_SIZE);
+ counter += DEC_GRU1_STATE_SIZE;
+ OPUS_COPY(h->gru2_state, &state_init[counter], DEC_GRU2_STATE_SIZE);
+ counter += DEC_GRU2_STATE_SIZE;
+ OPUS_COPY(h->gru3_state, &state_init[counter], DEC_GRU3_STATE_SIZE);
+ counter += DEC_GRU3_STATE_SIZE;
+ OPUS_COPY(h->gru4_state, &state_init[counter], DEC_GRU4_STATE_SIZE);
+ counter += DEC_GRU4_STATE_SIZE;
+ OPUS_COPY(h->gru5_state, &state_init[counter], DEC_GRU5_STATE_SIZE);
+ h->initialized = 0;
+}
+
+
+void dred_rdovae_decode_qframe(
+ RDOVAEDecState *dec_state, /* io: state buffer handle */
+ const RDOVAEDec *model,
+ float *qframe, /* o: quadruple feature frame (four concatenated frames in reverse order) */
+ const float *input, /* i: latent vector */
+ int arch
+ )
+{
+ float buffer[DEC_DENSE1_OUT_SIZE + DEC_GRU1_OUT_SIZE + DEC_GRU2_OUT_SIZE + DEC_GRU3_OUT_SIZE + DEC_GRU4_OUT_SIZE + DEC_GRU5_OUT_SIZE
+ + DEC_CONV1_OUT_SIZE + DEC_CONV2_OUT_SIZE + DEC_CONV3_OUT_SIZE + DEC_CONV4_OUT_SIZE + DEC_CONV5_OUT_SIZE];
+ int output_index = 0;
+
+ /* run encoder stack and concatenate output in buffer*/
+ compute_generic_dense(&model->dec_dense1, &buffer[output_index], input, ACTIVATION_TANH, arch);
+ output_index += DEC_DENSE1_OUT_SIZE;
+
+ compute_generic_gru(&model->dec_gru1_input, &model->dec_gru1_recurrent, dec_state->gru1_state, buffer, arch);
+ compute_glu(&model->dec_glu1, &buffer[output_index], dec_state->gru1_state, arch);
+ output_index += DEC_GRU1_OUT_SIZE;
+ conv1_cond_init(dec_state->conv1_state, output_index, 1, &dec_state->initialized);
+ compute_generic_conv1d(&model->dec_conv1, &buffer[output_index], dec_state->conv1_state, buffer, output_index, ACTIVATION_TANH, arch);
+ output_index += DEC_CONV1_OUT_SIZE;
+
+ compute_generic_gru(&model->dec_gru2_input, &model->dec_gru2_recurrent, dec_state->gru2_state, buffer, arch);
+ compute_glu(&model->dec_glu2, &buffer[output_index], dec_state->gru2_state, arch);
+ output_index += DEC_GRU2_OUT_SIZE;
+ conv1_cond_init(dec_state->conv2_state, output_index, 1, &dec_state->initialized);
+ compute_generic_conv1d(&model->dec_conv2, &buffer[output_index], dec_state->conv2_state, buffer, output_index, ACTIVATION_TANH, arch);
+ output_index += DEC_CONV2_OUT_SIZE;
+
+ compute_generic_gru(&model->dec_gru3_input, &model->dec_gru3_recurrent, dec_state->gru3_state, buffer, arch);
+ compute_glu(&model->dec_glu3, &buffer[output_index], dec_state->gru3_state, arch);
+ output_index += DEC_GRU3_OUT_SIZE;
+ conv1_cond_init(dec_state->conv3_state, output_index, 1, &dec_state->initialized);
+ compute_generic_conv1d(&model->dec_conv3, &buffer[output_index], dec_state->conv3_state, buffer, output_index, ACTIVATION_TANH, arch);
+ output_index += DEC_CONV3_OUT_SIZE;
+
+ compute_generic_gru(&model->dec_gru4_input, &model->dec_gru4_recurrent, dec_state->gru4_state, buffer, arch);
+ compute_glu(&model->dec_glu4, &buffer[output_index], dec_state->gru4_state, arch);
+ output_index += DEC_GRU4_OUT_SIZE;
+ conv1_cond_init(dec_state->conv4_state, output_index, 1, &dec_state->initialized);
+ compute_generic_conv1d(&model->dec_conv4, &buffer[output_index], dec_state->conv4_state, buffer, output_index, ACTIVATION_TANH, arch);
+ output_index += DEC_CONV4_OUT_SIZE;
+
+ compute_generic_gru(&model->dec_gru5_input, &model->dec_gru5_recurrent, dec_state->gru5_state, buffer, arch);
+ compute_glu(&model->dec_glu5, &buffer[output_index], dec_state->gru5_state, arch);
+ output_index += DEC_GRU5_OUT_SIZE;
+ conv1_cond_init(dec_state->conv5_state, output_index, 1, &dec_state->initialized);
+ compute_generic_conv1d(&model->dec_conv5, &buffer[output_index], dec_state->conv5_state, buffer, output_index, ACTIVATION_TANH, arch);
+ output_index += DEC_CONV5_OUT_SIZE;
+
+ compute_generic_dense(&model->dec_output, qframe, buffer, ACTIVATION_LINEAR, arch);
+}
diff --git a/dnn/dred_rdovae_dec.h b/dnn/dred_rdovae_dec.h
new file mode 100644
index 00000000..4e66911c
--- /dev/null
+++ b/dnn/dred_rdovae_dec.h
@@ -0,0 +1,53 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef DRED_RDOVAE_DEC_H
+#define DRED_RDOVAE_DEC_H
+
+#include "dred_rdovae.h"
+#include "dred_rdovae_dec_data.h"
+#include "dred_rdovae_stats_data.h"
+
+struct RDOVAEDecStruct {
+ int initialized;
+ float gru1_state[DEC_GRU1_STATE_SIZE];
+ float gru2_state[DEC_GRU2_STATE_SIZE];
+ float gru3_state[DEC_GRU3_STATE_SIZE];
+ float gru4_state[DEC_GRU4_STATE_SIZE];
+ float gru5_state[DEC_GRU5_STATE_SIZE];
+ float conv1_state[DEC_CONV1_STATE_SIZE];
+ float conv2_state[DEC_CONV2_STATE_SIZE];
+ float conv3_state[DEC_CONV3_STATE_SIZE];
+ float conv4_state[DEC_CONV4_STATE_SIZE];
+ float conv5_state[DEC_CONV5_STATE_SIZE];
+};
+
+void dred_rdovae_dec_init_states(RDOVAEDecState *h, const RDOVAEDec *model, const float * initial_state, int arch);
+void dred_rdovae_decode_qframe(RDOVAEDecState *h, const RDOVAEDec *model, float *qframe, const float * z, int arch);
+void DRED_rdovae_decode_all(const RDOVAEDec *model, float *features, const float *state, const float *latents, int nb_latents, int arch);
+
+#endif
diff --git a/dnn/dred_rdovae_enc.c b/dnn/dred_rdovae_enc.c
new file mode 100644
index 00000000..4f13ae21
--- /dev/null
+++ b/dnn/dred_rdovae_enc.c
@@ -0,0 +1,110 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#include <math.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#include "dred_rdovae_enc.h"
+#include "os_support.h"
+#include "dred_rdovae_constants.h"
+
+static void conv1_cond_init(float *mem, int len, int dilation, int *init)
+{
+ if (!*init) {
+ int i;
+ for (i=0;i<dilation;i++) OPUS_CLEAR(&mem[i*len], len);
+ }
+ *init = 1;
+}
+
+void dred_rdovae_encode_dframe(
+ RDOVAEEncState *enc_state, /* io: encoder state */
+ const RDOVAEEnc *model,
+ float *latents, /* o: latent vector */
+ float *initial_state, /* o: initial state */
+ const float *input, /* i: double feature frame (concatenated) */
+ int arch
+ )
+{
+ float padded_latents[DRED_PADDED_LATENT_DIM];
+ float padded_state[DRED_PADDED_STATE_DIM];
+ float buffer[ENC_DENSE1_OUT_SIZE + ENC_GRU1_OUT_SIZE + ENC_GRU2_OUT_SIZE + ENC_GRU3_OUT_SIZE + ENC_GRU4_OUT_SIZE + ENC_GRU5_OUT_SIZE
+ + ENC_CONV1_OUT_SIZE + ENC_CONV2_OUT_SIZE + ENC_CONV3_OUT_SIZE + ENC_CONV4_OUT_SIZE + ENC_CONV5_OUT_SIZE];
+ float state_hidden[GDENSE1_OUT_SIZE];
+ int output_index = 0;
+
+ /* run encoder stack and concatenate output in buffer*/
+ compute_generic_dense(&model->enc_dense1, &buffer[output_index], input, ACTIVATION_TANH, arch);
+ output_index += ENC_DENSE1_OUT_SIZE;
+
+ compute_generic_gru(&model->enc_gru1_input, &model->enc_gru1_recurrent, enc_state->gru1_state, buffer, arch);
+ OPUS_COPY(&buffer[output_index], enc_state->gru1_state, ENC_GRU1_OUT_SIZE);
+ output_index += ENC_GRU1_OUT_SIZE;
+ conv1_cond_init(enc_state->conv1_state, output_index, 1, &enc_state->initialized);
+ compute_generic_conv1d(&model->enc_conv1, &buffer[output_index], enc_state->conv1_state, buffer, output_index, ACTIVATION_TANH, arch);
+ output_index += ENC_CONV1_OUT_SIZE;
+
+ compute_generic_gru(&model->enc_gru2_input, &model->enc_gru2_recurrent, enc_state->gru2_state, buffer, arch);
+ OPUS_COPY(&buffer[output_index], enc_state->gru2_state, ENC_GRU2_OUT_SIZE);
+ output_index += ENC_GRU2_OUT_SIZE;
+ conv1_cond_init(enc_state->conv2_state, output_index, 2, &enc_state->initialized);
+ compute_generic_conv1d_dilation(&model->enc_conv2, &buffer[output_index], enc_state->conv2_state, buffer, output_index, 2, ACTIVATION_TANH, arch);
+ output_index += ENC_CONV2_OUT_SIZE;
+
+ compute_generic_gru(&model->enc_gru3_input, &model->enc_gru3_recurrent, enc_state->gru3_state, buffer, arch);
+ OPUS_COPY(&buffer[output_index], enc_state->gru3_state, ENC_GRU3_OUT_SIZE);
+ output_index += ENC_GRU3_OUT_SIZE;
+ conv1_cond_init(enc_state->conv3_state, output_index, 2, &enc_state->initialized);
+ compute_generic_conv1d_dilation(&model->enc_conv3, &buffer[output_index], enc_state->conv3_state, buffer, output_index, 2, ACTIVATION_TANH, arch);
+ output_index += ENC_CONV3_OUT_SIZE;
+
+ compute_generic_gru(&model->enc_gru4_input, &model->enc_gru4_recurrent, enc_state->gru4_state, buffer, arch);
+ OPUS_COPY(&buffer[output_index], enc_state->gru4_state, ENC_GRU4_OUT_SIZE);
+ output_index += ENC_GRU4_OUT_SIZE;
+ conv1_cond_init(enc_state->conv4_state, output_index, 2, &enc_state->initialized);
+ compute_generic_conv1d_dilation(&model->enc_conv4, &buffer[output_index], enc_state->conv4_state, buffer, output_index, 2, ACTIVATION_TANH, arch);
+ output_index += ENC_CONV4_OUT_SIZE;
+
+ compute_generic_gru(&model->enc_gru5_input, &model->enc_gru5_recurrent, enc_state->gru5_state, buffer, arch);
+ OPUS_COPY(&buffer[output_index], enc_state->gru5_state, ENC_GRU5_OUT_SIZE);
+ output_index += ENC_GRU5_OUT_SIZE;
+ conv1_cond_init(enc_state->conv5_state, output_index, 2, &enc_state->initialized);
+ compute_generic_conv1d_dilation(&model->enc_conv5, &buffer[output_index], enc_state->conv5_state, buffer, output_index, 2, ACTIVATION_TANH, arch);
+ output_index += ENC_CONV5_OUT_SIZE;
+
+ compute_generic_dense(&model->enc_zdense, padded_latents, buffer, ACTIVATION_LINEAR, arch);
+ OPUS_COPY(latents, padded_latents, DRED_LATENT_DIM);
+
+ /* next, calculate initial state */
+ compute_generic_dense(&model->gdense1, state_hidden, buffer, ACTIVATION_TANH, arch);
+ compute_generic_dense(&model->gdense2, padded_state, state_hidden, ACTIVATION_LINEAR, arch);
+ OPUS_COPY(initial_state, padded_state, DRED_STATE_DIM);
+}
diff --git a/dnn/dred_rdovae_enc.h b/dnn/dred_rdovae_enc.h
new file mode 100644
index 00000000..6fe537ee
--- /dev/null
+++ b/dnn/dred_rdovae_enc.h
@@ -0,0 +1,52 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef DRED_RDOVAE_ENC_H
+#define DRED_RDOVAE_ENC_H
+
+#include "dred_rdovae.h"
+
+#include "dred_rdovae_enc_data.h"
+
+struct RDOVAEEncStruct {
+ int initialized;
+ float gru1_state[ENC_GRU1_STATE_SIZE];
+ float gru2_state[ENC_GRU2_STATE_SIZE];
+ float gru3_state[ENC_GRU3_STATE_SIZE];
+ float gru4_state[ENC_GRU4_STATE_SIZE];
+ float gru5_state[ENC_GRU5_STATE_SIZE];
+ float conv1_state[ENC_CONV1_STATE_SIZE];
+ float conv2_state[2*ENC_CONV2_STATE_SIZE];
+ float conv3_state[2*ENC_CONV3_STATE_SIZE];
+ float conv4_state[2*ENC_CONV4_STATE_SIZE];
+ float conv5_state[2*ENC_CONV5_STATE_SIZE];
+};
+
+void dred_rdovae_encode_dframe(RDOVAEEncState *enc_state, const RDOVAEEnc *model, float *latents, float *initial_state, const float *input, int arch);
+
+
+#endif
diff --git a/dnn/dump_data.c b/dnn/dump_data.c
new file mode 100644
index 00000000..e4c78c7e
--- /dev/null
+++ b/dnn/dump_data.c
@@ -0,0 +1,280 @@
+/* Copyright (c) 2017-2018 Mozilla */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "kiss_fft.h"
+#include "common.h"
+#include <math.h>
+#include "freq.h"
+#include "pitch.h"
+#include "arch.h"
+#include <assert.h>
+#include "lpcnet.h"
+#include "lpcnet_private.h"
+#include "os_support.h"
+#include "cpu_support.h"
+
+
+static void biquad(float *y, float mem[2], const float *x, const float *b, const float *a, int N) {
+ int i;
+ for (i=0;i<N;i++) {
+ float xi, yi;
+ xi = x[i];
+ yi = x[i] + mem[0];
+ mem[0] = mem[1] + (b[0]*(double)xi - a[0]*(double)yi);
+ mem[1] = (b[1]*(double)xi - a[1]*(double)yi);
+ y[i] = yi;
+ }
+}
+
+static float uni_rand(void) {
+ return rand()/(double)RAND_MAX-.5;
+}
+
+static void rand_resp(float *a, float *b) {
+ a[0] = .75*uni_rand();
+ a[1] = .75*uni_rand();
+ b[0] = .75*uni_rand();
+ b[1] = .75*uni_rand();
+}
+
+void compute_noise(int *noise, float noise_std) {
+ int i;
+ for (i=0;i<FRAME_SIZE;i++) {
+ noise[i] = (int)floor(.5 + noise_std*.707*(log_approx(rand()/(float)RAND_MAX)-log_approx(rand()/(float)RAND_MAX)));
+ }
+}
+
+static opus_int16 float2short(float x)
+{
+ int i;
+ i = (int)floor(.5+x);
+ return IMAX(-32767, IMIN(32767, i));
+}
+
+
+void write_audio(LPCNetEncState *st, const opus_int16 *pcm, const int *noise, FILE *file) {
+ int i;
+ opus_int16 data[2*FRAME_SIZE];
+ for (i=0;i<FRAME_SIZE;i++) {
+ float p=0;
+ float e;
+ int j;
+ for (j=0;j<LPC_ORDER;j++) p -= st->features[NB_BANDS+2+j]*st->sig_mem[j];
+ e = lin2ulaw(pcm[i] - p);
+ /* Signal in. */
+ data[2*i] = float2short(st->sig_mem[0]);
+ /* Signal out. */
+ data[2*i+1] = pcm[i];
+ /* Simulate error on excitation. */
+ e += noise[i];
+ e = IMIN(255, IMAX(0, e));
+
+ OPUS_MOVE(&st->sig_mem[1], &st->sig_mem[0], LPC_ORDER-1);
+ st->sig_mem[0] = p + ulaw2lin(e);
+ }
+ fwrite(data, 4*FRAME_SIZE, 1, file);
+}
+
+int main(int argc, char **argv) {
+ int i;
+ char *argv0;
+ int count=0;
+ static const float a_hp[2] = {-1.99599, 0.99600};
+ static const float b_hp[2] = {-2, 1};
+ float a_sig[2] = {0};
+ float b_sig[2] = {0};
+ float mem_hp_x[2]={0};
+ float mem_resp_x[2]={0};
+ float mem_preemph=0;
+ float x[FRAME_SIZE];
+ int gain_change_count=0;
+ FILE *f1;
+ FILE *ffeat;
+ FILE *fpcm=NULL;
+ opus_int16 pcm[FRAME_SIZE]={0};
+ int noisebuf[FRAME_SIZE]={0};
+ opus_int16 tmp[FRAME_SIZE] = {0};
+ float speech_gain=1;
+ float old_speech_gain = 1;
+ int one_pass_completed = 0;
+ LPCNetEncState *st;
+ float noise_std=0;
+ int training = -1;
+ int burg = 0;
+ int pitch = 0;
+ FILE *fnoise = NULL;
+ float noise_gain = 0;
+ long noise_size=0;
+ int arch;
+ srand(getpid());
+ arch = opus_select_arch();
+ st = lpcnet_encoder_create();
+ argv0=argv[0];
+ if (argc == 5 && strcmp(argv[1], "-btrain")==0) {
+ burg = 1;
+ training = 1;
+ }
+ else if (argc == 4 && strcmp(argv[1], "-btest")==0) {
+ burg = 1;
+ training = 0;
+ }
+ else if (argc == 5 && strcmp(argv[1], "-ptrain")==0) {
+ pitch = 1;
+ training = 1;
+ fnoise = fopen(argv[2], "rb");
+ fseek(fnoise, 0, SEEK_END);
+ noise_size = ftell(fnoise);
+ fseek(fnoise, 0, SEEK_SET);
+ argv++;
+ }
+ else if (argc == 4 && strcmp(argv[1], "-ptest")==0) {
+ pitch = 1;
+ training = 0;
+ }
+ else if (argc == 5 && strcmp(argv[1], "-train")==0) training = 1;
+ else if (argc == 4 && strcmp(argv[1], "-test")==0) training = 0;
+ if (training == -1) {
+ fprintf(stderr, "usage: %s -train <speech> <features out> <pcm out>\n", argv0);
+ fprintf(stderr, " or %s -test <speech> <features out>\n", argv0);
+ return 1;
+ }
+ f1 = fopen(argv[2], "r");
+ if (f1 == NULL) {
+ fprintf(stderr,"Error opening input .s16 16kHz speech input file: %s\n", argv[2]);
+ exit(1);
+ }
+ ffeat = fopen(argv[3], "wb");
+ if (ffeat == NULL) {
+ fprintf(stderr,"Error opening output feature file: %s\n", argv[3]);
+ exit(1);
+ }
+ if (training && !pitch) {
+ fpcm = fopen(argv[4], "wb");
+ if (fpcm == NULL) {
+ fprintf(stderr,"Error opening output PCM file: %s\n", argv[4]);
+ exit(1);
+ }
+ }
+ while (1) {
+ size_t ret;
+ ret = fread(tmp, sizeof(opus_int16), FRAME_SIZE, f1);
+ if (feof(f1) || ret != FRAME_SIZE) {
+ if (!training) break;
+ rewind(f1);
+ ret = fread(tmp, sizeof(opus_int16), FRAME_SIZE, f1);
+ if (ret != FRAME_SIZE) {
+ fprintf(stderr, "error reading\n");
+ exit(1);
+ }
+ one_pass_completed = 1;
+ }
+ for (i=0;i<FRAME_SIZE;i++) x[i] = tmp[i];
+ if (count*FRAME_SIZE_5MS>=10000000 && one_pass_completed) break;
+ if (training && ++gain_change_count > 2821) {
+ float tmp1, tmp2;
+ speech_gain = pow(10., (-30+(rand()%40))/20.);
+ if (rand()&1) speech_gain = -speech_gain;
+ if (rand()%20==0) speech_gain *= .01;
+ if (!pitch && rand()%100==0) speech_gain = 0;
+ gain_change_count = 0;
+ rand_resp(a_sig, b_sig);
+ tmp1 = rand()/(float)RAND_MAX;
+ tmp2 = rand()/(float)RAND_MAX;
+ noise_std = ABS16(-1.5*log(1e-4+tmp1)-.5*log(1e-4+tmp2));
+ if (fnoise != NULL) {
+ long pos;
+ /* Randomize the fraction because rand() only gives us 31 bits. */
+ float frac_pos = rand()/(float)RAND_MAX;
+ pos = (long)(frac_pos*noise_size);
+ /* 32-bit alignment. */
+ pos = pos/4 * 4;
+ if (pos > noise_size-500000) pos = noise_size-500000;
+ noise_gain = pow(10., (-15+(rand()%40))/20.);
+ if (rand()%10==0) noise_gain = 0;
+ fseek(fnoise, pos, SEEK_SET);
+ }
+ }
+ if (fnoise != NULL) {
+ opus_int16 noise[FRAME_SIZE];
+ ret = fread(noise, sizeof(opus_int16), FRAME_SIZE, fnoise);
+ for (i=0;i<FRAME_SIZE;i++) x[i] += noise[i]*noise_gain;
+ }
+ biquad(x, mem_hp_x, x, b_hp, a_hp, FRAME_SIZE);
+ biquad(x, mem_resp_x, x, b_sig, a_sig, FRAME_SIZE);
+ for (i=0;i<FRAME_SIZE;i++) {
+ float g;
+ float f = (float)i/FRAME_SIZE;
+ g = f*speech_gain + (1-f)*old_speech_gain;
+ x[i] *= g;
+ }
+ if (burg) {
+ float ceps[2*NB_BANDS];
+ burg_cepstral_analysis(ceps, x);
+ fwrite(ceps, sizeof(float), 2*NB_BANDS, ffeat);
+ }
+ preemphasis(x, &mem_preemph, x, PREEMPHASIS, FRAME_SIZE);
+ for (i=0;i<FRAME_SIZE;i++) x[i] += rand()/(float)RAND_MAX - .5f;
+ /* PCM is delayed by 1/2 frame to make the features centered on the frames. */
+ for (i=0;i<FRAME_SIZE-TRAINING_OFFSET;i++) pcm[i+TRAINING_OFFSET] = float2short(x[i]);
+ compute_frame_features(st, x, arch);
+
+ if (fpcm) {
+ compute_noise(noisebuf, noise_std);
+ }
+
+ if (pitch) {
+ signed char pitch_features[PITCH_MAX_PERIOD-PITCH_MIN_PERIOD+PITCH_IF_FEATURES];
+ for (i=0;i<PITCH_MAX_PERIOD-PITCH_MIN_PERIOD;i++) {
+ pitch_features[i] = (int)floor(.5f + 127.f*st->xcorr_features[i]);
+ }
+ for (i=0;i<PITCH_IF_FEATURES;i++) {
+ pitch_features[i+PITCH_MAX_PERIOD-PITCH_MIN_PERIOD] = (int)floor(.5f + 127.f*st->if_features[i]);
+ }
+ fwrite(pitch_features, PITCH_MAX_PERIOD-PITCH_MIN_PERIOD+PITCH_IF_FEATURES, 1, ffeat);
+ } else {
+ fwrite(st->features, sizeof(float), NB_TOTAL_FEATURES, ffeat);
+ }
+ /*if(pitch) fwrite(pcm, FRAME_SIZE, 2, stdout);*/
+ if (fpcm) write_audio(st, pcm, noisebuf, fpcm);
+ /*if (fpcm) fwrite(pcm, sizeof(opus_int16), FRAME_SIZE, fpcm);*/
+ for (i=0;i<TRAINING_OFFSET;i++) pcm[i] = float2short(x[i+FRAME_SIZE-TRAINING_OFFSET]);
+ old_speech_gain = speech_gain;
+ count++;
+ }
+ fclose(f1);
+ fclose(ffeat);
+ if (fpcm) fclose(fpcm);
+ lpcnet_encoder_destroy(st);
+ return 0;
+}
diff --git a/dnn/dump_lpcnet_tables.c b/dnn/dump_lpcnet_tables.c
new file mode 100644
index 00000000..95084d6e
--- /dev/null
+++ b/dnn/dump_lpcnet_tables.c
@@ -0,0 +1,104 @@
+/* Copyright (c) 2017-2018 Mozilla
+ Copyright (c) 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <stdio.h>
+#include "freq.h"
+#include "kiss_fft.h"
+
+
+int main(void) {
+ int i;
+ FILE *file;
+ kiss_fft_state *kfft;
+ float half_window[OVERLAP_SIZE];
+ float dct_table[NB_BANDS*NB_BANDS];
+
+ file=fopen("lpcnet_tables.c", "wb");
+ fprintf(file, "/* The contents of this file was automatically generated by dump_lpcnet_tables.c*/\n\n");
+ fprintf(file, "#ifdef HAVE_CONFIG_H\n");
+ fprintf(file, "#include \"config.h\"\n");
+ fprintf(file, "#endif\n");
+
+ fprintf(file, "#include \"kiss_fft.h\"\n\n");
+
+ kfft = opus_fft_alloc_twiddles(WINDOW_SIZE, NULL, NULL, NULL, 0);
+
+ fprintf(file, "static const arch_fft_state arch_fft = {0, NULL};\n\n");
+
+ fprintf (file, "static const opus_int16 fft_bitrev[%d] = {\n", kfft->nfft);
+ for (i=0;i<kfft->nfft;i++)
+ fprintf (file, "%d,%c", kfft->bitrev[i],(i+16)%15==0?'\n':' ');
+ fprintf (file, "};\n\n");
+
+ fprintf (file, "static const kiss_twiddle_cpx fft_twiddles[%d] = {\n", kfft->nfft);
+ for (i=0;i<kfft->nfft;i++)
+ fprintf (file, "{%#0.9gf, %#0.9gf},%c", kfft->twiddles[i].r, kfft->twiddles[i].i,(i+3)%2==0?'\n':' ');
+ fprintf (file, "};\n\n");
+
+
+ fprintf(file, "const kiss_fft_state kfft = {\n");
+ fprintf(file, "%d, /* nfft */\n", kfft->nfft);
+ fprintf(file, "%#0.8gf, /* scale */\n", kfft->scale);
+ fprintf(file, "%d, /* shift */\n", kfft->shift);
+ fprintf(file, "{");
+ for (i=0;i<2*MAXFACTORS;i++) {
+ fprintf(file, "%d, ", kfft->factors[i]);
+ }
+ fprintf(file, "}, /* factors */\n");
+ fprintf(file, "fft_bitrev, /* bitrev*/\n");
+ fprintf(file, "fft_twiddles, /* twiddles*/\n");
+ fprintf(file, "(arch_fft_state *)&arch_fft, /* arch_fft*/\n");
+
+ fprintf(file, "};\n\n");
+
+ for (i=0;i<OVERLAP_SIZE;i++)
+ half_window[i] = sin(.5*M_PI*sin(.5*M_PI*(i+.5)/OVERLAP_SIZE) * sin(.5*M_PI*(i+.5)/OVERLAP_SIZE));
+ fprintf(file, "const float half_window[] = {\n");
+ for (i=0;i<OVERLAP_SIZE;i++)
+ fprintf (file, "%#0.9gf,%c", half_window[i],(i+6)%5==0?'\n':' ');
+ fprintf(file, "};\n\n");
+
+ for (i=0;i<NB_BANDS;i++) {
+ int j;
+ for (j=0;j<NB_BANDS;j++) {
+ dct_table[i*NB_BANDS + j] = cos((i+.5)*j*M_PI/NB_BANDS);
+ if (j==0) dct_table[i*NB_BANDS + j] *= sqrt(.5);
+ }
+ }
+ fprintf(file, "const float dct_table[] = {\n");
+ for (i=0;i<NB_BANDS*NB_BANDS;i++)
+ fprintf (file, "%#0.9gf,%c", dct_table[i],(i+6)%5==0?'\n':' ');
+ fprintf(file, "};\n");
+
+ fclose(file);
+ return 0;
+}
diff --git a/dnn/fargan.c b/dnn/fargan.c
new file mode 100644
index 00000000..c35b3f0c
--- /dev/null
+++ b/dnn/fargan.c
@@ -0,0 +1,225 @@
+/* Copyright (c) 2023 Amazon */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fargan.h"
+#include "os_support.h"
+#include "freq.h"
+#include "fargan_data.h"
+#include "lpcnet.h"
+#include "pitch.h"
+#include "nnet.h"
+#include "lpcnet_private.h"
+#include "cpu_support.h"
+
+#define FARGAN_FEATURES (NB_FEATURES)
+
+static void compute_fargan_cond(FARGANState *st, float *cond, const float *features, int period)
+{
+ FARGAN *model;
+ float dense_in[NB_FEATURES+COND_NET_PEMBED_OUT_SIZE];
+ float conv1_in[COND_NET_FCONV1_IN_SIZE];
+ float fdense2_in[COND_NET_FCONV1_OUT_SIZE];
+ model = &st->model;
+ celt_assert(FARGAN_FEATURES+COND_NET_PEMBED_OUT_SIZE == model->cond_net_fdense1.nb_inputs);
+ celt_assert(COND_NET_FCONV1_IN_SIZE == model->cond_net_fdense1.nb_outputs);
+ celt_assert(COND_NET_FCONV1_OUT_SIZE == model->cond_net_fconv1.nb_outputs);
+ OPUS_COPY(&dense_in[NB_FEATURES], &model->cond_net_pembed.float_weights[IMAX(0,IMIN(period-32, 223))*COND_NET_PEMBED_OUT_SIZE], COND_NET_PEMBED_OUT_SIZE);
+ OPUS_COPY(dense_in, features, NB_FEATURES);
+
+ compute_generic_dense(&model->cond_net_fdense1, conv1_in, dense_in, ACTIVATION_TANH, st->arch);
+ compute_generic_conv1d(&model->cond_net_fconv1, fdense2_in, st->cond_conv1_state, conv1_in, COND_NET_FCONV1_IN_SIZE, ACTIVATION_TANH, st->arch);
+ compute_generic_dense(&model->cond_net_fdense2, cond, fdense2_in, ACTIVATION_TANH, st->arch);
+}
+
+static void fargan_deemphasis(float *pcm, float *deemph_mem) {
+ int i;
+ for (i=0;i<FARGAN_SUBFRAME_SIZE;i++) {
+ pcm[i] += FARGAN_DEEMPHASIS * *deemph_mem;
+ *deemph_mem = pcm[i];
+ }
+}
+
+static void run_fargan_subframe(FARGANState *st, float *pcm, const float *cond, int period)
+{
+ int i, pos;
+ float fwc0_in[SIG_NET_INPUT_SIZE];
+ float gru1_in[SIG_NET_FWC0_CONV_OUT_SIZE+2*FARGAN_SUBFRAME_SIZE];
+ float gru2_in[SIG_NET_GRU1_OUT_SIZE+2*FARGAN_SUBFRAME_SIZE];
+ float gru3_in[SIG_NET_GRU2_OUT_SIZE+2*FARGAN_SUBFRAME_SIZE];
+ float pred[FARGAN_SUBFRAME_SIZE+4];
+ float prev[FARGAN_SUBFRAME_SIZE];
+ float pitch_gate[4];
+ float gain;
+ float gain_1;
+ float skip_cat[10000];
+ float skip_out[SIG_NET_SKIP_DENSE_OUT_SIZE];
+ FARGAN *model;
+
+ celt_assert(st->cont_initialized);
+ model = &st->model;
+
+ compute_generic_dense(&model->sig_net_cond_gain_dense, &gain, cond, ACTIVATION_LINEAR, st->arch);
+ gain = exp(gain);
+ gain_1 = 1.f/(1e-5f + gain);
+
+ pos = PITCH_MAX_PERIOD-period-2;
+ for (i=0;i<FARGAN_SUBFRAME_SIZE+4;i++) {
+ pred[i] = MIN32(1.f, MAX32(-1.f, gain_1*st->pitch_buf[IMAX(0, pos)]));
+ pos++;
+ if (pos == PITCH_MAX_PERIOD) pos -= period;
+ }
+ for (i=0;i<FARGAN_SUBFRAME_SIZE;i++) prev[i] = MAX32(-1.f, MIN16(1.f, gain_1*st->pitch_buf[PITCH_MAX_PERIOD-FARGAN_SUBFRAME_SIZE+i]));
+
+ OPUS_COPY(&fwc0_in[0], &cond[0], FARGAN_COND_SIZE);
+ OPUS_COPY(&fwc0_in[FARGAN_COND_SIZE], pred, FARGAN_SUBFRAME_SIZE+4);
+ OPUS_COPY(&fwc0_in[FARGAN_COND_SIZE+FARGAN_SUBFRAME_SIZE+4], prev, FARGAN_SUBFRAME_SIZE);
+
+ compute_generic_conv1d(&model->sig_net_fwc0_conv, gru1_in, st->fwc0_mem, fwc0_in, SIG_NET_INPUT_SIZE, ACTIVATION_TANH, st->arch);
+ celt_assert(SIG_NET_FWC0_GLU_GATE_OUT_SIZE == model->sig_net_fwc0_glu_gate.nb_outputs);
+ compute_glu(&model->sig_net_fwc0_glu_gate, gru1_in, gru1_in, st->arch);
+
+ compute_generic_dense(&model->sig_net_gain_dense_out, pitch_gate, gru1_in, ACTIVATION_SIGMOID, st->arch);
+
+ for (i=0;i<FARGAN_SUBFRAME_SIZE;i++) gru1_in[SIG_NET_FWC0_GLU_GATE_OUT_SIZE+i] = pitch_gate[0]*pred[i+2];
+ OPUS_COPY(&gru1_in[SIG_NET_FWC0_GLU_GATE_OUT_SIZE+FARGAN_SUBFRAME_SIZE], prev, FARGAN_SUBFRAME_SIZE);
+ compute_generic_gru(&model->sig_net_gru1_input, &model->sig_net_gru1_recurrent, st->gru1_state, gru1_in, st->arch);
+ compute_glu(&model->sig_net_gru1_glu_gate, gru2_in, st->gru1_state, st->arch);
+
+ for (i=0;i<FARGAN_SUBFRAME_SIZE;i++) gru2_in[SIG_NET_GRU1_OUT_SIZE+i] = pitch_gate[1]*pred[i+2];
+ OPUS_COPY(&gru2_in[SIG_NET_GRU1_OUT_SIZE+FARGAN_SUBFRAME_SIZE], prev, FARGAN_SUBFRAME_SIZE);
+ compute_generic_gru(&model->sig_net_gru2_input, &model->sig_net_gru2_recurrent, st->gru2_state, gru2_in, st->arch);
+ compute_glu(&model->sig_net_gru2_glu_gate, gru3_in, st->gru2_state, st->arch);
+
+ for (i=0;i<FARGAN_SUBFRAME_SIZE;i++) gru3_in[SIG_NET_GRU2_OUT_SIZE+i] = pitch_gate[2]*pred[i+2];
+ OPUS_COPY(&gru3_in[SIG_NET_GRU2_OUT_SIZE+FARGAN_SUBFRAME_SIZE], prev, FARGAN_SUBFRAME_SIZE);
+ compute_generic_gru(&model->sig_net_gru3_input, &model->sig_net_gru3_recurrent, st->gru3_state, gru3_in, st->arch);
+ compute_glu(&model->sig_net_gru3_glu_gate, &skip_cat[SIG_NET_GRU1_OUT_SIZE+SIG_NET_GRU2_OUT_SIZE], st->gru3_state, st->arch);
+
+ OPUS_COPY(skip_cat, gru2_in, SIG_NET_GRU1_OUT_SIZE);
+ OPUS_COPY(&skip_cat[SIG_NET_GRU1_OUT_SIZE], gru3_in, SIG_NET_GRU2_OUT_SIZE);
+ OPUS_COPY(&skip_cat[SIG_NET_GRU1_OUT_SIZE+SIG_NET_GRU2_OUT_SIZE+SIG_NET_GRU3_OUT_SIZE], gru1_in, SIG_NET_FWC0_CONV_OUT_SIZE);
+ for (i=0;i<FARGAN_SUBFRAME_SIZE;i++) skip_cat[SIG_NET_GRU1_OUT_SIZE+SIG_NET_GRU2_OUT_SIZE+SIG_NET_GRU3_OUT_SIZE+SIG_NET_FWC0_CONV_OUT_SIZE+i] = pitch_gate[3]*pred[i+2];
+ OPUS_COPY(&skip_cat[SIG_NET_GRU1_OUT_SIZE+SIG_NET_GRU2_OUT_SIZE+SIG_NET_GRU3_OUT_SIZE+SIG_NET_FWC0_CONV_OUT_SIZE+FARGAN_SUBFRAME_SIZE], prev, FARGAN_SUBFRAME_SIZE);
+
+ compute_generic_dense(&model->sig_net_skip_dense, skip_out, skip_cat, ACTIVATION_TANH, st->arch);
+ compute_glu(&model->sig_net_skip_glu_gate, skip_out, skip_out, st->arch);
+
+ compute_generic_dense(&model->sig_net_sig_dense_out, pcm, skip_out, ACTIVATION_TANH, st->arch);
+ for (i=0;i<FARGAN_SUBFRAME_SIZE;i++) pcm[i] *= gain;
+
+ OPUS_MOVE(st->pitch_buf, &st->pitch_buf[FARGAN_SUBFRAME_SIZE], PITCH_MAX_PERIOD-FARGAN_SUBFRAME_SIZE);
+ OPUS_COPY(&st->pitch_buf[PITCH_MAX_PERIOD-FARGAN_SUBFRAME_SIZE], pcm, FARGAN_SUBFRAME_SIZE);
+ fargan_deemphasis(pcm, &st->deemph_mem);
+}
+
+void fargan_cont(FARGANState *st, const float *pcm0, const float *features0)
+{
+ int i;
+ float cond[COND_NET_FDENSE2_OUT_SIZE];
+ float x0[FARGAN_CONT_SAMPLES];
+ float dummy[FARGAN_SUBFRAME_SIZE];
+ int period=0;
+
+ /* Pre-load features. */
+ for (i=0;i<5;i++) {
+ const float *features = &features0[i*NB_FEATURES];
+ st->last_period = period;
+ period = (int)floor(.5+256./pow(2.f,((1./60.)*((features[NB_BANDS]+1.5)*60))));
+ compute_fargan_cond(st, cond, features, period);
+ }
+
+ x0[0] = 0;
+ for (i=1;i<FARGAN_CONT_SAMPLES;i++) {
+ x0[i] = pcm0[i] - FARGAN_DEEMPHASIS*pcm0[i-1];
+ }
+
+ OPUS_COPY(&st->pitch_buf[PITCH_MAX_PERIOD-FARGAN_FRAME_SIZE], x0, FARGAN_FRAME_SIZE);
+ st->cont_initialized = 1;
+
+ for (i=0;i<FARGAN_NB_SUBFRAMES;i++) {
+ run_fargan_subframe(st, dummy, &cond[i*FARGAN_COND_SIZE], st->last_period);
+ OPUS_COPY(&st->pitch_buf[PITCH_MAX_PERIOD-FARGAN_SUBFRAME_SIZE], &x0[FARGAN_FRAME_SIZE+i*FARGAN_SUBFRAME_SIZE], FARGAN_SUBFRAME_SIZE);
+ }
+ st->deemph_mem = pcm0[FARGAN_CONT_SAMPLES-1];
+}
+
+
+void fargan_init(FARGANState *st)
+{
+ int ret;
+ OPUS_CLEAR(st, 1);
+ st->arch = opus_select_arch();
+#ifndef USE_WEIGHTS_FILE
+ ret = init_fargan(&st->model, fargan_arrays);
+#else
+ ret = 0;
+#endif
+ celt_assert(ret == 0);
+}
+
+int fargan_load_model(FARGANState *st, const void *data, int len) {
+ WeightArray *list;
+ int ret;
+ parse_weights(&list, data, len);
+ ret = init_fargan(&st->model, list);
+ opus_free(list);
+ if (ret == 0) return 0;
+ else return -1;
+}
+
+static void fargan_synthesize_impl(FARGANState *st, float *pcm, const float *features)
+{
+ int subframe;
+ float cond[COND_NET_FDENSE2_OUT_SIZE];
+ int period;
+ celt_assert(st->cont_initialized);
+
+ period = (int)floor(.5+256./pow(2.f,((1./60.)*((features[NB_BANDS]+1.5)*60))));
+ compute_fargan_cond(st, cond, features, period);
+ for (subframe=0;subframe<FARGAN_NB_SUBFRAMES;subframe++) {
+ float *sub_cond;
+ sub_cond = &cond[subframe*FARGAN_COND_SIZE];
+ run_fargan_subframe(st, &pcm[subframe*FARGAN_SUBFRAME_SIZE], sub_cond, st->last_period);
+ }
+ st->last_period = period;
+}
+
+void fargan_synthesize(FARGANState *st, float *pcm, const float *features)
+{
+ fargan_synthesize_impl(st, pcm, features);
+}
+
+void fargan_synthesize_int(FARGANState *st, opus_int16 *pcm, const float *features)
+{
+ int i;
+ float fpcm[FARGAN_FRAME_SIZE];
+ fargan_synthesize(st, fpcm, features);
+ for (i=0;i<LPCNET_FRAME_SIZE;i++) pcm[i] = (int)floor(.5 + MIN32(32767, MAX32(-32767, 32768.f*fpcm[i])));
+}
diff --git a/dnn/fargan.h b/dnn/fargan.h
new file mode 100644
index 00000000..d44ae89b
--- /dev/null
+++ b/dnn/fargan.h
@@ -0,0 +1,68 @@
+/* Copyright (c) 2023 Amazon */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef FARGAN_H
+#define FARGAN_H
+
+#include "freq.h"
+#include "fargan_data.h"
+#include "pitchdnn.h"
+
+#define FARGAN_CONT_SAMPLES 320
+#define FARGAN_NB_SUBFRAMES 4
+#define FARGAN_SUBFRAME_SIZE 40
+#define FARGAN_FRAME_SIZE (FARGAN_NB_SUBFRAMES*FARGAN_SUBFRAME_SIZE)
+#define FARGAN_COND_SIZE (COND_NET_FDENSE2_OUT_SIZE/FARGAN_NB_SUBFRAMES)
+#define FARGAN_DEEMPHASIS 0.85f
+
+#define SIG_NET_INPUT_SIZE (FARGAN_COND_SIZE+2*FARGAN_SUBFRAME_SIZE+4)
+#define SIG_NET_FWC0_STATE_SIZE (2*SIG_NET_INPUT_SIZE)
+
+#define FARGAN_MAX_RNN_NEURONS SIG_NET_GRU1_OUT_SIZE
+typedef struct {
+ FARGAN model;
+ int arch;
+ int cont_initialized;
+ float deemph_mem;
+ float pitch_buf[PITCH_MAX_PERIOD];
+ float cond_conv1_state[COND_NET_FCONV1_STATE_SIZE];
+ float fwc0_mem[SIG_NET_FWC0_STATE_SIZE];
+ float gru1_state[SIG_NET_GRU1_STATE_SIZE];
+ float gru2_state[SIG_NET_GRU2_STATE_SIZE];
+ float gru3_state[SIG_NET_GRU3_STATE_SIZE];
+ int last_period;
+} FARGANState;
+
+void fargan_init(FARGANState *st);
+int fargan_load_model(FARGANState *st, const void *data, int len);
+
+void fargan_cont(FARGANState *st, const float *pcm0, const float *features0);
+
+void fargan_synthesize(FARGANState *st, float *pcm, const float *features);
+void fargan_synthesize_int(FARGANState *st, opus_int16 *pcm, const float *features);
+
+
+#endif /* FARGAN_H */
diff --git a/dnn/freq.c b/dnn/freq.c
new file mode 100644
index 00000000..8e91649a
--- /dev/null
+++ b/dnn/freq.c
@@ -0,0 +1,328 @@
+/* Copyright (c) 2017-2018 Mozilla */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "kiss_fft.h"
+#include <math.h>
+#include "freq.h"
+#include "pitch.h"
+#include "arch.h"
+#include "burg.h"
+#include <assert.h>
+#include "os_support.h"
+
+#define SQUARE(x) ((x)*(x))
+
+static const opus_int16 eband5ms[] = {
+/*0 200 400 600 800 1k 1.2 1.4 1.6 2k 2.4 2.8 3.2 4k 4.8 5.6 6.8 8k*/
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 34, 40
+};
+
+static const float compensation[] = {
+ 0.8f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.666667f, 0.5f, 0.5f, 0.5f, 0.333333f, 0.25f, 0.25f, 0.2f, 0.166667f, 0.173913f
+};
+
+
+extern const kiss_fft_state kfft;
+extern const float half_window[OVERLAP_SIZE];
+extern const float dct_table[NB_BANDS*NB_BANDS];
+
+
+static void compute_band_energy_inverse(float *bandE, const kiss_fft_cpx *X) {
+ int i;
+ float sum[NB_BANDS] = {0};
+ for (i=0;i<NB_BANDS-1;i++)
+ {
+ int j;
+ int band_size;
+ band_size = (eband5ms[i+1]-eband5ms[i])*WINDOW_SIZE_5MS;
+ for (j=0;j<band_size;j++) {
+ float tmp;
+ float frac = (float)j/band_size;
+ tmp = SQUARE(X[(eband5ms[i]*WINDOW_SIZE_5MS) + j].r);
+ tmp += SQUARE(X[(eband5ms[i]*WINDOW_SIZE_5MS) + j].i);
+ tmp = 1.f/(tmp + 1e-9);
+ sum[i] += (1-frac)*tmp;
+ sum[i+1] += frac*tmp;
+ }
+ }
+ sum[0] *= 2;
+ sum[NB_BANDS-1] *= 2;
+ for (i=0;i<NB_BANDS;i++)
+ {
+ bandE[i] = sum[i];
+ }
+}
+
+static float lpcn_lpc(
+ opus_val16 *lpc, /* out: [0...p-1] LPC coefficients */
+ opus_val16 *rc,
+const opus_val32 *ac, /* in: [0...p] autocorrelation values */
+int p
+)
+{
+ int i, j;
+ opus_val32 r;
+ opus_val32 error = ac[0];
+
+ OPUS_CLEAR(lpc, p);
+ OPUS_CLEAR(rc, p);
+ if (ac[0] != 0)
+ {
+ for (i = 0; i < p; i++) {
+ /* Sum up this iteration's reflection coefficient */
+ opus_val32 rr = 0;
+ for (j = 0; j < i; j++)
+ rr += MULT32_32_Q31(lpc[j],ac[i - j]);
+ rr += SHR32(ac[i + 1],3);
+ r = -SHL32(rr,3)/error;
+ rc[i] = r;
+ /* Update LPC coefficients and total error */
+ lpc[i] = SHR32(r,3);
+ for (j = 0; j < (i+1)>>1; j++)
+ {
+ opus_val32 tmp1, tmp2;
+ tmp1 = lpc[j];
+ tmp2 = lpc[i-1-j];
+ lpc[j] = tmp1 + MULT32_32_Q31(r,tmp2);
+ lpc[i-1-j] = tmp2 + MULT32_32_Q31(r,tmp1);
+ }
+
+ error = error - MULT32_32_Q31(MULT32_32_Q31(r,r),error);
+ /* Bail out once we get 30 dB gain */
+ if (error<.001f*ac[0])
+ break;
+ }
+ }
+ return error;
+}
+
+
+
+void lpcn_compute_band_energy(float *bandE, const kiss_fft_cpx *X) {
+ int i;
+ float sum[NB_BANDS] = {0};
+ for (i=0;i<NB_BANDS-1;i++)
+ {
+ int j;
+ int band_size;
+ band_size = (eband5ms[i+1]-eband5ms[i])*WINDOW_SIZE_5MS;
+ for (j=0;j<band_size;j++) {
+ float tmp;
+ float frac = (float)j/band_size;
+ tmp = SQUARE(X[(eband5ms[i]*WINDOW_SIZE_5MS) + j].r);
+ tmp += SQUARE(X[(eband5ms[i]*WINDOW_SIZE_5MS) + j].i);
+ sum[i] += (1-frac)*tmp;
+ sum[i+1] += frac*tmp;
+ }
+ }
+ sum[0] *= 2;
+ sum[NB_BANDS-1] *= 2;
+ for (i=0;i<NB_BANDS;i++)
+ {
+ bandE[i] = sum[i];
+ }
+}
+
+static void compute_burg_cepstrum(const float *pcm, float *burg_cepstrum, int len, int order) {
+ int i;
+ float burg_in[FRAME_SIZE];
+ float burg_lpc[LPC_ORDER];
+ float x[WINDOW_SIZE];
+ float Eburg[NB_BANDS];
+ float g;
+ kiss_fft_cpx LPC[FREQ_SIZE];
+ float Ly[NB_BANDS];
+ float logMax = -2;
+ float follow = -2;
+ assert(order <= LPC_ORDER);
+ assert(len <= FRAME_SIZE);
+ for (i=0;i<len-1;i++) burg_in[i] = pcm[i+1] - PREEMPHASIS*pcm[i];
+ g = silk_burg_analysis(burg_lpc, burg_in, 1e-3, len-1, 1, order);
+ g /= len - 2*(order-1);
+ OPUS_CLEAR(x, WINDOW_SIZE);
+ x[0] = 1;
+ for (i=0;i<order;i++) x[i+1] = -burg_lpc[i]*pow(.995, i+1);
+ forward_transform(LPC, x);
+ compute_band_energy_inverse(Eburg, LPC);
+ for (i=0;i<NB_BANDS;i++) Eburg[i] *= .45*g*(1.f/((float)WINDOW_SIZE*WINDOW_SIZE*WINDOW_SIZE));
+ for (i=0;i<NB_BANDS;i++) {
+ Ly[i] = log10(1e-2+Eburg[i]);
+ Ly[i] = MAX16(logMax-8, MAX16(follow-2.5, Ly[i]));
+ logMax = MAX16(logMax, Ly[i]);
+ follow = MAX16(follow-2.5, Ly[i]);
+ }
+ dct(burg_cepstrum, Ly);
+ burg_cepstrum[0] += - 4;
+}
+
+void burg_cepstral_analysis(float *ceps, const float *x) {
+ int i;
+ compute_burg_cepstrum(x, &ceps[0 ], FRAME_SIZE/2, LPC_ORDER);
+ compute_burg_cepstrum(&x[FRAME_SIZE/2], &ceps[NB_BANDS], FRAME_SIZE/2, LPC_ORDER);
+ for (i=0;i<NB_BANDS;i++) {
+ float c0, c1;
+ c0 = ceps[i];
+ c1 = ceps[NB_BANDS+i];
+ ceps[i ] = .5*(c0+c1);
+ ceps[NB_BANDS+i] = (c0-c1);
+ }
+}
+
+
+static void interp_band_gain(float *g, const float *bandE) {
+ int i;
+ memset(g, 0, FREQ_SIZE);
+ for (i=0;i<NB_BANDS-1;i++)
+ {
+ int j;
+ int band_size;
+ band_size = (eband5ms[i+1]-eband5ms[i])*WINDOW_SIZE_5MS;
+ for (j=0;j<band_size;j++) {
+ float frac = (float)j/band_size;
+ g[(eband5ms[i]*WINDOW_SIZE_5MS) + j] = (1-frac)*bandE[i] + frac*bandE[i+1];
+ }
+ }
+}
+
+
+void dct(float *out, const float *in) {
+ int i;
+ for (i=0;i<NB_BANDS;i++) {
+ int j;
+ float sum = 0;
+ for (j=0;j<NB_BANDS;j++) {
+ sum += in[j] * dct_table[j*NB_BANDS + i];
+ }
+ out[i] = sum*sqrt(2./NB_BANDS);
+ }
+}
+
+static void idct(float *out, const float *in) {
+ int i;
+ for (i=0;i<NB_BANDS;i++) {
+ int j;
+ float sum = 0;
+ for (j=0;j<NB_BANDS;j++) {
+ sum += in[j] * dct_table[i*NB_BANDS + j];
+ }
+ out[i] = sum*sqrt(2./NB_BANDS);
+ }
+}
+
+void forward_transform(kiss_fft_cpx *out, const float *in) {
+ int i;
+ kiss_fft_cpx x[WINDOW_SIZE];
+ kiss_fft_cpx y[WINDOW_SIZE];
+ for (i=0;i<WINDOW_SIZE;i++) {
+ x[i].r = in[i];
+ x[i].i = 0;
+ }
+ opus_fft(&kfft, x, y, 0);
+ for (i=0;i<FREQ_SIZE;i++) {
+ out[i] = y[i];
+ }
+}
+
+static void inverse_transform(float *out, const kiss_fft_cpx *in) {
+ int i;
+ kiss_fft_cpx x[WINDOW_SIZE];
+ kiss_fft_cpx y[WINDOW_SIZE];
+ for (i=0;i<FREQ_SIZE;i++) {
+ x[i] = in[i];
+ }
+ for (;i<WINDOW_SIZE;i++) {
+ x[i].r = x[WINDOW_SIZE - i].r;
+ x[i].i = -x[WINDOW_SIZE - i].i;
+ }
+ opus_fft(&kfft, x, y, 0);
+ /* output in reverse order for IFFT. */
+ out[0] = WINDOW_SIZE*y[0].r;
+ for (i=1;i<WINDOW_SIZE;i++) {
+ out[i] = WINDOW_SIZE*y[WINDOW_SIZE - i].r;
+ }
+}
+
+static float lpc_from_bands(float *lpc, const float *Ex)
+{
+ int i;
+ float e;
+ float ac[LPC_ORDER+1];
+ float rc[LPC_ORDER];
+ float Xr[FREQ_SIZE];
+ kiss_fft_cpx X_auto[FREQ_SIZE];
+ float x_auto[WINDOW_SIZE];
+ interp_band_gain(Xr, Ex);
+ Xr[FREQ_SIZE-1] = 0;
+ OPUS_CLEAR(X_auto, FREQ_SIZE);
+ for (i=0;i<FREQ_SIZE;i++) X_auto[i].r = Xr[i];
+ inverse_transform(x_auto, X_auto);
+ for (i=0;i<LPC_ORDER+1;i++) ac[i] = x_auto[i];
+
+ /* -40 dB noise floor. */
+ ac[0] += ac[0]*1e-4 + 320/12/38.;
+ /* Lag windowing. */
+ for (i=1;i<LPC_ORDER+1;i++) ac[i] *= (1 - 6e-5*i*i);
+ e = lpcn_lpc(lpc, rc, ac, LPC_ORDER);
+ return e;
+}
+
+void lpc_weighting(float *lpc, float gamma)
+{
+ int i;
+ float gamma_i = gamma;
+ for (i = 0; i < LPC_ORDER; i++)
+ {
+ lpc[i] *= gamma_i;
+ gamma_i *= gamma;
+ }
+}
+
+float lpc_from_cepstrum(float *lpc, const float *cepstrum)
+{
+ int i;
+ float Ex[NB_BANDS];
+ float tmp[NB_BANDS];
+ OPUS_COPY(tmp, cepstrum, NB_BANDS);
+ tmp[0] += 4;
+ idct(Ex, tmp);
+ for (i=0;i<NB_BANDS;i++) Ex[i] = pow(10.f, Ex[i])*compensation[i];
+ return lpc_from_bands(lpc, Ex);
+}
+
+void apply_window(float *x) {
+ int i;
+ for (i=0;i<OVERLAP_SIZE;i++) {
+ x[i] *= half_window[i];
+ x[WINDOW_SIZE - 1 - i] *= half_window[i];
+ }
+}
diff --git a/dnn/freq.h b/dnn/freq.h
new file mode 100644
index 00000000..99ebf567
--- /dev/null
+++ b/dnn/freq.h
@@ -0,0 +1,61 @@
+/* Copyright (c) 2017-2018 Mozilla */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifndef FREQ_H
+#define FREQ_H
+
+#include "kiss_fft.h"
+
+#define LPC_ORDER 16
+
+#define PREEMPHASIS (0.85f)
+
+#define FRAME_SIZE_5MS (2)
+#define OVERLAP_SIZE_5MS (2)
+#define TRAINING_OFFSET_5MS (1)
+
+#define WINDOW_SIZE_5MS (FRAME_SIZE_5MS + OVERLAP_SIZE_5MS)
+
+#define FRAME_SIZE (80*FRAME_SIZE_5MS)
+#define OVERLAP_SIZE (80*OVERLAP_SIZE_5MS)
+#define TRAINING_OFFSET (80*TRAINING_OFFSET_5MS)
+#define WINDOW_SIZE (FRAME_SIZE + OVERLAP_SIZE)
+#define FREQ_SIZE (WINDOW_SIZE/2 + 1)
+
+#define NB_BANDS 18
+#define NB_BANDS_1 (NB_BANDS - 1)
+
+void lpcn_compute_band_energy(float *bandE, const kiss_fft_cpx *X);
+void burg_cepstral_analysis(float *ceps, const float *x);
+
+void apply_window(float *x);
+void dct(float *out, const float *in);
+void forward_transform(kiss_fft_cpx *out, const float *in);
+float lpc_from_cepstrum(float *lpc, const float *cepstrum);
+void apply_window(float *x);
+void lpc_weighting(float *lpc, float gamma);
+
+#endif
diff --git a/dnn/fwgan.c b/dnn/fwgan.c
new file mode 100644
index 00000000..301c73f6
--- /dev/null
+++ b/dnn/fwgan.c
@@ -0,0 +1,322 @@
+/* Copyright (c) 2023 Amazon */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fwgan.h"
+#include "os_support.h"
+#include "freq.h"
+#include "fwgan_data.h"
+#include "lpcnet.h"
+#include "pitch.h"
+#include "nnet.h"
+#include "lpcnet_private.h"
+
+#define FEAT_IN_SIZE (BFCC_WITH_CORR_UPSAMPLER_FC_OUT_SIZE/4 + FWGAN_FRAME_SIZE/2)
+
+#define FWGAN_FEATURES (NB_FEATURES-1)
+
+static void pitch_embeddings(float *pembed, float *phase, double w0) {
+ int i;
+ float wreal, wimag;
+#if 1
+ /* This Taylor expansion should be good enough since w0 is always small. */
+ float w2 = w0*w0;
+ wreal = 1 - .5*w2*(1.f - 0.083333333f*w2);
+ wimag = w0*(1 - 0.166666667f*w2*(1.f - 0.05f*w2));
+#else
+ wreal = cos(w0);
+ wimag = sin(w0);
+#endif
+ /* Speed-up phase reference by making phase a unit-norm complex value and rotating it
+ by exp(-i*w0) each sample. */
+ for (i=0;i<SUBFRAME_SIZE;i++) {
+ float tmp;
+ tmp = phase[0]*wreal - phase[1]*wimag;
+ phase[1] = phase[0]*wimag + phase[1]*wreal;
+ phase[0] = tmp;
+ pembed[i] = phase[1];
+ pembed[SUBFRAME_SIZE+i] = phase[0];
+ }
+ /* Renormalize once per sub-frame, though we could probably do it even less frequently. */
+ {
+ float r = 1.f/sqrt(phase[0]*phase[0] + phase[1]*phase[1]);
+ phase[0] *= r;
+ phase[1] *= r;
+ }
+}
+
+static void compute_wlpc(float lpc[LPC_ORDER], const float *features) {
+ float lpc_weight;
+ int i;
+ lpc_from_cepstrum(lpc, features);
+ lpc_weight = 1.f;
+ for (i=0;i<LPC_ORDER;i++) {
+ lpc_weight *= FWGAN_GAMMA;
+ lpc[i] *= lpc_weight;
+ }
+}
+
+static void run_fwgan_upsampler(FWGANState *st, float *cond, const float *features)
+{
+ FWGAN *model;
+ model = &st->model;
+ celt_assert(FWGAN_FEATURES == model->bfcc_with_corr_upsampler_fc.nb_inputs);
+ celt_assert(BFCC_WITH_CORR_UPSAMPLER_FC_OUT_SIZE == model->bfcc_with_corr_upsampler_fc.nb_outputs);
+ compute_generic_dense(&model->bfcc_with_corr_upsampler_fc, cond, features, ACTIVATION_TANH);
+}
+
+static void fwgan_synthesize_impl(FWGANState *st, float *pcm, const float *lpc, const float *features);
+void fwgan_cont(FWGANState *st, const float *pcm0, const float *features0)
+{
+ int i;
+ float norm2, norm_1;
+ float wpcm0[CONT_PCM_INPUTS];
+ float cont_inputs[CONT_PCM_INPUTS+1];
+ float tmp1[MAX_CONT_SIZE];
+ float tmp2[MAX_CONT_SIZE];
+ float lpc[LPC_ORDER];
+ float new_pcm[FWGAN_FRAME_SIZE];
+ FWGAN *model;
+ st->embed_phase[0] = 1;
+ model = &st->model;
+ compute_wlpc(lpc, features0);
+ /* Deemphasis memory is just the last continuation sample. */
+ st->deemph_mem = pcm0[CONT_PCM_INPUTS-1];
+
+ /* Apply analysis filter, considering that the preemphasis and deemphasis filter
+ cancel each other in this case since the LPC filter is constant across that boundary.
+ */
+ for (i=LPC_ORDER;i<CONT_PCM_INPUTS;i++) {
+ int j;
+ wpcm0[i] = pcm0[i];
+ for (j=0;j<LPC_ORDER;j++) wpcm0[i] += lpc[j]*pcm0[i-j-1];
+ }
+ /* FIXME: Make this less stupid. */
+ for (i=0;i<LPC_ORDER;i++) wpcm0[i] = wpcm0[LPC_ORDER];
+
+ /* The memory of the pre-empahsis is the last sample of the weighted signal
+ (ignoring preemphasis+deemphasis combination). */
+ st->preemph_mem = wpcm0[CONT_PCM_INPUTS-1];
+ /* The memory of the synthesis filter is the pre-emphasized continuation. */
+ for (i=0;i<LPC_ORDER;i++) st->syn_mem[i] = pcm0[CONT_PCM_INPUTS-1-i] - FWGAN_DEEMPHASIS*pcm0[CONT_PCM_INPUTS-2-i];
+
+ norm2 = celt_inner_prod(wpcm0, wpcm0, CONT_PCM_INPUTS, st->arch);
+ norm_1 = 1.f/sqrt(1e-8f + norm2);
+ for (i=0;i<CONT_PCM_INPUTS;i++) cont_inputs[i+1] = norm_1*wpcm0[i];
+ cont_inputs[0] = log(sqrt(norm2) + 1e-7f);
+
+ /* Continuation network */
+ compute_generic_dense(&model->cont_net_0, tmp1, cont_inputs, ACTIVATION_TANH);
+ compute_generic_dense(&model->cont_net_2, tmp2, tmp1, ACTIVATION_TANH);
+ compute_generic_dense(&model->cont_net_4, tmp1, tmp2, ACTIVATION_TANH);
+ compute_generic_dense(&model->cont_net_6, tmp2, tmp1, ACTIVATION_TANH);
+ compute_generic_dense(&model->cont_net_8, tmp1, tmp2, ACTIVATION_TANH);
+ celt_assert(CONT_NET_10_OUT_SIZE == model->cont_net_10.nb_outputs);
+ compute_generic_dense(&model->cont_net_10, st->cont, tmp1, ACTIVATION_TANH);
+
+ /* Computing continuation for each layer. */
+ celt_assert(RNN_GRU_STATE_SIZE == model->rnn_cont_fc_0.nb_outputs);
+ compute_generic_dense(&model->rnn_cont_fc_0, st->rnn_state, st->cont, ACTIVATION_TANH);
+
+ celt_assert(FWC1_STATE_SIZE == model->fwc1_cont_fc_0.nb_outputs);
+ compute_generic_dense(&model->fwc1_cont_fc_0, st->fwc1_state, st->cont, ACTIVATION_TANH);
+ celt_assert(FWC2_STATE_SIZE == model->fwc2_cont_fc_0.nb_outputs);
+ compute_generic_dense(&model->fwc2_cont_fc_0, st->fwc2_state, st->cont, ACTIVATION_TANH);
+ celt_assert(FWC3_STATE_SIZE == model->fwc3_cont_fc_0.nb_outputs);
+ compute_generic_dense(&model->fwc3_cont_fc_0, st->fwc3_state, st->cont, ACTIVATION_TANH);
+ celt_assert(FWC4_STATE_SIZE == model->fwc4_cont_fc_0.nb_outputs);
+ compute_generic_dense(&model->fwc4_cont_fc_0, st->fwc4_state, st->cont, ACTIVATION_TANH);
+ celt_assert(FWC5_STATE_SIZE == model->fwc5_cont_fc_0.nb_outputs);
+ compute_generic_dense(&model->fwc5_cont_fc_0, st->fwc5_state, st->cont, ACTIVATION_TANH);
+ celt_assert(FWC6_STATE_SIZE == model->fwc6_cont_fc_0.nb_outputs);
+ compute_generic_dense(&model->fwc6_cont_fc_0, st->fwc6_state, st->cont, ACTIVATION_TANH);
+ celt_assert(FWC7_STATE_SIZE == model->fwc7_cont_fc_0.nb_outputs);
+ compute_generic_dense(&model->fwc7_cont_fc_0, st->fwc7_state, st->cont, ACTIVATION_TANH);
+
+ st->cont_initialized = 1;
+ /* Process the first frame, discard the first subframe, and keep the rest for the first
+ synthesis call. */
+ fwgan_synthesize_impl(st, new_pcm, lpc, features0);
+ OPUS_COPY(st->pcm_buf, &new_pcm[SUBFRAME_SIZE], FWGAN_FRAME_SIZE-SUBFRAME_SIZE);
+}
+
+static void apply_gain(float *pcm, float c0, float *last_gain) {
+ int i;
+ float gain = pow(10.f, (0.5f*c0/sqrt(18.f)));
+ for (i=0;i<SUBFRAME_SIZE;i++) pcm[i] *= *last_gain;
+ *last_gain = gain;
+}
+
+static void fwgan_lpc_syn(float *pcm, float *mem, const float *lpc, float last_lpc[LPC_ORDER]) {
+ int i;
+ for (i=0;i<SUBFRAME_SIZE;i++) {
+ int j;
+ for (j=0;j<LPC_ORDER;j++) pcm[i] -= mem[j]*last_lpc[j];
+ OPUS_MOVE(&mem[1], &mem[0], LPC_ORDER-1);
+ mem[0] = pcm[i];
+ }
+ OPUS_COPY(last_lpc, lpc, LPC_ORDER);
+}
+
+static void fwgan_preemphasis(float *pcm, float *preemph_mem) {
+ int i;
+ for (i=0;i<SUBFRAME_SIZE;i++) {
+ float tmp = pcm[i];
+ pcm[i] -= FWGAN_DEEMPHASIS * *preemph_mem;
+ *preemph_mem = tmp;
+ }
+}
+
+static void fwgan_deemphasis(float *pcm, float *deemph_mem) {
+ int i;
+ for (i=0;i<SUBFRAME_SIZE;i++) {
+ pcm[i] += FWGAN_DEEMPHASIS * *deemph_mem;
+ *deemph_mem = pcm[i];
+ }
+}
+
+static void run_fwgan_subframe(FWGANState *st, float *pcm, const float *cond, double w0, const float *lpc, float c0)
+{
+ float tmp1[FWC1_FC_0_OUT_SIZE];
+ float tmp2[IMAX(RNN_GRU_STATE_SIZE, FWC2_FC_0_OUT_SIZE)];
+ float feat_in[FEAT_IN_SIZE];
+ float rnn_in[FEAT_IN_CONV1_CONV_OUT_SIZE];
+ float pembed[FWGAN_FRAME_SIZE/2];
+ FWGAN *model;
+ model = &st->model;
+
+ pitch_embeddings(pembed, st->embed_phase, w0);
+ /* Interleave bfcc_cond and pembed for each subframe in feat_in. */
+ OPUS_COPY(&feat_in[BFCC_WITH_CORR_UPSAMPLER_FC_OUT_SIZE/4], &cond[0], BFCC_WITH_CORR_UPSAMPLER_FC_OUT_SIZE/4);
+ OPUS_COPY(&feat_in[0], &pembed[0], FWGAN_FRAME_SIZE/2);
+
+ compute_generic_conv1d(&model->feat_in_conv1_conv, rnn_in, st->cont_conv1_mem, feat_in, FEAT_IN_CONV1_CONV_IN_SIZE, ACTIVATION_LINEAR);
+ celt_assert(FEAT_IN_NL1_GATE_OUT_SIZE == model->feat_in_nl1_gate.nb_outputs);
+ compute_gated_activation(&model->feat_in_nl1_gate, rnn_in, rnn_in, ACTIVATION_TANH);
+
+ if (st->cont_initialized == 1) {
+ /* On the very first subframe we stop here. We only want to run the feat_in layer since the
+ others are initialized via the continuation network. */
+ OPUS_CLEAR(pcm, SUBFRAME_SIZE);
+ st->cont_initialized = 2;
+ apply_gain(pcm, c0, &st->last_gain);
+ OPUS_COPY(st->last_lpc, lpc, LPC_ORDER);
+ return;
+ }
+
+ compute_generic_gru(&model->rnn_gru_input, &model->rnn_gru_recurrent, st->rnn_state, rnn_in);
+ celt_assert(IMAX(RNN_GRU_STATE_SIZE, FWC2_FC_0_OUT_SIZE) >= model->rnn_nl_gate.nb_outputs);
+ compute_gated_activation(&model->rnn_nl_gate, tmp2, st->rnn_state, ACTIVATION_TANH);
+
+ compute_generic_conv1d(&model->fwc1_fc_0, tmp1, st->fwc1_state, tmp2, RNN_GRU_STATE_SIZE, ACTIVATION_LINEAR);
+ compute_gated_activation(&model->fwc1_fc_1_gate, tmp1, tmp1, ACTIVATION_TANH);
+
+ compute_generic_conv1d(&model->fwc2_fc_0, tmp2, st->fwc2_state, tmp1, FWC1_FC_0_OUT_SIZE, ACTIVATION_LINEAR);
+ compute_gated_activation(&model->fwc2_fc_1_gate, tmp2, tmp2, ACTIVATION_TANH);
+
+ compute_generic_conv1d(&model->fwc3_fc_0, tmp1, st->fwc3_state, tmp2, FWC2_FC_0_OUT_SIZE, ACTIVATION_LINEAR);
+ compute_gated_activation(&model->fwc3_fc_1_gate, tmp1, tmp1, ACTIVATION_TANH);
+
+ compute_generic_conv1d(&model->fwc4_fc_0, tmp2, st->fwc4_state, tmp1, FWC3_FC_0_OUT_SIZE, ACTIVATION_LINEAR);
+ compute_gated_activation(&model->fwc4_fc_1_gate, tmp2, tmp2, ACTIVATION_TANH);
+
+ compute_generic_conv1d(&model->fwc5_fc_0, tmp1, st->fwc5_state, tmp2, FWC4_FC_0_OUT_SIZE, ACTIVATION_LINEAR);
+ compute_gated_activation(&model->fwc5_fc_1_gate, tmp1, tmp1, ACTIVATION_TANH);
+
+ compute_generic_conv1d(&model->fwc6_fc_0, tmp2, st->fwc6_state, tmp1, FWC5_FC_0_OUT_SIZE, ACTIVATION_LINEAR);
+ compute_gated_activation(&model->fwc6_fc_1_gate, tmp2, tmp2, ACTIVATION_TANH);
+
+ compute_generic_conv1d(&model->fwc7_fc_0, tmp1, st->fwc7_state, tmp2, FWC6_FC_0_OUT_SIZE, ACTIVATION_LINEAR);
+ compute_gated_activation(&model->fwc7_fc_1_gate, pcm, tmp1, ACTIVATION_TANH);
+
+ apply_gain(pcm, c0, &st->last_gain);
+ fwgan_preemphasis(pcm, &st->preemph_mem);
+ fwgan_lpc_syn(pcm, st->syn_mem, lpc, st->last_lpc);
+ fwgan_deemphasis(pcm, &st->deemph_mem);
+}
+
+void fwgan_init(FWGANState *st)
+{
+ int ret;
+ OPUS_CLEAR(st, 1);
+ ret = init_fwgan(&st->model, fwgan_arrays);
+ celt_assert(ret == 0);
+ /* FIXME: perform arch detection. */
+}
+
+int fwgan_load_model(FWGANState *st, const unsigned char *data, int len) {
+ WeightArray *list;
+ int ret;
+ parse_weights(&list, data, len);
+ ret = init_fwgan(&st->model, list);
+ opus_free(list);
+ if (ret == 0) return 0;
+ else return -1;
+}
+
+static void fwgan_synthesize_impl(FWGANState *st, float *pcm, const float *lpc, const float *features)
+{
+ int subframe;
+ float cond[BFCC_WITH_CORR_UPSAMPLER_FC_OUT_SIZE];
+ double w0;
+ int period;
+ float fwgan_features[NB_FEATURES-1];
+ celt_assert(st->cont_initialized);
+ OPUS_COPY(fwgan_features, features, NB_FEATURES-2);
+ fwgan_features[NB_FEATURES-2] = features[NB_FEATURES-1]+.5;
+
+ period = (int)floor(.1 + 50*features[NB_BANDS]+100);
+ w0 = 2*M_PI/period;
+ run_fwgan_upsampler(st, cond, fwgan_features);
+ for (subframe=0;subframe<NB_SUBFRAMES;subframe++) {
+ float *sub_cond;
+ sub_cond = &cond[subframe*BFCC_WITH_CORR_UPSAMPLER_FC_OUT_SIZE/4];
+ run_fwgan_subframe(st, &pcm[subframe*SUBFRAME_SIZE], sub_cond, w0, lpc, features[0]);
+ }
+}
+
+void fwgan_synthesize(FWGANState *st, float *pcm, const float *features)
+{
+ float lpc[LPC_ORDER];
+ float new_pcm[FWGAN_FRAME_SIZE];
+ compute_wlpc(lpc, features);
+ fwgan_synthesize_impl(st, new_pcm, lpc, features);
+ /* Handle buffering. */
+ OPUS_COPY(pcm, st->pcm_buf, FWGAN_FRAME_SIZE-SUBFRAME_SIZE);
+ OPUS_COPY(&pcm[FWGAN_FRAME_SIZE-SUBFRAME_SIZE], new_pcm, SUBFRAME_SIZE);
+ OPUS_COPY(st->pcm_buf, &new_pcm[SUBFRAME_SIZE], FWGAN_FRAME_SIZE-SUBFRAME_SIZE);
+}
+
+void fwgan_synthesize_int(FWGANState *st, opus_int16 *pcm, const float *features)
+{
+ int i;
+ float fpcm[FWGAN_FRAME_SIZE];
+ fwgan_synthesize(st, fpcm, features);
+ for (i=0;i<LPCNET_FRAME_SIZE;i++) pcm[i] = (int)floor(.5 + MIN32(32767, MAX32(-32767, 32768.f*fpcm[i])));
+}
diff --git a/dnn/fwgan.h b/dnn/fwgan.h
new file mode 100644
index 00000000..acb7014d
--- /dev/null
+++ b/dnn/fwgan.h
@@ -0,0 +1,83 @@
+/* Copyright (c) 2023 Amazon */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef FWGAN_H
+#define FWGAN_H
+
+#include "freq.h"
+#include "fwgan_data.h"
+
+#define FWGAN_CONT_SAMPLES 320
+#define NB_SUBFRAMES 4
+#define SUBFRAME_SIZE 40
+#define FWGAN_FRAME_SIZE (NB_SUBFRAMES*SUBFRAME_SIZE)
+#define CONT_PCM_INPUTS 320
+#define MAX_CONT_SIZE CONT_NET_0_OUT_SIZE
+#define FWGAN_GAMMA 0.92f
+#define FWGAN_DEEMPHASIS 0.85f
+
+/* FIXME: Derive those from the model rather than hardcoding. */
+#define FWC1_STATE_SIZE 512
+#define FWC2_STATE_SIZE 512
+#define FWC3_STATE_SIZE 256
+#define FWC4_STATE_SIZE 256
+#define FWC5_STATE_SIZE 128
+#define FWC6_STATE_SIZE 128
+#define FWC7_STATE_SIZE 80
+
+typedef struct {
+ FWGAN model;
+ int arch;
+ int cont_initialized;
+ float embed_phase[2];
+ float last_gain;
+ float last_lpc[LPC_ORDER];
+ float syn_mem[LPC_ORDER];
+ float preemph_mem;
+ float deemph_mem;
+ float pcm_buf[FWGAN_FRAME_SIZE];
+ float cont[CONT_NET_10_OUT_SIZE];
+ float cont_conv1_mem[FEAT_IN_CONV1_CONV_STATE_SIZE];
+ float rnn_state[RNN_GRU_STATE_SIZE];
+ float fwc1_state[FWC1_STATE_SIZE];
+ float fwc2_state[FWC2_STATE_SIZE];
+ float fwc3_state[FWC3_STATE_SIZE];
+ float fwc4_state[FWC4_STATE_SIZE];
+ float fwc5_state[FWC5_STATE_SIZE];
+ float fwc6_state[FWC6_STATE_SIZE];
+ float fwc7_state[FWC7_STATE_SIZE];
+} FWGANState;
+
+void fwgan_init(FWGANState *st);
+int fwgan_load_model(FWGANState *st, const unsigned char *data, int len);
+
+void fwgan_cont(FWGANState *st, const float *pcm0, const float *features0);
+
+void fwgan_synthesize(FWGANState *st, float *pcm, const float *features);
+void fwgan_synthesize_int(FWGANState *st, opus_int16 *pcm, const float *features);
+
+
+#endif /* FWGAN_H */
diff --git a/dnn/kiss99.c b/dnn/kiss99.c
new file mode 100644
index 00000000..325918f8
--- /dev/null
+++ b/dnn/kiss99.c
@@ -0,0 +1,81 @@
+/*Daala video codec
+Copyright (c) 2012 Daala project contributors. All rights reserved.
+Author: Timothy B. Terriberry
+
+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 OWNER 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.*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "kiss99.h"
+
+void kiss99_srand(kiss99_ctx *_this,const unsigned char *_data,int _ndata){
+ int i;
+ _this->z=362436069;
+ _this->w=521288629;
+ _this->jsr=123456789;
+ _this->jcong=380116160;
+ for(i=3;i<_ndata;i+=4){
+ _this->z^=_data[i-3];
+ _this->w^=_data[i-2];
+ _this->jsr^=_data[i-1];
+ _this->jcong^=_data[i];
+ kiss99_rand(_this);
+ }
+ if(i-3<_ndata)_this->z^=_data[i-3];
+ if(i-2<_ndata)_this->w^=_data[i-2];
+ if(i-1<_ndata)_this->jsr^=_data[i-1];
+ /*Fix any potential short cycles that show up.
+ These are not too likely, given the way we initialize the state, but they
+ are technically possible, so let us go ahead and eliminate that
+ possibility.
+ See Gregory G. Rose: "KISS: A Bit Too Simple", Cryptographic Communications
+ No. 10, pp. 123---137, 2018.*/
+ if(_this->z==0||_this->z==0x9068FFFF)_this->z++;
+ if(_this->w==0||_this->w==0x464FFFFF)_this->w++;
+ if(_this->jsr==0)_this->jsr++;
+}
+
+uint32_t kiss99_rand(kiss99_ctx *_this){
+ uint32_t znew;
+ uint32_t wnew;
+ uint32_t mwc;
+ uint32_t shr3;
+ uint32_t cong;
+ znew=36969*(_this->z&0xFFFF)+(_this->z>>16);
+ wnew=18000*(_this->w&0xFFFF)+(_this->w>>16);
+ mwc=(znew<<16)+wnew;
+ /*We swap the 13 and 17 from the original 1999 algorithm to produce a single
+ cycle of maximal length, matching KISS11.
+ We are not actually using KISS11 because of the impractically large (16 MB)
+ internal state of the full algorithm.*/
+ shr3=_this->jsr^(_this->jsr<<13);
+ shr3^=shr3>>17;
+ shr3^=shr3<<5;
+ cong=69069*_this->jcong+1234567;
+ _this->z=znew;
+ _this->w=wnew;
+ _this->jsr=shr3;
+ _this->jcong=cong;
+ return (mwc^cong)+shr3;
+}
diff --git a/dnn/kiss99.h b/dnn/kiss99.h
new file mode 100644
index 00000000..28646dbc
--- /dev/null
+++ b/dnn/kiss99.h
@@ -0,0 +1,46 @@
+/*Daala video codec
+Copyright (c) 2012 Daala project contributors. All rights reserved.
+Author: Timothy B. Terriberry
+
+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 OWNER 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.*/
+
+#if !defined(_kiss99_H)
+# define _kiss99_H (1)
+# include <stdint.h>
+
+/*KISS PRNG from George Marsaglia (1999 version).
+ See https://en.wikipedia.org/wiki/KISS_(algorithm) for details.
+ This is suitable for simulations, but not for use in crytographic contexts.*/
+
+typedef struct kiss99_ctx kiss99_ctx;
+
+struct kiss99_ctx{
+ uint32_t z;
+ uint32_t w;
+ uint32_t jsr;
+ uint32_t jcong;
+};
+
+void kiss99_srand(kiss99_ctx *_this,const unsigned char *_data,int _ndata);
+uint32_t kiss99_rand(kiss99_ctx *_this);
+
+#endif
diff --git a/dnn/lossgen.c b/dnn/lossgen.c
new file mode 100644
index 00000000..d0e781a8
--- /dev/null
+++ b/dnn/lossgen.c
@@ -0,0 +1,196 @@
+/* Copyright (c) 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+/* This packet loss simulator can be used independently of the Opus codebase.
+ To do that, you need to compile the following files:
+ dnn/lossgen.c
+ dnn/lossgen_data.c
+
+ with the following files needed as #include
+ dnn/lossgen_data.h
+ dnn/lossgen.h
+ dnn/nnet_arch.h
+ dnn/nnet.h
+ dnn/parse_lpcnet_weights.c (included despite being a C file)
+ dnn/vec_avx.h
+ dnn/vec.h
+ celt/os_support.h
+ celt/arch.h
+ celt/x86/x86_arch_macros.h
+ include/opus_defines.h
+ include/opus_types.h
+
+ Additionally, the code in dnn/lossgen_demo.c can be used to generate losses from
+ the command line.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "arch.h"
+
+#include <math.h>
+#include "lossgen.h"
+#include "os_support.h"
+#include "nnet.h"
+#include "assert.h"
+
+/* Disable RTCD for this. */
+#define RTCD_ARCH c
+
+/* Override assert to avoid undefined/redefined symbols. */
+#undef celt_assert
+#define celt_assert assert
+
+/* Directly include the C files we need since the symbols won't be exposed if we link in a shared object. */
+#include "parse_lpcnet_weights.c"
+#include "nnet_arch.h"
+
+#undef compute_linear
+#undef compute_activation
+
+/* Force the C version since the SIMD versions may be hidden. */
+#define compute_linear(linear, out, in, arch) ((void)(arch),compute_linear_c(linear, out, in))
+#define compute_activation(output, input, N, activation, arch) ((void)(arch),compute_activation_c(output, input, N, activation))
+
+#define MAX_RNN_NEURONS_ALL IMAX(LOSSGEN_GRU1_STATE_SIZE, LOSSGEN_GRU2_STATE_SIZE)
+
+/* These two functions are copied from nnet.c to make sure we don't have linking issues. */
+void compute_generic_gru_lossgen(const LinearLayer *input_weights, const LinearLayer *recurrent_weights, float *state, const float *in, int arch)
+{
+ int i;
+ int N;
+ float zrh[3*MAX_RNN_NEURONS_ALL];
+ float recur[3*MAX_RNN_NEURONS_ALL];
+ float *z;
+ float *r;
+ float *h;
+ celt_assert(3*recurrent_weights->nb_inputs == recurrent_weights->nb_outputs);
+ celt_assert(input_weights->nb_outputs == recurrent_weights->nb_outputs);
+ N = recurrent_weights->nb_inputs;
+ z = zrh;
+ r = &zrh[N];
+ h = &zrh[2*N];
+ celt_assert(recurrent_weights->nb_outputs <= 3*MAX_RNN_NEURONS_ALL);
+ celt_assert(in != state);
+ compute_linear(input_weights, zrh, in, arch);
+ compute_linear(recurrent_weights, recur, state, arch);
+ for (i=0;i<2*N;i++)
+ zrh[i] += recur[i];
+ compute_activation(zrh, zrh, 2*N, ACTIVATION_SIGMOID, arch);
+ for (i=0;i<N;i++)
+ h[i] += recur[2*N+i]*r[i];
+ compute_activation(h, h, N, ACTIVATION_TANH, arch);
+ for (i=0;i<N;i++)
+ h[i] = z[i]*state[i] + (1-z[i])*h[i];
+ for (i=0;i<N;i++)
+ state[i] = h[i];
+}
+
+
+void compute_generic_dense_lossgen(const LinearLayer *layer, float *output, const float *input, int activation, int arch)
+{
+ compute_linear(layer, output, input, arch);
+ compute_activation(output, output, layer->nb_outputs, activation, arch);
+}
+
+
+static int sample_loss_impl(
+ LossGenState *st,
+ float percent_loss)
+{
+ float input[2];
+ float tmp[LOSSGEN_DENSE_IN_OUT_SIZE];
+ float out;
+ int loss;
+ LossGen *model = &st->model;
+ input[0] = st->last_loss;
+ input[1] = percent_loss;
+ compute_generic_dense_lossgen(&model->lossgen_dense_in, tmp, input, ACTIVATION_TANH, 0);
+ compute_generic_gru_lossgen(&model->lossgen_gru1_input, &model->lossgen_gru1_recurrent, st->gru1_state, tmp, 0);
+ compute_generic_gru_lossgen(&model->lossgen_gru2_input, &model->lossgen_gru2_recurrent, st->gru2_state, st->gru1_state, 0);
+ compute_generic_dense_lossgen(&model->lossgen_dense_out, &out, st->gru2_state, ACTIVATION_SIGMOID, 0);
+ loss = (float)rand()/RAND_MAX < out;
+ st->last_loss = loss;
+ return loss;
+}
+
+int sample_loss(
+ LossGenState *st,
+ float percent_loss)
+{
+ /* Due to GRU being initialized with zeros, the first packets aren't quite random,
+ so we skip them. */
+ if (!st->used) {
+ int i;
+ for (i=0;i<100;i++) sample_loss_impl(st, percent_loss);
+ st->used = 1;
+ }
+ return sample_loss_impl(st, percent_loss);
+}
+
+void lossgen_init(LossGenState *st)
+{
+ int ret;
+ OPUS_CLEAR(st, 1);
+#ifndef USE_WEIGHTS_FILE
+ ret = init_lossgen(&st->model, lossgen_arrays);
+#else
+ ret = 0;
+#endif
+ celt_assert(ret == 0);
+ (void)ret;
+}
+
+int lossgen_load_model(LossGenState *st, const void *data, int len) {
+ WeightArray *list;
+ int ret;
+ parse_weights(&list, data, len);
+ ret = init_lossgen(&st->model, list);
+ opus_free(list);
+ if (ret == 0) return 0;
+ else return -1;
+}
+
+#if 0
+#include <stdio.h>
+int main(int argc, char **argv) {
+ int i, N;
+ float p;
+ LossGenState st;
+ if (argc!=3) {
+ fprintf(stderr, "usage: lossgen <percentage> <length>\n");
+ return 1;
+ }
+ lossgen_init(&st);
+ p = atof(argv[1]);
+ N = atoi(argv[2]);
+ for (i=0;i<N;i++) {
+ printf("%d\n", sample_loss(&st, p));
+ }
+}
+#endif
diff --git a/dnn/lossgen.h b/dnn/lossgen.h
new file mode 100644
index 00000000..318f32f7
--- /dev/null
+++ b/dnn/lossgen.h
@@ -0,0 +1,55 @@
+/* Copyright (c) 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifndef LOSSGEN_H
+#define LOSSGEN_H
+
+
+#include "lossgen_data.h"
+
+#define PITCH_MIN_PERIOD 32
+#define PITCH_MAX_PERIOD 256
+
+#define NB_XCORR_FEATURES (PITCH_MAX_PERIOD-PITCH_MIN_PERIOD)
+
+
+typedef struct {
+ LossGen model;
+ float gru1_state[LOSSGEN_GRU1_STATE_SIZE];
+ float gru2_state[LOSSGEN_GRU2_STATE_SIZE];
+ int last_loss;
+ int used;
+} LossGenState;
+
+
+void lossgen_init(LossGenState *st);
+int lossgen_load_model(LossGenState *st, const void *data, int len);
+
+int sample_loss(
+ LossGenState *st,
+ float percent_loss);
+
+#endif
diff --git a/dnn/lossgen_demo.c b/dnn/lossgen_demo.c
new file mode 100644
index 00000000..bad7bdc3
--- /dev/null
+++ b/dnn/lossgen_demo.c
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "lossgen.h"
+int main(int argc, char **argv)
+{
+ LossGenState st;
+ long num_packets;
+ long i;
+ float percent;
+ if (argc != 3) {
+ fprintf(stderr, "usage: %s <percent_loss> <nb packets>\n", argv[0]);
+ return 1;
+ }
+ lossgen_init(&st);
+ percent = atof(argv[1]);
+ num_packets = atol(argv[2]);
+ /*printf("loss: %f %d\n", percent, num_packets);*/
+ for (i=0;i<num_packets;i++) {
+ printf("%d\n", sample_loss(&st, percent*0.01f));
+ }
+ return 0;
+}
diff --git a/dnn/lpcnet.c b/dnn/lpcnet.c
new file mode 100644
index 00000000..52e81b07
--- /dev/null
+++ b/dnn/lpcnet.c
@@ -0,0 +1,283 @@
+/* Copyright (c) 2018 Mozilla */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <stdio.h>
+#include "nnet_data.h"
+#include "nnet.h"
+#include "common.h"
+#include "arch.h"
+#include "lpcnet.h"
+#include "lpcnet_private.h"
+#include "os_support.h"
+
+#define PREEMPH 0.85f
+
+#define PDF_FLOOR 0.002
+
+#define FRAME_INPUT_SIZE (NB_FEATURES + EMBED_PITCH_OUT_SIZE)
+
+
+#if 0
+static void print_vector(float *x, int N)
+{
+ int i;
+ for (i=0;i<N;i++) printf("%f ", x[i]);
+ printf("\n");
+}
+#endif
+
+#ifdef END2END
+void rc2lpc(float *lpc, const float *rc)
+{
+ int i, j, k;
+ float tmp[LPC_ORDER];
+ float ntmp[LPC_ORDER] = {0.0};
+ OPUS_COPY(tmp, rc, LPC_ORDER);
+ for(i = 0; i < LPC_ORDER ; i++)
+ {
+ for(j = 0; j <= i-1; j++)
+ {
+ ntmp[j] = tmp[j] + tmp[i]*tmp[i - j - 1];
+ }
+ for(k = 0; k <= i-1; k++)
+ {
+ tmp[k] = ntmp[k];
+ }
+ }
+ for(i = 0; i < LPC_ORDER ; i++)
+ {
+ lpc[i] = tmp[i];
+ }
+}
+
+#endif
+
+void run_frame_network(LPCNetState *lpcnet, float *gru_a_condition, float *gru_b_condition, float *lpc, const float *features)
+{
+ NNetState *net;
+ float condition[FEATURE_DENSE2_OUT_SIZE];
+ float in[FRAME_INPUT_SIZE];
+ float conv1_out[FEATURE_CONV1_OUT_SIZE];
+ float conv2_out[FEATURE_CONV2_OUT_SIZE];
+ float dense1_out[FEATURE_DENSE1_OUT_SIZE];
+ int pitch;
+ float rc[LPC_ORDER];
+ /* Matches the Python code -- the 0.1 avoids rounding issues. */
+ pitch = (int)floor(.1 + 50*features[NB_BANDS]+100);
+ pitch = IMIN(255, IMAX(33, pitch));
+ net = &lpcnet->nnet;
+ OPUS_COPY(in, features, NB_FEATURES);
+ compute_embedding(&lpcnet->model.embed_pitch, &in[NB_FEATURES], pitch);
+ compute_conv1d(&lpcnet->model.feature_conv1, conv1_out, net->feature_conv1_state, in);
+ if (lpcnet->frame_count < FEATURE_CONV1_DELAY) OPUS_CLEAR(conv1_out, FEATURE_CONV1_OUT_SIZE);
+ compute_conv1d(&lpcnet->model.feature_conv2, conv2_out, net->feature_conv2_state, conv1_out);
+ if (lpcnet->frame_count < FEATURES_DELAY) OPUS_CLEAR(conv2_out, FEATURE_CONV2_OUT_SIZE);
+ _lpcnet_compute_dense(&lpcnet->model.feature_dense1, dense1_out, conv2_out);
+ _lpcnet_compute_dense(&lpcnet->model.feature_dense2, condition, dense1_out);
+ OPUS_COPY(rc, condition, LPC_ORDER);
+ _lpcnet_compute_dense(&lpcnet->model.gru_a_dense_feature, gru_a_condition, condition);
+ _lpcnet_compute_dense(&lpcnet->model.gru_b_dense_feature, gru_b_condition, condition);
+#ifdef END2END
+ rc2lpc(lpc, rc);
+#elif FEATURES_DELAY>0
+ memcpy(lpc, lpcnet->old_lpc[FEATURES_DELAY-1], LPC_ORDER*sizeof(lpc[0]));
+ memmove(lpcnet->old_lpc[1], lpcnet->old_lpc[0], (FEATURES_DELAY-1)*LPC_ORDER*sizeof(lpc[0]));
+ lpc_from_cepstrum(lpcnet->old_lpc[0], features);
+#else
+ lpc_from_cepstrum(lpc, features);
+#endif
+#ifdef LPC_GAMMA
+ lpc_weighting(lpc, LPC_GAMMA);
+#endif
+ if (lpcnet->frame_count < 1000) lpcnet->frame_count++;
+}
+
+void run_frame_network_deferred(LPCNetState *lpcnet, const float *features)
+{
+ int max_buffer_size = lpcnet->model.feature_conv1.kernel_size + lpcnet->model.feature_conv2.kernel_size - 2;
+ celt_assert(max_buffer_size <= MAX_FEATURE_BUFFER_SIZE);
+ if (lpcnet->feature_buffer_fill == max_buffer_size) {
+ OPUS_MOVE(lpcnet->feature_buffer, &lpcnet->feature_buffer[NB_FEATURES], (max_buffer_size-1)*NB_FEATURES);
+ } else {
+ lpcnet->feature_buffer_fill++;
+ }
+ OPUS_COPY(&lpcnet->feature_buffer[(lpcnet->feature_buffer_fill-1)*NB_FEATURES], features, NB_FEATURES);
+}
+
+void run_frame_network_flush(LPCNetState *lpcnet)
+{
+ int i;
+ for (i=0;i<lpcnet->feature_buffer_fill;i++) {
+ float lpc[LPC_ORDER];
+ float gru_a_condition[3*GRU_A_STATE_SIZE];
+ float gru_b_condition[3*GRU_B_STATE_SIZE];
+ run_frame_network(lpcnet, gru_a_condition, gru_b_condition, lpc, &lpcnet->feature_buffer[i*NB_FEATURES]);
+ }
+ lpcnet->feature_buffer_fill = 0;
+}
+
+int run_sample_network(LPCNetState *lpcnet, const float *gru_a_condition, const float *gru_b_condition, int last_exc, int last_sig, int pred, const float *sampling_logit_table, kiss99_ctx *rng)
+{
+ NNetState *net;
+ float gru_a_input[3*GRU_A_STATE_SIZE];
+ float in_b[GRU_A_STATE_SIZE+FEATURE_DENSE2_OUT_SIZE];
+ float gru_b_input[3*GRU_B_STATE_SIZE];
+ net = &lpcnet->nnet;
+#if 1
+ compute_gru_a_input(gru_a_input, gru_a_condition, GRU_A_STATE_SIZE, &lpcnet->model.gru_a_embed_sig, last_sig, &lpcnet->model.gru_a_embed_pred, pred, &lpcnet->model.gru_a_embed_exc, last_exc);
+#else
+ OPUS_COPY(gru_a_input, gru_a_condition, 3*GRU_A_STATE_SIZE);
+ accum_embedding(&lpcnet->model.gru_a_embed_sig, gru_a_input, last_sig);
+ accum_embedding(&lpcnet->model.gru_a_embed_pred, gru_a_input, pred);
+ accum_embedding(&lpcnet->model.gru_a_embed_exc, gru_a_input, last_exc);
+#endif
+ /*compute_gru3(&gru_a, net->gru_a_state, gru_a_input);*/
+ compute_sparse_gru(&lpcnet->model.sparse_gru_a, net->gru_a_state, gru_a_input);
+ OPUS_COPY(in_b, net->gru_a_state, GRU_A_STATE_SIZE);
+ OPUS_COPY(gru_b_input, gru_b_condition, 3*GRU_B_STATE_SIZE);
+ compute_gruB(&lpcnet->model.gru_b, gru_b_input, net->gru_b_state, in_b);
+ return sample_mdense(&lpcnet->model.dual_fc, net->gru_b_state, sampling_logit_table, rng);
+}
+
+int lpcnet_get_size()
+{
+ return sizeof(LPCNetState);
+}
+
+void lpcnet_reset(LPCNetState *lpcnet)
+{
+ const char* rng_string="LPCNet";
+ OPUS_CLEAR((char*)&lpcnet->LPCNET_RESET_START,
+ sizeof(LPCNetState)-
+ ((char*)&lpcnet->LPCNET_RESET_START - (char*)lpcnet));
+ lpcnet->last_exc = lin2ulaw(0.f);
+ kiss99_srand(&lpcnet->rng, (const unsigned char *)rng_string, strlen(rng_string));
+}
+
+int lpcnet_init(LPCNetState *lpcnet)
+{
+ int i;
+ int ret;
+ for (i=0;i<256;i++) {
+ float prob = .025f+.95f*i/255.f;
+ lpcnet->sampling_logit_table[i] = -log((1-prob)/prob);
+ }
+#ifndef USE_WEIGHTS_FILE
+ ret = init_lpcnet_model(&lpcnet->model, lpcnet_arrays);
+#else
+ ret = 0;
+#endif
+ lpcnet_reset(lpcnet);
+ celt_assert(ret == 0);
+ return ret;
+}
+
+int lpcnet_load_model(LPCNetState *st, const unsigned char *data, int len) {
+ WeightArray *list;
+ int ret;
+ parse_weights(&list, data, len);
+ ret = init_lpcnet_model(&st->model, list);
+ opus_free(list);
+ if (ret == 0) return 0;
+ else return -1;
+}
+
+
+LPCNetState *lpcnet_create()
+{
+ LPCNetState *lpcnet;
+ lpcnet = (LPCNetState *)opus_alloc(lpcnet_get_size(), 1);
+ OPUS_CLEAR(lpcnet, 1);
+ lpcnet_init(lpcnet);
+ return lpcnet;
+}
+
+void lpcnet_destroy(LPCNetState *lpcnet)
+{
+ opus_free(lpcnet);
+}
+
+void lpcnet_reset_signal(LPCNetState *lpcnet)
+{
+ lpcnet->deemph_mem = 0;
+ lpcnet->last_exc = lin2ulaw(0.f);
+ OPUS_CLEAR(lpcnet->last_sig, LPC_ORDER);
+ OPUS_CLEAR(lpcnet->nnet.gru_a_state, GRU_A_STATE_SIZE);
+ OPUS_CLEAR(lpcnet->nnet.gru_b_state, GRU_B_STATE_SIZE);
+}
+
+void lpcnet_synthesize_tail_impl(LPCNetState *lpcnet, opus_int16 *output, int N, int preload)
+{
+ int i;
+
+ if (lpcnet->frame_count <= FEATURES_DELAY)
+ {
+ OPUS_CLEAR(output, N);
+ return;
+ }
+ for (i=0;i<N;i++)
+ {
+ int j;
+ float pcm;
+ int exc;
+ int last_sig_ulaw;
+ int pred_ulaw;
+ float pred = 0;
+ for (j=0;j<LPC_ORDER;j++) pred -= lpcnet->last_sig[j]*lpcnet->lpc[j];
+ last_sig_ulaw = lin2ulaw(lpcnet->last_sig[0]);
+ pred_ulaw = lin2ulaw(pred);
+ exc = run_sample_network(lpcnet, lpcnet->gru_a_condition, lpcnet->gru_b_condition, lpcnet->last_exc, last_sig_ulaw, pred_ulaw, lpcnet->sampling_logit_table, &lpcnet->rng);
+ if (i < preload) {
+ exc = lin2ulaw(output[i]-PREEMPH*lpcnet->deemph_mem - pred);
+ pcm = output[i]-PREEMPH*lpcnet->deemph_mem;
+ } else {
+ pcm = pred + ulaw2lin(exc);
+ }
+ OPUS_MOVE(&lpcnet->last_sig[1], &lpcnet->last_sig[0], LPC_ORDER-1);
+ lpcnet->last_sig[0] = pcm;
+ lpcnet->last_exc = exc;
+ pcm += PREEMPH*lpcnet->deemph_mem;
+ lpcnet->deemph_mem = pcm;
+ if (pcm<-32767) pcm = -32767;
+ if (pcm>32767) pcm = 32767;
+ if (i >= preload) output[i] = (int)floor(.5 + pcm);
+ }
+}
+
+void lpcnet_synthesize_impl(LPCNetState *lpcnet, const float *features, opus_int16 *output, int N, int preload)
+{
+ run_frame_network(lpcnet, lpcnet->gru_a_condition, lpcnet->gru_b_condition, lpcnet->lpc, features);
+ lpcnet_synthesize_tail_impl(lpcnet, output, N, preload);
+}
+
+void lpcnet_synthesize(LPCNetState *lpcnet, const float *features, opus_int16 *output, int N) {
+ lpcnet_synthesize_impl(lpcnet, features, output, N, 0);
+}
diff --git a/dnn/lpcnet.h b/dnn/lpcnet.h
new file mode 100644
index 00000000..4a981246
--- /dev/null
+++ b/dnn/lpcnet.h
@@ -0,0 +1,183 @@
+/* Copyright (c) 2018 Mozilla */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifndef LPCNET_H_
+#define LPCNET_H_
+
+#include "opus_types.h"
+
+#define NB_FEATURES 20
+#define NB_TOTAL_FEATURES 36
+
+/** Number of audio samples in a feature frame (not for encoding/decoding). */
+#define LPCNET_FRAME_SIZE (160)
+
+typedef struct LPCNetState LPCNetState;
+
+typedef struct LPCNetDecState LPCNetDecState;
+
+typedef struct LPCNetEncState LPCNetEncState;
+
+typedef struct LPCNetPLCState LPCNetPLCState;
+
+
+/** Gets the size of an <code>LPCNetDecState</code> structure.
+ * @returns The size in bytes.
+ */
+int lpcnet_decoder_get_size(void);
+
+/** Initializes a previously allocated decoder state
+ * The memory pointed to by st must be at least the size returned by lpcnet_decoder_get_size().
+ * This is intended for applications which use their own allocator instead of malloc.
+ * @see lpcnet_decoder_create(),lpcnet_decoder_get_size()
+ * @param [in] st <tt>LPCNetDecState*</tt>: Decoder state
+ * @retval 0 Success
+ */
+int lpcnet_decoder_init(LPCNetDecState *st);
+
+void lpcnet_reset(LPCNetState *lpcnet);
+
+/** Allocates and initializes a decoder state.
+ * @returns The newly created state
+ */
+LPCNetDecState *lpcnet_decoder_create(void);
+
+/** Frees an <code>LPCNetDecState</code> allocated by lpcnet_decoder_create().
+ * @param[in] st <tt>LPCNetDecState*</tt>: State to be freed.
+ */
+void lpcnet_decoder_destroy(LPCNetDecState *st);
+
+/** Decodes a packet of LPCNET_COMPRESSED_SIZE bytes (currently 8) into LPCNET_PACKET_SAMPLES samples (currently 640).
+ * @param [in] st <tt>LPCNetDecState*</tt>: Decoder state
+ * @param [in] buf <tt>const unsigned char *</tt>: Compressed packet
+ * @param [out] pcm <tt>opus_int16 *</tt>: Decoded audio
+ * @retval 0 Success
+ */
+int lpcnet_decode(LPCNetDecState *st, const unsigned char *buf, opus_int16 *pcm);
+
+
+
+/** Gets the size of an <code>LPCNetEncState</code> structure.
+ * @returns The size in bytes.
+ */
+int lpcnet_encoder_get_size(void);
+
+/** Initializes a previously allocated encoder state
+ * The memory pointed to by st must be at least the size returned by lpcnet_encoder_get_size().
+ * This is intended for applications which use their own allocator instead of malloc.
+ * @see lpcnet_encoder_create(),lpcnet_encoder_get_size()
+ * @param [in] st <tt>LPCNetEncState*</tt>: Encoder state
+ * @retval 0 Success
+ */
+int lpcnet_encoder_init(LPCNetEncState *st);
+
+int lpcnet_encoder_load_model(LPCNetEncState *st, const void *data, int len);
+
+/** Allocates and initializes an encoder state.
+ * @returns The newly created state
+ */
+LPCNetEncState *lpcnet_encoder_create(void);
+
+/** Frees an <code>LPCNetEncState</code> allocated by lpcnet_encoder_create().
+ * @param[in] st <tt>LPCNetEncState*</tt>: State to be freed.
+ */
+void lpcnet_encoder_destroy(LPCNetEncState *st);
+
+/** Encodes LPCNET_PACKET_SAMPLES speech samples (currently 640) into a packet of LPCNET_COMPRESSED_SIZE bytes (currently 8).
+ * @param [in] st <tt>LPCNetDecState*</tt>: Encoder state
+ * @param [in] pcm <tt>opus_int16 *</tt>: Input speech to be encoded
+ * @param [out] buf <tt>const unsigned char *</tt>: Compressed packet
+ * @retval 0 Success
+ */
+int lpcnet_encode(LPCNetEncState *st, const opus_int16 *pcm, unsigned char *buf);
+
+/** Compute features on LPCNET_FRAME_SIZE speech samples (currently 160) and output features for one 10-ms frame.
+ * @param [in] st <tt>LPCNetDecState*</tt>: Encoder state
+ * @param [in] pcm <tt>opus_int16 *</tt>: Input speech to be analyzed
+ * @param [out] features <tt>float[NB_TOTAL_FEATURES]</tt>: Four feature vectors
+ * @retval 0 Success
+ */
+int lpcnet_compute_single_frame_features(LPCNetEncState *st, const opus_int16 *pcm, float features[NB_TOTAL_FEATURES], int arch);
+
+
+/** Compute features on LPCNET_FRAME_SIZE speech samples (currently 160) and output features for one 10-ms frame.
+ * @param [in] st <tt>LPCNetDecState*</tt>: Encoder state
+ * @param [in] pcm <tt>float *</tt>: Input speech to be analyzed
+ * @param [out] features <tt>float[NB_TOTAL_FEATURES]</tt>: Four feature vectors
+ * @retval 0 Success
+ */
+int lpcnet_compute_single_frame_features_float(LPCNetEncState *st, const float *pcm, float features[NB_TOTAL_FEATURES], int arch);
+
+/** Gets the size of an <code>LPCNetState</code> structure.
+ * @returns The size in bytes.
+ */
+int lpcnet_get_size(void);
+
+/** Initializes a previously allocated synthesis state
+ * The memory pointed to by st must be at least the size returned by lpcnet_get_size().
+ * This is intended for applications which use their own allocator instead of malloc.
+ * @see lpcnet_create(),lpcnet_get_size()
+ * @param [in] st <tt>LPCNetState*</tt>: Synthesis state
+ * @retval 0 Success
+ */
+int lpcnet_init(LPCNetState *st);
+
+/** Allocates and initializes a synthesis state.
+ * @returns The newly created state
+ */
+LPCNetState *lpcnet_create(void);
+
+/** Frees an <code>LPCNetState</code> allocated by lpcnet_create().
+ * @param[in] st <tt>LPCNetState*</tt>: State to be freed.
+ */
+void lpcnet_destroy(LPCNetState *st);
+
+/** Synthesizes speech from an LPCNet feature vector.
+ * @param [in] st <tt>LPCNetState*</tt>: Synthesis state
+ * @param [in] features <tt>const float *</tt>: Compressed packet
+ * @param [out] output <tt>opus_int16 **</tt>: Synthesized speech
+ * @param [in] N <tt>int</tt>: Number of samples to generate
+ * @retval 0 Success
+ */
+void lpcnet_synthesize(LPCNetState *st, const float *features, opus_int16 *output, int N);
+
+
+
+int lpcnet_plc_init(LPCNetPLCState *st);
+void lpcnet_plc_reset(LPCNetPLCState *st);
+
+int lpcnet_plc_update(LPCNetPLCState *st, opus_int16 *pcm);
+
+int lpcnet_plc_conceal(LPCNetPLCState *st, opus_int16 *pcm);
+
+void lpcnet_plc_fec_add(LPCNetPLCState *st, const float *features);
+
+void lpcnet_plc_fec_clear(LPCNetPLCState *st);
+
+int lpcnet_load_model(LPCNetState *st, const void *data, int len);
+int lpcnet_plc_load_model(LPCNetPLCState *st, const void *data, int len);
+
+#endif
diff --git a/dnn/lpcnet_demo.c b/dnn/lpcnet_demo.c
new file mode 100644
index 00000000..59651f3e
--- /dev/null
+++ b/dnn/lpcnet_demo.c
@@ -0,0 +1,217 @@
+/* Copyright (c) 2018 Mozilla */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "arch.h"
+#include "lpcnet.h"
+#include "freq.h"
+#include "os_support.h"
+#include "fargan.h"
+#include "cpu_support.h"
+
+#ifdef USE_WEIGHTS_FILE
+# if __unix__
+# include <fcntl.h>
+# include <sys/mman.h>
+# include <unistd.h>
+# include <sys/stat.h>
+/* When available, mmap() is preferable to reading the file, as it leads to
+ better resource utilization, especially if multiple processes are using the same
+ file (mapping will be shared in cache). */
+void *load_blob(const char *filename, int *len) {
+ int fd;
+ void *data;
+ struct stat st;
+ if (stat(filename, &st)) {
+ *len = 0;
+ return NULL;
+ }
+ *len = st.st_size;
+ fd = open(filename, O_RDONLY);
+ if (fd<0) {
+ *len = 0;
+ return NULL;
+ }
+ data = mmap(NULL, *len, PROT_READ, MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED) {
+ *len = 0;
+ data = NULL;
+ }
+ close(fd);
+ return data;
+}
+void free_blob(void *blob, int len) {
+ if (blob) munmap(blob, len);
+}
+# else
+void *load_blob(const char *filename, int *len) {
+ FILE *file;
+ void *data;
+ file = fopen(filename, "r");
+ if (file == NULL)
+ {
+ perror("could not open blob file");
+ *len = 0;
+ return NULL;
+ }
+ fseek(file, 0L, SEEK_END);
+ *len = ftell(file);
+ fseek(file, 0L, SEEK_SET);
+ if (*len <= 0) {
+ *len = 0;
+ return NULL;
+ }
+ data = malloc(*len);
+ if (!data) {
+ *len = 0;
+ return NULL;
+ }
+ *len = fread(data, 1, *len, file);
+ return data;
+}
+void free_blob(void *blob, int len) {
+ free(blob);
+ (void)len;
+}
+# endif
+#endif
+
+#define MODE_FEATURES 2
+/*#define MODE_SYNTHESIS 3*/
+#define MODE_ADDLPC 5
+#define MODE_FWGAN_SYNTHESIS 6
+#define MODE_FARGAN_SYNTHESIS 7
+
+void usage(void) {
+ fprintf(stderr, "usage: lpcnet_demo -features <input.pcm> <features.f32>\n");
+ fprintf(stderr, " lpcnet_demo -fargan-synthesis <features.f32> <output.pcm>\n");
+ fprintf(stderr, " lpcnet_demo -addlpc <features_without_lpc.f32> <features_with_lpc.lpc>\n\n");
+ fprintf(stderr, " plc_options:\n");
+ fprintf(stderr, " causal: normal (causal) PLC\n");
+ fprintf(stderr, " codec: normal (causal) PLC without cross-fade (will glitch)\n");
+ exit(1);
+}
+
+int main(int argc, char **argv) {
+ int mode=0;
+ int arch;
+ FILE *fin, *fout;
+#ifdef USE_WEIGHTS_FILE
+ int len;
+ void *data;
+ const char *filename = "weights_blob.bin";
+#endif
+ arch = opus_select_arch();
+ if (argc < 4) usage();
+ if (strcmp(argv[1], "-features") == 0) mode=MODE_FEATURES;
+ else if (strcmp(argv[1], "-fargan-synthesis") == 0) mode=MODE_FARGAN_SYNTHESIS;
+ else if (strcmp(argv[1], "-addlpc") == 0){
+ mode=MODE_ADDLPC;
+ } else {
+ usage();
+ }
+ if (argc != 4) usage();
+ fin = fopen(argv[2], "rb");
+ if (fin == NULL) {
+ fprintf(stderr, "Can't open %s\n", argv[2]);
+ exit(1);
+ }
+
+ fout = fopen(argv[3], "wb");
+ if (fout == NULL) {
+ fprintf(stderr, "Can't open %s\n", argv[3]);
+ exit(1);
+ }
+#ifdef USE_WEIGHTS_FILE
+ data = load_blob(filename, &len);
+#endif
+ if (mode == MODE_FEATURES) {
+ LPCNetEncState *net;
+ net = lpcnet_encoder_create();
+ while (1) {
+ float features[NB_TOTAL_FEATURES];
+ opus_int16 pcm[LPCNET_FRAME_SIZE];
+ size_t ret;
+ ret = fread(pcm, sizeof(pcm[0]), LPCNET_FRAME_SIZE, fin);
+ if (feof(fin) || ret != LPCNET_FRAME_SIZE) break;
+ lpcnet_compute_single_frame_features(net, pcm, features, arch);
+ fwrite(features, sizeof(float), NB_TOTAL_FEATURES, fout);
+ }
+ lpcnet_encoder_destroy(net);
+ } else if (mode == MODE_FARGAN_SYNTHESIS) {
+ FARGANState fargan;
+ size_t ret, i;
+ float in_features[5*NB_TOTAL_FEATURES];
+ float zeros[320] = {0};
+ fargan_init(&fargan);
+#ifdef USE_WEIGHTS_FILE
+ fargan_load_model(&fargan, data, len);
+#endif
+ /* uncomment the following to align with Python code */
+ /*ret = fread(&in_features[0], sizeof(in_features[0]), NB_TOTAL_FEATURES, fin);*/
+ for (i=0;i<5;i++) {
+ ret = fread(&in_features[i*NB_FEATURES], sizeof(in_features[0]), NB_TOTAL_FEATURES, fin);
+ }
+ fargan_cont(&fargan, zeros, in_features);
+ while (1) {
+ float features[NB_FEATURES];
+ float fpcm[LPCNET_FRAME_SIZE];
+ opus_int16 pcm[LPCNET_FRAME_SIZE];
+ ret = fread(in_features, sizeof(features[0]), NB_TOTAL_FEATURES, fin);
+ if (feof(fin) || ret != NB_TOTAL_FEATURES) break;
+ OPUS_COPY(features, in_features, NB_FEATURES);
+ fargan_synthesize(&fargan, fpcm, features);
+ for (i=0;i<LPCNET_FRAME_SIZE;i++) pcm[i] = (int)floor(.5 + MIN32(32767, MAX32(-32767, 32768.f*fpcm[i])));
+ fwrite(pcm, sizeof(pcm[0]), LPCNET_FRAME_SIZE, fout);
+ }
+ } else if (mode == MODE_ADDLPC) {
+ float features[36];
+ size_t ret;
+
+ while (1) {
+ ret = fread(features, sizeof(features[0]), 36, fin);
+ if (ret != 36 || feof(fin)) break;
+ lpc_from_cepstrum(&features[20], &features[0]);
+ fwrite(features, sizeof(features[0]), 36, fout);
+ }
+
+ } else {
+ fprintf(stderr, "unknown action\n");
+ }
+ fclose(fin);
+ fclose(fout);
+#ifdef USE_WEIGHTS_FILE
+ free_blob(data, len);
+#endif
+ return 0;
+}
diff --git a/dnn/lpcnet_enc.c b/dnn/lpcnet_enc.c
new file mode 100644
index 00000000..52930416
--- /dev/null
+++ b/dnn/lpcnet_enc.c
@@ -0,0 +1,230 @@
+/* Copyright (c) 2017-2019 Mozilla */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "kiss_fft.h"
+#include "common.h"
+#include <math.h>
+#include "freq.h"
+#include "pitch.h"
+#include "arch.h"
+#include <assert.h>
+#include "lpcnet_private.h"
+#include "lpcnet.h"
+#include "os_support.h"
+#include "_kiss_fft_guts.h"
+#include "celt_lpc.h"
+#include "mathops.h"
+
+
+int lpcnet_encoder_get_size(void) {
+ return sizeof(LPCNetEncState);
+}
+
+int lpcnet_encoder_init(LPCNetEncState *st) {
+ memset(st, 0, sizeof(*st));
+ pitchdnn_init(&st->pitchdnn);
+ return 0;
+}
+
+int lpcnet_encoder_load_model(LPCNetEncState *st, const void *data, int len) {
+ return pitchdnn_load_model(&st->pitchdnn, data, len);
+}
+
+LPCNetEncState *lpcnet_encoder_create(void) {
+ LPCNetEncState *st;
+ st = opus_alloc(lpcnet_encoder_get_size());
+ lpcnet_encoder_init(st);
+ return st;
+}
+
+void lpcnet_encoder_destroy(LPCNetEncState *st) {
+ opus_free(st);
+}
+
+static void frame_analysis(LPCNetEncState *st, kiss_fft_cpx *X, float *Ex, const float *in) {
+ float x[WINDOW_SIZE];
+ OPUS_COPY(x, st->analysis_mem, OVERLAP_SIZE);
+ OPUS_COPY(&x[OVERLAP_SIZE], in, FRAME_SIZE);
+ OPUS_COPY(st->analysis_mem, &in[FRAME_SIZE-OVERLAP_SIZE], OVERLAP_SIZE);
+ apply_window(x);
+ forward_transform(X, x);
+ lpcn_compute_band_energy(Ex, X);
+}
+
+static void biquad(float *y, float mem[2], const float *x, const float *b, const float *a, int N) {
+ int i;
+ float mem0, mem1;
+ mem0 = mem[0];
+ mem1 = mem[1];
+ for (i=0;i<N;i++) {
+ float xi, yi, mem00;
+ xi = x[i];
+ yi = x[i] + mem0;
+ mem00 = mem0;
+ /* Original code:
+ mem0 = mem1 + (b[0]*xi - a[0]*yi);
+ mem1 = (b[1]*xi - a[1]*yi);
+ Modified to reduce dependency chains: (the +1e-30f forces the ordering and has no effect on the output)
+ */
+ mem0 = (b[0]-a[0])*xi + mem1 - a[0]*mem0;
+ mem1 = (b[1]-a[1])*xi + 1e-30f - a[1]*mem00;
+ y[i] = yi;
+ }
+ mem[0] = mem0;
+ mem[1] = mem1;
+}
+
+#define celt_log10(x) (0.3010299957f*celt_log2(x))
+
+void compute_frame_features(LPCNetEncState *st, const float *in, int arch) {
+ float aligned_in[FRAME_SIZE];
+ int i;
+ float Ly[NB_BANDS];
+ float follow, logMax;
+ kiss_fft_cpx X[FREQ_SIZE];
+ float Ex[NB_BANDS];
+ float xcorr[PITCH_MAX_PERIOD];
+ float ener0;
+ float ener;
+ float x[FRAME_SIZE+LPC_ORDER];
+ float frame_corr;
+ float xy, xx, yy;
+ int pitch;
+ float ener_norm[PITCH_MAX_PERIOD - PITCH_MIN_PERIOD];
+ /* [b,a]=ellip(2, 2, 20, 1200/8000); */
+ static const float lp_b[2] = {-0.84946f, 1.f};
+ static const float lp_a[2] = {-1.54220f, 0.70781f};
+ OPUS_COPY(aligned_in, &st->analysis_mem[OVERLAP_SIZE-TRAINING_OFFSET], TRAINING_OFFSET);
+ frame_analysis(st, X, Ex, in);
+ st->if_features[0] = MAX16(-1.f, MIN16(1.f, (1.f/64)*(10.f*celt_log10(1e-15f + X[0].r*X[0].r)-6.f)));
+ for (i=1;i<PITCH_IF_MAX_FREQ;i++) {
+ kiss_fft_cpx prod;
+ float norm_1;
+ C_MULC(prod, X[i], st->prev_if[i]);
+ norm_1 = 1.f/sqrt(1e-15f + prod.r*prod.r + prod.i*prod.i);
+ C_MULBYSCALAR(prod, norm_1);
+ st->if_features[3*i-2] = prod.r;
+ st->if_features[3*i-1] = prod.i;
+ st->if_features[3*i] = MAX16(-1.f, MIN16(1.f, (1.f/64)*(10.f*celt_log10(1e-15f + X[i].r*X[i].r + X[i].i*X[i].i)-6.f)));
+ }
+ OPUS_COPY(st->prev_if, X, PITCH_IF_MAX_FREQ);
+ /*for (i=0;i<88;i++) printf("%f ", st->if_features[i]);printf("\n");*/
+ logMax = -2;
+ follow = -2;
+ for (i=0;i<NB_BANDS;i++) {
+ Ly[i] = celt_log10(1e-2f+Ex[i]);
+ Ly[i] = MAX16(logMax-8, MAX16(follow-2.5f, Ly[i]));
+ logMax = MAX16(logMax, Ly[i]);
+ follow = MAX16(follow-2.5f, Ly[i]);
+ }
+ dct(st->features, Ly);
+ st->features[0] -= 4;
+ lpc_from_cepstrum(st->lpc, st->features);
+ for (i=0;i<LPC_ORDER;i++) st->features[NB_BANDS+2+i] = st->lpc[i];
+ OPUS_MOVE(st->exc_buf, &st->exc_buf[FRAME_SIZE], PITCH_MAX_PERIOD);
+ OPUS_MOVE(st->lp_buf, &st->lp_buf[FRAME_SIZE], PITCH_MAX_PERIOD);
+ OPUS_COPY(&aligned_in[TRAINING_OFFSET], in, FRAME_SIZE-TRAINING_OFFSET);
+ OPUS_COPY(&x[0], st->pitch_mem, LPC_ORDER);
+ OPUS_COPY(&x[LPC_ORDER], aligned_in, FRAME_SIZE);
+ OPUS_COPY(st->pitch_mem, &aligned_in[FRAME_SIZE-LPC_ORDER], LPC_ORDER);
+ celt_fir(&x[LPC_ORDER], st->lpc, &st->lp_buf[PITCH_MAX_PERIOD], FRAME_SIZE, LPC_ORDER, arch);
+ for (i=0;i<FRAME_SIZE;i++) {
+ st->exc_buf[PITCH_MAX_PERIOD+i] = st->lp_buf[PITCH_MAX_PERIOD+i] + .7f*st->pitch_filt;
+ st->pitch_filt = st->lp_buf[PITCH_MAX_PERIOD+i];
+ /*printf("%f\n", st->exc_buf[PITCH_MAX_PERIOD+i]);*/
+ }
+ biquad(&st->lp_buf[PITCH_MAX_PERIOD], st->lp_mem, &st->lp_buf[PITCH_MAX_PERIOD], lp_b, lp_a, FRAME_SIZE);
+ {
+ double ener1;
+ float *buf = st->exc_buf;
+ celt_pitch_xcorr(&buf[PITCH_MAX_PERIOD], buf, xcorr, FRAME_SIZE, PITCH_MAX_PERIOD-PITCH_MIN_PERIOD, arch);
+ ener0 = celt_inner_prod(&buf[PITCH_MAX_PERIOD], &buf[PITCH_MAX_PERIOD], FRAME_SIZE, arch);
+ ener1 = celt_inner_prod(&buf[0], &buf[0], FRAME_SIZE, arch);
+ /*printf("%f\n", st->frame_weight[sub]);*/
+ for (i=0;i<PITCH_MAX_PERIOD-PITCH_MIN_PERIOD;i++) {
+ ener = 1 + ener0 + ener1;
+ st->xcorr_features[i] = 2*xcorr[i];
+ ener_norm[i] = ener;
+ ener1 += buf[i+FRAME_SIZE]*(double)buf[i+FRAME_SIZE] - buf[i]*(double)buf[i];
+ /*printf("%f ", st->xcorr_features[i]);*/
+ }
+ /* Split in a separate loop so the compiler can vectorize it */
+ for (i=0;i<PITCH_MAX_PERIOD-PITCH_MIN_PERIOD;i++) {
+ st->xcorr_features[i] /= ener_norm[i];
+ }
+ /*printf("\n");*/
+ }
+ st->dnn_pitch = compute_pitchdnn(&st->pitchdnn, st->if_features, st->xcorr_features, arch);
+ pitch = (int)floor(.5+256./pow(2.f,((1./60.)*((st->dnn_pitch+1.5)*60))));
+ xx = celt_inner_prod(&st->lp_buf[PITCH_MAX_PERIOD], &st->lp_buf[PITCH_MAX_PERIOD], FRAME_SIZE, arch);
+ yy = celt_inner_prod(&st->lp_buf[PITCH_MAX_PERIOD-pitch], &st->lp_buf[PITCH_MAX_PERIOD-pitch], FRAME_SIZE, arch);
+ xy = celt_inner_prod(&st->lp_buf[PITCH_MAX_PERIOD], &st->lp_buf[PITCH_MAX_PERIOD-pitch], FRAME_SIZE, arch);
+ /*printf("%f %f\n", frame_corr, xy/sqrt(1e-15+xx*yy));*/
+ frame_corr = xy/sqrt(1+xx*yy);
+ frame_corr = log(1.f+exp(5.f*frame_corr))/log(1+exp(5.f));
+ st->features[NB_BANDS] = st->dnn_pitch;
+ st->features[NB_BANDS + 1] = frame_corr-.5f;
+}
+
+void preemphasis(float *y, float *mem, const float *x, float coef, int N) {
+ int i;
+ for (i=0;i<N;i++) {
+ float yi;
+ yi = x[i] + *mem;
+ *mem = -coef*x[i];
+ y[i] = yi;
+ }
+}
+
+static int lpcnet_compute_single_frame_features_impl(LPCNetEncState *st, float *x, float features[NB_TOTAL_FEATURES], int arch) {
+ preemphasis(x, &st->mem_preemph, x, PREEMPHASIS, FRAME_SIZE);
+ compute_frame_features(st, x, arch);
+ OPUS_COPY(features, &st->features[0], NB_TOTAL_FEATURES);
+ return 0;
+}
+
+int lpcnet_compute_single_frame_features(LPCNetEncState *st, const opus_int16 *pcm, float features[NB_TOTAL_FEATURES], int arch) {
+ int i;
+ float x[FRAME_SIZE];
+ for (i=0;i<FRAME_SIZE;i++) x[i] = pcm[i];
+ lpcnet_compute_single_frame_features_impl(st, x, features, arch);
+ return 0;
+}
+
+int lpcnet_compute_single_frame_features_float(LPCNetEncState *st, const float *pcm, float features[NB_TOTAL_FEATURES], int arch) {
+ int i;
+ float x[FRAME_SIZE];
+ for (i=0;i<FRAME_SIZE;i++) x[i] = pcm[i];
+ lpcnet_compute_single_frame_features_impl(st, x, features, arch);
+ return 0;
+}
diff --git a/dnn/lpcnet_plc.c b/dnn/lpcnet_plc.c
new file mode 100644
index 00000000..55122779
--- /dev/null
+++ b/dnn/lpcnet_plc.c
@@ -0,0 +1,211 @@
+/* Copyright (c) 2021 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "lpcnet_private.h"
+#include "lpcnet.h"
+#include "plc_data.h"
+#include "os_support.h"
+#include "common.h"
+#include "cpu_support.h"
+
+#ifndef M_PI
+#define M_PI 3.141592653
+#endif
+
+/* Comment this out to have LPCNet update its state on every good packet (slow). */
+#define PLC_SKIP_UPDATES
+
+void lpcnet_plc_reset(LPCNetPLCState *st) {
+ OPUS_CLEAR((char*)&st->LPCNET_PLC_RESET_START,
+ sizeof(LPCNetPLCState)-
+ ((char*)&st->LPCNET_PLC_RESET_START - (char*)st));
+ lpcnet_encoder_init(&st->enc);
+ OPUS_CLEAR(st->pcm, PLC_BUF_SIZE);
+ st->blend = 0;
+ st->loss_count = 0;
+ st->analysis_gap = 1;
+ st->analysis_pos = PLC_BUF_SIZE;
+ st->predict_pos = PLC_BUF_SIZE;
+}
+
+int lpcnet_plc_init(LPCNetPLCState *st) {
+ int ret;
+ st->arch = opus_select_arch();
+ fargan_init(&st->fargan);
+ lpcnet_encoder_init(&st->enc);
+ st->loaded = 0;
+#ifndef USE_WEIGHTS_FILE
+ ret = init_plcmodel(&st->model, plcmodel_arrays);
+ if (ret == 0) st->loaded = 1;
+#else
+ ret = 0;
+#endif
+ celt_assert(ret == 0);
+ lpcnet_plc_reset(st);
+ return ret;
+}
+
+int lpcnet_plc_load_model(LPCNetPLCState *st, const void *data, int len) {
+ WeightArray *list;
+ int ret;
+ parse_weights(&list, data, len);
+ ret = init_plcmodel(&st->model, list);
+ opus_free(list);
+ if (ret == 0) {
+ ret = lpcnet_encoder_load_model(&st->enc, data, len);
+ }
+ if (ret == 0) {
+ ret = fargan_load_model(&st->fargan, data, len);
+ }
+ if (ret == 0) st->loaded = 1;
+ return ret;
+}
+
+void lpcnet_plc_fec_add(LPCNetPLCState *st, const float *features) {
+ if (features == NULL) {
+ st->fec_skip++;
+ return;
+ }
+ if (st->fec_fill_pos == PLC_MAX_FEC) {
+ OPUS_MOVE(&st->fec[0][0], &st->fec[st->fec_read_pos][0], (st->fec_fill_pos-st->fec_read_pos)*NB_FEATURES);
+ st->fec_fill_pos = st->fec_fill_pos-st->fec_read_pos;
+ st->fec_read_pos -= st->fec_read_pos;
+ }
+ OPUS_COPY(&st->fec[st->fec_fill_pos][0], features, NB_FEATURES);
+ st->fec_fill_pos++;
+}
+
+void lpcnet_plc_fec_clear(LPCNetPLCState *st) {
+ st->fec_read_pos = st->fec_fill_pos = st->fec_skip = 0;
+}
+
+
+static void compute_plc_pred(LPCNetPLCState *st, float *out, const float *in) {
+ float tmp[PLC_DENSE_IN_OUT_SIZE];
+ PLCModel *model = &st->model;
+ PLCNetState *net = &st->plc_net;
+ celt_assert(st->loaded);
+ compute_generic_dense(&model->plc_dense_in, tmp, in, ACTIVATION_TANH, st->arch);
+ compute_generic_gru(&model->plc_gru1_input, &model->plc_gru1_recurrent, net->gru1_state, tmp, st->arch);
+ compute_generic_gru(&model->plc_gru2_input, &model->plc_gru2_recurrent, net->gru2_state, net->gru1_state, st->arch);
+ compute_generic_dense(&model->plc_dense_out, out, net->gru2_state, ACTIVATION_LINEAR, st->arch);
+}
+
+static int get_fec_or_pred(LPCNetPLCState *st, float *out) {
+ if (st->fec_read_pos != st->fec_fill_pos && st->fec_skip==0) {
+ float plc_features[2*NB_BANDS+NB_FEATURES+1] = {0};
+ float discard[NB_FEATURES];
+ OPUS_COPY(out, &st->fec[st->fec_read_pos][0], NB_FEATURES);
+ st->fec_read_pos++;
+ /* Update PLC state using FEC, so without Burg features. */
+ OPUS_COPY(&plc_features[2*NB_BANDS], out, NB_FEATURES);
+ plc_features[2*NB_BANDS+NB_FEATURES] = -1;
+ compute_plc_pred(st, discard, plc_features);
+ return 1;
+ } else {
+ float zeros[2*NB_BANDS+NB_FEATURES+1] = {0};
+ compute_plc_pred(st, out, zeros);
+ if (st->fec_skip > 0) st->fec_skip--;
+ return 0;
+ }
+}
+
+static void queue_features(LPCNetPLCState *st, const float *features) {
+ OPUS_MOVE(&st->cont_features[0], &st->cont_features[NB_FEATURES], (CONT_VECTORS-1)*NB_FEATURES);
+ OPUS_COPY(&st->cont_features[(CONT_VECTORS-1)*NB_FEATURES], features, NB_FEATURES);
+}
+
+/* In this causal version of the code, the DNN model implemented by compute_plc_pred()
+ needs to generate two feature vectors to conceal the first lost packet.*/
+
+int lpcnet_plc_update(LPCNetPLCState *st, opus_int16 *pcm) {
+ int i;
+ if (st->analysis_pos - FRAME_SIZE >= 0) st->analysis_pos -= FRAME_SIZE;
+ else st->analysis_gap = 1;
+ if (st->predict_pos - FRAME_SIZE >= 0) st->predict_pos -= FRAME_SIZE;
+ OPUS_MOVE(st->pcm, &st->pcm[FRAME_SIZE], PLC_BUF_SIZE-FRAME_SIZE);
+ for (i=0;i<FRAME_SIZE;i++) st->pcm[PLC_BUF_SIZE-FRAME_SIZE+i] = (1.f/32768.f)*pcm[i];
+ st->loss_count = 0;
+ st->blend = 0;
+ return 0;
+}
+
+static const float att_table[10] = {0, 0, -.2, -.2, -.4, -.4, -.8, -.8, -1.6, -1.6};
+int lpcnet_plc_conceal(LPCNetPLCState *st, opus_int16 *pcm) {
+ int i;
+ celt_assert(st->loaded);
+ if (st->blend == 0) {
+ int count = 0;
+ st->plc_net = st->plc_bak[0];
+ while (st->analysis_pos + FRAME_SIZE <= PLC_BUF_SIZE) {
+ float x[FRAME_SIZE];
+ float plc_features[2*NB_BANDS+NB_FEATURES+1];
+ celt_assert(st->analysis_pos >= 0);
+ for (i=0;i<FRAME_SIZE;i++) x[i] = 32768.f*st->pcm[st->analysis_pos+i];
+ burg_cepstral_analysis(plc_features, x);
+ lpcnet_compute_single_frame_features_float(&st->enc, x, st->features, st->arch);
+ if ((!st->analysis_gap || count>0) && st->analysis_pos >= st->predict_pos) {
+ queue_features(st, st->features);
+ OPUS_COPY(&plc_features[2*NB_BANDS], st->features, NB_FEATURES);
+ plc_features[2*NB_BANDS+NB_FEATURES] = 1;
+ st->plc_bak[0] = st->plc_bak[1];
+ st->plc_bak[1] = st->plc_net;
+ compute_plc_pred(st, st->features, plc_features);
+ }
+ st->analysis_pos += FRAME_SIZE;
+ count++;
+ }
+ st->plc_bak[0] = st->plc_bak[1];
+ st->plc_bak[1] = st->plc_net;
+ get_fec_or_pred(st, st->features);
+ queue_features(st, st->features);
+ st->plc_bak[0] = st->plc_bak[1];
+ st->plc_bak[1] = st->plc_net;
+ get_fec_or_pred(st, st->features);
+ queue_features(st, st->features);
+ fargan_cont(&st->fargan, &st->pcm[PLC_BUF_SIZE-FARGAN_CONT_SAMPLES], st->cont_features);
+ st->analysis_gap = 0;
+ }
+ st->plc_bak[0] = st->plc_bak[1];
+ st->plc_bak[1] = st->plc_net;
+ if (get_fec_or_pred(st, st->features)) st->loss_count = 0;
+ else st->loss_count++;
+ if (st->loss_count >= 10) st->features[0] = MAX16(-10, st->features[0]+att_table[9] - 2*(st->loss_count-9));
+ else st->features[0] = MAX16(-10, st->features[0]+att_table[st->loss_count]);
+ fargan_synthesize_int(&st->fargan, pcm, &st->features[0]);
+ queue_features(st, st->features);
+ if (st->analysis_pos - FRAME_SIZE >= 0) st->analysis_pos -= FRAME_SIZE;
+ else st->analysis_gap = 1;
+ st->predict_pos = PLC_BUF_SIZE;
+ OPUS_MOVE(st->pcm, &st->pcm[FRAME_SIZE], PLC_BUF_SIZE-FRAME_SIZE);
+ for (i=0;i<FRAME_SIZE;i++) st->pcm[PLC_BUF_SIZE-FRAME_SIZE+i] = (1.f/32768.f)*pcm[i];
+ st->blend = 1;
+ return 0;
+}
diff --git a/dnn/lpcnet_private.h b/dnn/lpcnet_private.h
new file mode 100644
index 00000000..4aa376b6
--- /dev/null
+++ b/dnn/lpcnet_private.h
@@ -0,0 +1,90 @@
+#ifndef LPCNET_PRIVATE_H
+#define LPCNET_PRIVATE_H
+
+#include <stdio.h>
+#include "freq.h"
+#include "lpcnet.h"
+#include "plc_data.h"
+#include "pitchdnn.h"
+#include "fargan.h"
+
+
+#define PITCH_FRAME_SIZE 320
+#define PITCH_BUF_SIZE (PITCH_MAX_PERIOD+PITCH_FRAME_SIZE)
+
+#define PLC_MAX_FEC 100
+#define MAX_FEATURE_BUFFER_SIZE 4
+
+#define PITCH_IF_MAX_FREQ 30
+#define PITCH_IF_FEATURES (3*PITCH_IF_MAX_FREQ - 2)
+
+#define CONT_VECTORS 5
+
+#define FEATURES_DELAY 1
+
+struct LPCNetEncState{
+ PitchDNNState pitchdnn;
+ float analysis_mem[OVERLAP_SIZE];
+ float mem_preemph;
+ kiss_fft_cpx prev_if[PITCH_IF_MAX_FREQ];
+ float if_features[PITCH_IF_FEATURES];
+ float xcorr_features[PITCH_MAX_PERIOD - PITCH_MIN_PERIOD];
+ float dnn_pitch;
+ float pitch_mem[LPC_ORDER];
+ float pitch_filt;
+ float exc_buf[PITCH_BUF_SIZE];
+ float lp_buf[PITCH_BUF_SIZE];
+ float lp_mem[4];
+ float lpc[LPC_ORDER];
+ float features[NB_TOTAL_FEATURES];
+ float sig_mem[LPC_ORDER];
+ float burg_cepstrum[2*NB_BANDS];
+};
+
+typedef struct {
+ float gru1_state[PLC_GRU1_STATE_SIZE];
+ float gru2_state[PLC_GRU2_STATE_SIZE];
+} PLCNetState;
+
+#define PLC_BUF_SIZE ((CONT_VECTORS+5)*FRAME_SIZE)
+struct LPCNetPLCState {
+ PLCModel model;
+ FARGANState fargan;
+ LPCNetEncState enc;
+ int loaded;
+ int arch;
+
+#define LPCNET_PLC_RESET_START fec
+ float fec[PLC_MAX_FEC][NB_FEATURES];
+ int analysis_gap;
+ int fec_read_pos;
+ int fec_fill_pos;
+ int fec_skip;
+ int analysis_pos;
+ int predict_pos;
+ float pcm[PLC_BUF_SIZE];
+ int blend;
+ float features[NB_TOTAL_FEATURES];
+ float cont_features[CONT_VECTORS*NB_FEATURES];
+ int loss_count;
+ PLCNetState plc_net;
+ PLCNetState plc_bak[2];
+};
+
+void preemphasis(float *y, float *mem, const float *x, float coef, int N);
+
+void compute_frame_features(LPCNetEncState *st, const float *in, int arch);
+
+void lpcnet_reset_signal(LPCNetState *lpcnet);
+void run_frame_network(LPCNetState *lpcnet, float *gru_a_condition, float *gru_b_condition, float *lpc, const float *features);
+void run_frame_network_deferred(LPCNetState *lpcnet, const float *features);
+void run_frame_network_flush(LPCNetState *lpcnet);
+
+
+void lpcnet_synthesize_tail_impl(LPCNetState *lpcnet, opus_int16 *output, int N, int preload);
+void lpcnet_synthesize_impl(LPCNetState *lpcnet, const float *features, opus_int16 *output, int N, int preload);
+void lpcnet_synthesize_blend_impl(LPCNetState *lpcnet, const opus_int16 *pcm_in, opus_int16 *output, int N);
+
+void run_frame_network(LPCNetState *lpcnet, float *gru_a_condition, float *gru_b_condition, float *lpc, const float *features);
+
+#endif
diff --git a/dnn/lpcnet_tables.c b/dnn/lpcnet_tables.c
new file mode 100644
index 00000000..467cb473
--- /dev/null
+++ b/dnn/lpcnet_tables.c
@@ -0,0 +1,307 @@
+/* The contents of this file was automatically generated by dump_lpcnet_tables.c*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "kiss_fft.h"
+
+static const arch_fft_state arch_fft = {0, NULL};
+
+static const opus_int16 fft_bitrev[320] = {
+0, 64, 128, 192, 256, 16, 80, 144, 208, 272, 32, 96, 160, 224, 288,
+48, 112, 176, 240, 304, 4, 68, 132, 196, 260, 20, 84, 148, 212, 276,
+36, 100, 164, 228, 292, 52, 116, 180, 244, 308, 8, 72, 136, 200, 264,
+24, 88, 152, 216, 280, 40, 104, 168, 232, 296, 56, 120, 184, 248, 312,
+12, 76, 140, 204, 268, 28, 92, 156, 220, 284, 44, 108, 172, 236, 300,
+60, 124, 188, 252, 316, 1, 65, 129, 193, 257, 17, 81, 145, 209, 273,
+33, 97, 161, 225, 289, 49, 113, 177, 241, 305, 5, 69, 133, 197, 261,
+21, 85, 149, 213, 277, 37, 101, 165, 229, 293, 53, 117, 181, 245, 309,
+9, 73, 137, 201, 265, 25, 89, 153, 217, 281, 41, 105, 169, 233, 297,
+57, 121, 185, 249, 313, 13, 77, 141, 205, 269, 29, 93, 157, 221, 285,
+45, 109, 173, 237, 301, 61, 125, 189, 253, 317, 2, 66, 130, 194, 258,
+18, 82, 146, 210, 274, 34, 98, 162, 226, 290, 50, 114, 178, 242, 306,
+6, 70, 134, 198, 262, 22, 86, 150, 214, 278, 38, 102, 166, 230, 294,
+54, 118, 182, 246, 310, 10, 74, 138, 202, 266, 26, 90, 154, 218, 282,
+42, 106, 170, 234, 298, 58, 122, 186, 250, 314, 14, 78, 142, 206, 270,
+30, 94, 158, 222, 286, 46, 110, 174, 238, 302, 62, 126, 190, 254, 318,
+3, 67, 131, 195, 259, 19, 83, 147, 211, 275, 35, 99, 163, 227, 291,
+51, 115, 179, 243, 307, 7, 71, 135, 199, 263, 23, 87, 151, 215, 279,
+39, 103, 167, 231, 295, 55, 119, 183, 247, 311, 11, 75, 139, 203, 267,
+27, 91, 155, 219, 283, 43, 107, 171, 235, 299, 59, 123, 187, 251, 315,
+15, 79, 143, 207, 271, 31, 95, 159, 223, 287, 47, 111, 175, 239, 303,
+63, 127, 191, 255, 319, };
+
+static const kiss_twiddle_cpx fft_twiddles[320] = {
+{1.00000000f, -0.00000000f}, {0.999807239f, -0.0196336918f},
+{0.999229014f, -0.0392598175f}, {0.998265624f, -0.0588708036f},
+{0.996917307f, -0.0784590989f}, {0.995184720f, -0.0980171412f},
+{0.993068457f, -0.117537394f}, {0.990569353f, -0.137012348f},
+{0.987688363f, -0.156434461f}, {0.984426558f, -0.175796285f},
+{0.980785251f, -0.195090324f}, {0.976765871f, -0.214309156f},
+{0.972369909f, -0.233445361f}, {0.967599094f, -0.252491564f},
+{0.962455213f, -0.271440446f}, {0.956940353f, -0.290284663f},
+{0.951056540f, -0.309017003f}, {0.944806039f, -0.327630192f},
+{0.938191354f, -0.346117049f}, {0.931214929f, -0.364470512f},
+{0.923879504f, -0.382683426f}, {0.916187942f, -0.400748819f},
+{0.908143163f, -0.418659747f}, {0.899748266f, -0.436409235f},
+{0.891006529f, -0.453990489f}, {0.881921291f, -0.471396744f},
+{0.872496009f, -0.488621235f}, {0.862734377f, -0.505657375f},
+{0.852640152f, -0.522498548f}, {0.842217207f, -0.539138317f},
+{0.831469595f, -0.555570245f}, {0.820401430f, -0.571787953f},
+{0.809017003f, -0.587785244f}, {0.797320664f, -0.603555918f},
+{0.785316944f, -0.619093955f}, {0.773010433f, -0.634393275f},
+{0.760405958f, -0.649448037f}, {0.747508347f, -0.664252460f},
+{0.734322488f, -0.678800762f}, {0.720853567f, -0.693087339f},
+{0.707106769f, -0.707106769f}, {0.693087339f, -0.720853567f},
+{0.678800762f, -0.734322488f}, {0.664252460f, -0.747508347f},
+{0.649448037f, -0.760405958f}, {0.634393275f, -0.773010433f},
+{0.619093955f, -0.785316944f}, {0.603555918f, -0.797320664f},
+{0.587785244f, -0.809017003f}, {0.571787953f, -0.820401430f},
+{0.555570245f, -0.831469595f}, {0.539138317f, -0.842217207f},
+{0.522498548f, -0.852640152f}, {0.505657375f, -0.862734377f},
+{0.488621235f, -0.872496009f}, {0.471396744f, -0.881921291f},
+{0.453990489f, -0.891006529f}, {0.436409235f, -0.899748266f},
+{0.418659747f, -0.908143163f}, {0.400748819f, -0.916187942f},
+{0.382683426f, -0.923879504f}, {0.364470512f, -0.931214929f},
+{0.346117049f, -0.938191354f}, {0.327630192f, -0.944806039f},
+{0.309017003f, -0.951056540f}, {0.290284663f, -0.956940353f},
+{0.271440446f, -0.962455213f}, {0.252491564f, -0.967599094f},
+{0.233445361f, -0.972369909f}, {0.214309156f, -0.976765871f},
+{0.195090324f, -0.980785251f}, {0.175796285f, -0.984426558f},
+{0.156434461f, -0.987688363f}, {0.137012348f, -0.990569353f},
+{0.117537394f, -0.993068457f}, {0.0980171412f, -0.995184720f},
+{0.0784590989f, -0.996917307f}, {0.0588708036f, -0.998265624f},
+{0.0392598175f, -0.999229014f}, {0.0196336918f, -0.999807239f},
+{6.12323426e-17f, -1.00000000f}, {-0.0196336918f, -0.999807239f},
+{-0.0392598175f, -0.999229014f}, {-0.0588708036f, -0.998265624f},
+{-0.0784590989f, -0.996917307f}, {-0.0980171412f, -0.995184720f},
+{-0.117537394f, -0.993068457f}, {-0.137012348f, -0.990569353f},
+{-0.156434461f, -0.987688363f}, {-0.175796285f, -0.984426558f},
+{-0.195090324f, -0.980785251f}, {-0.214309156f, -0.976765871f},
+{-0.233445361f, -0.972369909f}, {-0.252491564f, -0.967599094f},
+{-0.271440446f, -0.962455213f}, {-0.290284663f, -0.956940353f},
+{-0.309017003f, -0.951056540f}, {-0.327630192f, -0.944806039f},
+{-0.346117049f, -0.938191354f}, {-0.364470512f, -0.931214929f},
+{-0.382683426f, -0.923879504f}, {-0.400748819f, -0.916187942f},
+{-0.418659747f, -0.908143163f}, {-0.436409235f, -0.899748266f},
+{-0.453990489f, -0.891006529f}, {-0.471396744f, -0.881921291f},
+{-0.488621235f, -0.872496009f}, {-0.505657375f, -0.862734377f},
+{-0.522498548f, -0.852640152f}, {-0.539138317f, -0.842217207f},
+{-0.555570245f, -0.831469595f}, {-0.571787953f, -0.820401430f},
+{-0.587785244f, -0.809017003f}, {-0.603555918f, -0.797320664f},
+{-0.619093955f, -0.785316944f}, {-0.634393275f, -0.773010433f},
+{-0.649448037f, -0.760405958f}, {-0.664252460f, -0.747508347f},
+{-0.678800762f, -0.734322488f}, {-0.693087339f, -0.720853567f},
+{-0.707106769f, -0.707106769f}, {-0.720853567f, -0.693087339f},
+{-0.734322488f, -0.678800762f}, {-0.747508347f, -0.664252460f},
+{-0.760405958f, -0.649448037f}, {-0.773010433f, -0.634393275f},
+{-0.785316944f, -0.619093955f}, {-0.797320664f, -0.603555918f},
+{-0.809017003f, -0.587785244f}, {-0.820401430f, -0.571787953f},
+{-0.831469595f, -0.555570245f}, {-0.842217207f, -0.539138317f},
+{-0.852640152f, -0.522498548f}, {-0.862734377f, -0.505657375f},
+{-0.872496009f, -0.488621235f}, {-0.881921291f, -0.471396744f},
+{-0.891006529f, -0.453990489f}, {-0.899748266f, -0.436409235f},
+{-0.908143163f, -0.418659747f}, {-0.916187942f, -0.400748819f},
+{-0.923879504f, -0.382683426f}, {-0.931214929f, -0.364470512f},
+{-0.938191354f, -0.346117049f}, {-0.944806039f, -0.327630192f},
+{-0.951056540f, -0.309017003f}, {-0.956940353f, -0.290284663f},
+{-0.962455213f, -0.271440446f}, {-0.967599094f, -0.252491564f},
+{-0.972369909f, -0.233445361f}, {-0.976765871f, -0.214309156f},
+{-0.980785251f, -0.195090324f}, {-0.984426558f, -0.175796285f},
+{-0.987688363f, -0.156434461f}, {-0.990569353f, -0.137012348f},
+{-0.993068457f, -0.117537394f}, {-0.995184720f, -0.0980171412f},
+{-0.996917307f, -0.0784590989f}, {-0.998265624f, -0.0588708036f},
+{-0.999229014f, -0.0392598175f}, {-0.999807239f, -0.0196336918f},
+{-1.00000000f, -1.22464685e-16f}, {-0.999807239f, 0.0196336918f},
+{-0.999229014f, 0.0392598175f}, {-0.998265624f, 0.0588708036f},
+{-0.996917307f, 0.0784590989f}, {-0.995184720f, 0.0980171412f},
+{-0.993068457f, 0.117537394f}, {-0.990569353f, 0.137012348f},
+{-0.987688363f, 0.156434461f}, {-0.984426558f, 0.175796285f},
+{-0.980785251f, 0.195090324f}, {-0.976765871f, 0.214309156f},
+{-0.972369909f, 0.233445361f}, {-0.967599094f, 0.252491564f},
+{-0.962455213f, 0.271440446f}, {-0.956940353f, 0.290284663f},
+{-0.951056540f, 0.309017003f}, {-0.944806039f, 0.327630192f},
+{-0.938191354f, 0.346117049f}, {-0.931214929f, 0.364470512f},
+{-0.923879504f, 0.382683426f}, {-0.916187942f, 0.400748819f},
+{-0.908143163f, 0.418659747f}, {-0.899748266f, 0.436409235f},
+{-0.891006529f, 0.453990489f}, {-0.881921291f, 0.471396744f},
+{-0.872496009f, 0.488621235f}, {-0.862734377f, 0.505657375f},
+{-0.852640152f, 0.522498548f}, {-0.842217207f, 0.539138317f},
+{-0.831469595f, 0.555570245f}, {-0.820401430f, 0.571787953f},
+{-0.809017003f, 0.587785244f}, {-0.797320664f, 0.603555918f},
+{-0.785316944f, 0.619093955f}, {-0.773010433f, 0.634393275f},
+{-0.760405958f, 0.649448037f}, {-0.747508347f, 0.664252460f},
+{-0.734322488f, 0.678800762f}, {-0.720853567f, 0.693087339f},
+{-0.707106769f, 0.707106769f}, {-0.693087339f, 0.720853567f},
+{-0.678800762f, 0.734322488f}, {-0.664252460f, 0.747508347f},
+{-0.649448037f, 0.760405958f}, {-0.634393275f, 0.773010433f},
+{-0.619093955f, 0.785316944f}, {-0.603555918f, 0.797320664f},
+{-0.587785244f, 0.809017003f}, {-0.571787953f, 0.820401430f},
+{-0.555570245f, 0.831469595f}, {-0.539138317f, 0.842217207f},
+{-0.522498548f, 0.852640152f}, {-0.505657375f, 0.862734377f},
+{-0.488621235f, 0.872496009f}, {-0.471396744f, 0.881921291f},
+{-0.453990489f, 0.891006529f}, {-0.436409235f, 0.899748266f},
+{-0.418659747f, 0.908143163f}, {-0.400748819f, 0.916187942f},
+{-0.382683426f, 0.923879504f}, {-0.364470512f, 0.931214929f},
+{-0.346117049f, 0.938191354f}, {-0.327630192f, 0.944806039f},
+{-0.309017003f, 0.951056540f}, {-0.290284663f, 0.956940353f},
+{-0.271440446f, 0.962455213f}, {-0.252491564f, 0.967599094f},
+{-0.233445361f, 0.972369909f}, {-0.214309156f, 0.976765871f},
+{-0.195090324f, 0.980785251f}, {-0.175796285f, 0.984426558f},
+{-0.156434461f, 0.987688363f}, {-0.137012348f, 0.990569353f},
+{-0.117537394f, 0.993068457f}, {-0.0980171412f, 0.995184720f},
+{-0.0784590989f, 0.996917307f}, {-0.0588708036f, 0.998265624f},
+{-0.0392598175f, 0.999229014f}, {-0.0196336918f, 0.999807239f},
+{-1.83697015e-16f, 1.00000000f}, {0.0196336918f, 0.999807239f},
+{0.0392598175f, 0.999229014f}, {0.0588708036f, 0.998265624f},
+{0.0784590989f, 0.996917307f}, {0.0980171412f, 0.995184720f},
+{0.117537394f, 0.993068457f}, {0.137012348f, 0.990569353f},
+{0.156434461f, 0.987688363f}, {0.175796285f, 0.984426558f},
+{0.195090324f, 0.980785251f}, {0.214309156f, 0.976765871f},
+{0.233445361f, 0.972369909f}, {0.252491564f, 0.967599094f},
+{0.271440446f, 0.962455213f}, {0.290284663f, 0.956940353f},
+{0.309017003f, 0.951056540f}, {0.327630192f, 0.944806039f},
+{0.346117049f, 0.938191354f}, {0.364470512f, 0.931214929f},
+{0.382683426f, 0.923879504f}, {0.400748819f, 0.916187942f},
+{0.418659747f, 0.908143163f}, {0.436409235f, 0.899748266f},
+{0.453990489f, 0.891006529f}, {0.471396744f, 0.881921291f},
+{0.488621235f, 0.872496009f}, {0.505657375f, 0.862734377f},
+{0.522498548f, 0.852640152f}, {0.539138317f, 0.842217207f},
+{0.555570245f, 0.831469595f}, {0.571787953f, 0.820401430f},
+{0.587785244f, 0.809017003f}, {0.603555918f, 0.797320664f},
+{0.619093955f, 0.785316944f}, {0.634393275f, 0.773010433f},
+{0.649448037f, 0.760405958f}, {0.664252460f, 0.747508347f},
+{0.678800762f, 0.734322488f}, {0.693087339f, 0.720853567f},
+{0.707106769f, 0.707106769f}, {0.720853567f, 0.693087339f},
+{0.734322488f, 0.678800762f}, {0.747508347f, 0.664252460f},
+{0.760405958f, 0.649448037f}, {0.773010433f, 0.634393275f},
+{0.785316944f, 0.619093955f}, {0.797320664f, 0.603555918f},
+{0.809017003f, 0.587785244f}, {0.820401430f, 0.571787953f},
+{0.831469595f, 0.555570245f}, {0.842217207f, 0.539138317f},
+{0.852640152f, 0.522498548f}, {0.862734377f, 0.505657375f},
+{0.872496009f, 0.488621235f}, {0.881921291f, 0.471396744f},
+{0.891006529f, 0.453990489f}, {0.899748266f, 0.436409235f},
+{0.908143163f, 0.418659747f}, {0.916187942f, 0.400748819f},
+{0.923879504f, 0.382683426f}, {0.931214929f, 0.364470512f},
+{0.938191354f, 0.346117049f}, {0.944806039f, 0.327630192f},
+{0.951056540f, 0.309017003f}, {0.956940353f, 0.290284663f},
+{0.962455213f, 0.271440446f}, {0.967599094f, 0.252491564f},
+{0.972369909f, 0.233445361f}, {0.976765871f, 0.214309156f},
+{0.980785251f, 0.195090324f}, {0.984426558f, 0.175796285f},
+{0.987688363f, 0.156434461f}, {0.990569353f, 0.137012348f},
+{0.993068457f, 0.117537394f}, {0.995184720f, 0.0980171412f},
+{0.996917307f, 0.0784590989f}, {0.998265624f, 0.0588708036f},
+{0.999229014f, 0.0392598175f}, {0.999807239f, 0.0196336918f},
+};
+
+const kiss_fft_state kfft = {
+320, /* nfft */
+0.0031250000f, /* scale */
+-1, /* shift */
+{5, 64, 4, 16, 4, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */
+fft_bitrev, /* bitrev*/
+fft_twiddles, /* twiddles*/
+(arch_fft_state *)&arch_fft, /* arch_fft*/
+};
+
+const float half_window[] = {
+3.78491532e-05f, 0.000340620492f, 0.000946046319f, 0.00185389258f, 0.00306380726f,
+0.00457531959f, 0.00638783723f, 0.00850064680f, 0.0109129101f, 0.0136236614f,
+0.0166318044f, 0.0199361145f, 0.0235352255f, 0.0274276342f, 0.0316116922f,
+0.0360856056f, 0.0408474281f, 0.0458950549f, 0.0512262285f, 0.0568385124f,
+0.0627293140f, 0.0688958541f, 0.0753351897f, 0.0820441842f, 0.0890194997f,
+0.0962576419f, 0.103754878f, 0.111507311f, 0.119510807f, 0.127761051f,
+0.136253506f, 0.144983411f, 0.153945804f, 0.163135484f, 0.172547072f,
+0.182174906f, 0.192013159f, 0.202055752f, 0.212296382f, 0.222728521f,
+0.233345464f, 0.244140238f, 0.255105674f, 0.266234398f, 0.277518868f,
+0.288951218f, 0.300523549f, 0.312227666f, 0.324055225f, 0.335997701f,
+0.348046392f, 0.360192508f, 0.372427016f, 0.384740859f, 0.397124738f,
+0.409569323f, 0.422065198f, 0.434602767f, 0.447172493f, 0.459764689f,
+0.472369671f, 0.484977663f, 0.497579008f, 0.510163903f, 0.522722721f,
+0.535245717f, 0.547723293f, 0.560145974f, 0.572504222f, 0.584788740f,
+0.596990347f, 0.609099925f, 0.621108532f, 0.633007407f, 0.644788086f,
+0.656442165f, 0.667961538f, 0.679338276f, 0.690564752f, 0.701633692f,
+0.712537885f, 0.723270535f, 0.733825266f, 0.744195819f, 0.754376352f,
+0.764361382f, 0.774145722f, 0.783724606f, 0.793093503f, 0.802248418f,
+0.811185598f, 0.819901764f, 0.828393936f, 0.836659551f, 0.844696403f,
+0.852502763f, 0.860077202f, 0.867418647f, 0.874526560f, 0.881400526f,
+0.888040781f, 0.894447744f, 0.900622249f, 0.906565487f, 0.912279010f,
+0.917764664f, 0.923024654f, 0.928061485f, 0.932878017f, 0.937477291f,
+0.941862822f, 0.946038187f, 0.950007319f, 0.953774393f, 0.957343817f,
+0.960720181f, 0.963908315f, 0.966913164f, 0.969739914f, 0.972393870f,
+0.974880517f, 0.977205336f, 0.979374051f, 0.981392324f, 0.983266115f,
+0.985001266f, 0.986603677f, 0.988079309f, 0.989434063f, 0.990674019f,
+0.991804957f, 0.992832899f, 0.993763626f, 0.994602919f, 0.995356441f,
+0.996029854f, 0.996628702f, 0.997158289f, 0.997623861f, 0.998030603f,
+0.998383403f, 0.998687088f, 0.998946249f, 0.999165416f, 0.999348700f,
+0.999500215f, 0.999623775f, 0.999723017f, 0.999801278f, 0.999861658f,
+0.999907196f, 0.999940455f, 0.999963880f, 0.999979615f, 0.999989510f,
+0.999995291f, 0.999998271f, 0.999999523f, 0.999999940f, 1.00000000f,
+};
+
+const float dct_table[] = {
+0.707106769f, 0.996194720f, 0.984807730f, 0.965925813f, 0.939692616f,
+0.906307817f, 0.866025388f, 0.819152057f, 0.766044438f, 0.707106769f,
+0.642787635f, 0.573576450f, 0.500000000f, 0.422618270f, 0.342020154f,
+0.258819044f, 0.173648179f, 0.0871557444f, 0.707106769f, 0.965925813f,
+0.866025388f, 0.707106769f, 0.500000000f, 0.258819044f, 6.12323426e-17f,
+-0.258819044f, -0.500000000f, -0.707106769f, -0.866025388f, -0.965925813f,
+-1.00000000f, -0.965925813f, -0.866025388f, -0.707106769f, -0.500000000f,
+-0.258819044f, 0.707106769f, 0.906307817f, 0.642787635f, 0.258819044f,
+-0.173648179f, -0.573576450f, -0.866025388f, -0.996194720f, -0.939692616f,
+-0.707106769f, -0.342020154f, 0.0871557444f, 0.500000000f, 0.819152057f,
+0.984807730f, 0.965925813f, 0.766044438f, 0.422618270f, 0.707106769f,
+0.819152057f, 0.342020154f, -0.258819044f, -0.766044438f, -0.996194720f,
+-0.866025388f, -0.422618270f, 0.173648179f, 0.707106769f, 0.984807730f,
+0.906307817f, 0.500000000f, -0.0871557444f, -0.642787635f, -0.965925813f,
+-0.939692616f, -0.573576450f, 0.707106769f, 0.707106769f, 6.12323426e-17f,
+-0.707106769f, -1.00000000f, -0.707106769f, -1.83697015e-16f, 0.707106769f,
+1.00000000f, 0.707106769f, 3.06161700e-16f, -0.707106769f, -1.00000000f,
+-0.707106769f, -4.28626385e-16f, 0.707106769f, 1.00000000f, 0.707106769f,
+0.707106769f, 0.573576450f, -0.342020154f, -0.965925813f, -0.766044438f,
+0.0871557444f, 0.866025388f, 0.906307817f, 0.173648179f, -0.707106769f,
+-0.984807730f, -0.422618270f, 0.500000000f, 0.996194720f, 0.642787635f,
+-0.258819044f, -0.939692616f, -0.819152057f, 0.707106769f, 0.422618270f,
+-0.642787635f, -0.965925813f, -0.173648179f, 0.819152057f, 0.866025388f,
+-0.0871557444f, -0.939692616f, -0.707106769f, 0.342020154f, 0.996194720f,
+0.500000000f, -0.573576450f, -0.984807730f, -0.258819044f, 0.766044438f,
+0.906307817f, 0.707106769f, 0.258819044f, -0.866025388f, -0.707106769f,
+0.500000000f, 0.965925813f, 3.06161700e-16f, -0.965925813f, -0.500000000f,
+0.707106769f, 0.866025388f, -0.258819044f, -1.00000000f, -0.258819044f,
+0.866025388f, 0.707106769f, -0.500000000f, -0.965925813f, 0.707106769f,
+0.0871557444f, -0.984807730f, -0.258819044f, 0.939692616f, 0.422618270f,
+-0.866025388f, -0.573576450f, 0.766044438f, 0.707106769f, -0.642787635f,
+-0.819152057f, 0.500000000f, 0.906307817f, -0.342020154f, -0.965925813f,
+0.173648179f, 0.996194720f, 0.707106769f, -0.0871557444f, -0.984807730f,
+0.258819044f, 0.939692616f, -0.422618270f, -0.866025388f, 0.573576450f,
+0.766044438f, -0.707106769f, -0.642787635f, 0.819152057f, 0.500000000f,
+-0.906307817f, -0.342020154f, 0.965925813f, 0.173648179f, -0.996194720f,
+0.707106769f, -0.258819044f, -0.866025388f, 0.707106769f, 0.500000000f,
+-0.965925813f, -4.28626385e-16f, 0.965925813f, -0.500000000f, -0.707106769f,
+0.866025388f, 0.258819044f, -1.00000000f, 0.258819044f, 0.866025388f,
+-0.707106769f, -0.500000000f, 0.965925813f, 0.707106769f, -0.422618270f,
+-0.642787635f, 0.965925813f, -0.173648179f, -0.819152057f, 0.866025388f,
+0.0871557444f, -0.939692616f, 0.707106769f, 0.342020154f, -0.996194720f,
+0.500000000f, 0.573576450f, -0.984807730f, 0.258819044f, 0.766044438f,
+-0.906307817f, 0.707106769f, -0.573576450f, -0.342020154f, 0.965925813f,
+-0.766044438f, -0.0871557444f, 0.866025388f, -0.906307817f, 0.173648179f,
+0.707106769f, -0.984807730f, 0.422618270f, 0.500000000f, -0.996194720f,
+0.642787635f, 0.258819044f, -0.939692616f, 0.819152057f, 0.707106769f,
+-0.707106769f, -1.83697015e-16f, 0.707106769f, -1.00000000f, 0.707106769f,
+5.51091070e-16f, -0.707106769f, 1.00000000f, -0.707106769f, -2.69484189e-15f,
+0.707106769f, -1.00000000f, 0.707106769f, -4.90477710e-16f, -0.707106769f,
+1.00000000f, -0.707106769f, 0.707106769f, -0.819152057f, 0.342020154f,
+0.258819044f, -0.766044438f, 0.996194720f, -0.866025388f, 0.422618270f,
+0.173648179f, -0.707106769f, 0.984807730f, -0.906307817f, 0.500000000f,
+0.0871557444f, -0.642787635f, 0.965925813f, -0.939692616f, 0.573576450f,
+0.707106769f, -0.906307817f, 0.642787635f, -0.258819044f, -0.173648179f,
+0.573576450f, -0.866025388f, 0.996194720f, -0.939692616f, 0.707106769f,
+-0.342020154f, -0.0871557444f, 0.500000000f, -0.819152057f, 0.984807730f,
+-0.965925813f, 0.766044438f, -0.422618270f, 0.707106769f, -0.965925813f,
+0.866025388f, -0.707106769f, 0.500000000f, -0.258819044f, 1.10280111e-15f,
+0.258819044f, -0.500000000f, 0.707106769f, -0.866025388f, 0.965925813f,
+-1.00000000f, 0.965925813f, -0.866025388f, 0.707106769f, -0.500000000f,
+0.258819044f, 0.707106769f, -0.996194720f, 0.984807730f, -0.965925813f,
+0.939692616f, -0.906307817f, 0.866025388f, -0.819152057f, 0.766044438f,
+-0.707106769f, 0.642787635f, -0.573576450f, 0.500000000f, -0.422618270f,
+0.342020154f, -0.258819044f, 0.173648179f, -0.0871557444f, };
diff --git a/dnn/meson.build b/dnn/meson.build
new file mode 100644
index 00000000..737d4a02
--- /dev/null
+++ b/dnn/meson.build
@@ -0,0 +1,64 @@
+dnn_sources = sources['DEEP_PLC_SOURCES']
+
+dred_sources = sources['DRED_SOURCES']
+if opt_enable_dred
+ dnn_sources += dred_sources
+endif
+
+osce_sources = sources['OSCE_SOURCES']
+if opt_enable_osce
+ dnn_sources += osce_sources
+endif
+
+dnn_sources_sse2 = sources['DNN_SOURCES_SSE2']
+dnn_sources_sse4_1 = sources['DNN_SOURCES_SSE4_1']
+dnn_sources_avx2 = sources['DNN_SOURCES_AVX2']
+
+dnn_sources_neon_intr = sources['DNN_SOURCES_NEON']
+dnn_sources_dotprod_intr = sources['DNN_SOURCES_DOTPROD']
+
+dnn_includes = [opus_includes]
+dnn_static_libs = []
+
+if host_cpu_family in ['x86', 'x86_64'] and opus_conf.has('OPUS_HAVE_RTCD')
+ dnn_sources += sources['DNN_SOURCES_X86_RTCD']
+endif
+
+if host_cpu_family in ['arm', 'aarch64'] and have_arm_intrinsics_or_asm
+ if opus_conf.has('OPUS_HAVE_RTCD')
+ dnn_sources += sources['DNN_SOURCES_ARM_RTCD']
+ endif
+endif
+
+foreach intr_name : ['sse2', 'sse4_1', 'avx2', 'neon_intr', 'dotprod_intr']
+ have_intr = get_variable('have_' + intr_name)
+ if not have_intr
+ continue
+ endif
+
+ intr_sources = get_variable('dnn_sources_' + intr_name)
+
+ intr_args = get_variable('opus_@0@_args'.format(intr_name), [])
+ dnn_static_libs += static_library('dnn_' + intr_name, intr_sources,
+ c_args: intr_args,
+ include_directories: dnn_includes,
+ install: false)
+endforeach
+
+dnn_c_args = []
+if host_machine.system() == 'windows'
+ dnn_c_args += ['-DDLL_EXPORT']
+endif
+
+
+if opt_enable_deep_plc
+ dnn_lib = static_library('opus-dnn',
+ dnn_sources,
+ c_args: dnn_c_args,
+ include_directories: dnn_includes,
+ link_whole: [dnn_static_libs],
+ dependencies: libm,
+ install: false)
+else
+ dnn_lib = []
+endif
diff --git a/dnn/nndsp.c b/dnn/nndsp.c
new file mode 100644
index 00000000..caa77038
--- /dev/null
+++ b/dnn/nndsp.c
@@ -0,0 +1,416 @@
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#include "nndsp.h"
+#include "arch.h"
+#include "nnet.h"
+#include "os_support.h"
+#include "pitch.h"
+
+#include <math.h>
+
+#ifndef M_PI
+#define M_PI 3.141592653589793f
+#endif
+
+#define KERNEL_INDEX(i_out_channels, i_in_channels, i_kernel) ((((i_out_channels) * in_channels) + (i_in_channels)) * kernel_size + (i_kernel))
+
+void init_adaconv_state(AdaConvState *hAdaConv)
+{
+ OPUS_CLEAR(hAdaConv, 1);
+}
+
+void init_adacomb_state(AdaCombState *hAdaComb)
+{
+ OPUS_CLEAR(hAdaComb, 1);
+}
+
+void init_adashape_state(AdaShapeState *hAdaShape)
+{
+ OPUS_CLEAR(hAdaShape, 1);
+}
+
+void compute_overlap_window(float *window, int overlap_size)
+{
+ int i_sample;
+ for (i_sample=0; i_sample < overlap_size; i_sample++)
+ {
+ window[i_sample] = 0.5f + 0.5f * cos(M_PI * (i_sample + 0.5f) / overlap_size);
+ }
+}
+
+#ifdef DEBUG_NNDSP
+void print_float_vector(const char* name, const float *vec, int length)
+{
+ for (int i = 0; i < length; i ++)
+ {
+ printf("%s[%d]: %f\n", name, i, vec[i]);
+ }
+}
+#endif
+
+static void scale_kernel(
+ float *kernel,
+ int in_channels,
+ int out_channels,
+ int kernel_size,
+ float *gain
+)
+/* normalizes (p-norm) kernel over input channel and kernel dimension */
+{
+ float norm;
+ int i_in_channels, i_out_channels, i_kernel;
+
+ for (i_out_channels = 0; i_out_channels < out_channels; i_out_channels++)
+ {
+ norm = 0;
+ for (i_in_channels = 0; i_in_channels < in_channels; i_in_channels ++)
+ {
+ for (i_kernel = 0; i_kernel < kernel_size; i_kernel++)
+ {
+ norm += kernel[KERNEL_INDEX(i_out_channels, i_in_channels, i_kernel)] * kernel[KERNEL_INDEX(i_out_channels, i_in_channels, i_kernel)];
+ }
+ }
+#ifdef DEBUG_NNDSP
+ printf("kernel norm: %f, %f\n", norm, sqrt(norm));
+#endif
+ norm = 1.f / (1e-6f + sqrt(norm));
+ for (i_in_channels = 0; i_in_channels < in_channels; i_in_channels++)
+ {
+ for (i_kernel = 0; i_kernel < kernel_size; i_kernel++)
+ {
+
+ kernel[KERNEL_INDEX(i_out_channels, i_in_channels, i_kernel)] *= norm * gain[i_out_channels];
+ }
+ }
+ }
+}
+
+static void transform_gains(
+ float *gains,
+ int num_gains,
+ float filter_gain_a,
+ float filter_gain_b
+)
+{
+ int i;
+ for (i = 0; i < num_gains; i++)
+ {
+ gains[i] = exp(filter_gain_a * gains[i] + filter_gain_b);
+ }
+}
+
+void adaconv_process_frame(
+ AdaConvState* hAdaConv,
+ float *x_out,
+ const float *x_in,
+ const float *features,
+ const LinearLayer *kernel_layer,
+ const LinearLayer *gain_layer,
+ int feature_dim,
+ int frame_size,
+ int overlap_size,
+ int in_channels,
+ int out_channels,
+ int kernel_size,
+ int left_padding,
+ float filter_gain_a,
+ float filter_gain_b,
+ float shape_gain,
+ float *window,
+ int arch
+)
+{
+ float output_buffer[ADACONV_MAX_FRAME_SIZE * ADACONV_MAX_OUTPUT_CHANNELS];
+ float kernel_buffer[ADACONV_MAX_KERNEL_SIZE * ADACONV_MAX_INPUT_CHANNELS * ADACONV_MAX_OUTPUT_CHANNELS];
+ float input_buffer[ADACONV_MAX_INPUT_CHANNELS * (ADACONV_MAX_FRAME_SIZE + ADACONV_MAX_KERNEL_SIZE)];
+ float kernel0[ADACONV_MAX_KERNEL_SIZE];
+ float kernel1[ADACONV_MAX_KERNEL_SIZE];
+ float channel_buffer0[ADACONV_MAX_OVERLAP_SIZE];
+ float channel_buffer1[ADACONV_MAX_FRAME_SIZE];
+ float gain_buffer[ADACONV_MAX_OUTPUT_CHANNELS];
+ float *p_input;
+ int i_in_channels, i_out_channels, i_sample;
+
+ (void) feature_dim; /* ToDo: figure out whether we might need this information */
+
+ celt_assert(shape_gain == 1);
+ celt_assert(left_padding == kernel_size - 1); /* currently only supports causal version. Non-causal version not difficult to implement but will require third loop */
+ celt_assert(kernel_size < frame_size);
+
+ OPUS_CLEAR(output_buffer, ADACONV_MAX_FRAME_SIZE * ADACONV_MAX_OUTPUT_CHANNELS);
+ OPUS_CLEAR(kernel_buffer, ADACONV_MAX_KERNEL_SIZE * ADACONV_MAX_INPUT_CHANNELS * ADACONV_MAX_OUTPUT_CHANNELS);
+ OPUS_CLEAR(input_buffer, ADACONV_MAX_INPUT_CHANNELS * (ADACONV_MAX_FRAME_SIZE + ADACONV_MAX_KERNEL_SIZE));
+
+#ifdef DEBUG_NNDSP
+ print_float_vector("x_in", x_in, in_channels * frame_size);
+#endif
+
+ /* prepare input */
+ for (i_in_channels=0; i_in_channels < in_channels; i_in_channels ++)
+ {
+ OPUS_COPY(input_buffer + i_in_channels * (kernel_size + frame_size), hAdaConv->history + i_in_channels * kernel_size, kernel_size);
+ OPUS_COPY(input_buffer + kernel_size + i_in_channels * (kernel_size + frame_size), x_in + frame_size * i_in_channels, frame_size);
+ }
+ p_input = input_buffer + kernel_size;
+
+
+ /* calculate new kernel and new gain */
+ compute_generic_dense(kernel_layer, kernel_buffer, features, ACTIVATION_LINEAR, arch);
+ compute_generic_dense(gain_layer, gain_buffer, features, ACTIVATION_TANH, arch);
+#ifdef DEBUG_NNDSP
+ print_float_vector("features", features, feature_dim);
+ print_float_vector("adaconv_kernel_raw", kernel_buffer, in_channels * out_channels * kernel_size);
+ print_float_vector("adaconv_gain_raw", gain_buffer, out_channels);
+#endif
+ transform_gains(gain_buffer, out_channels, filter_gain_a, filter_gain_b);
+ scale_kernel(kernel_buffer, in_channels, out_channels, kernel_size, gain_buffer);
+
+#ifdef DEBUG_NNDSP
+ print_float_vector("adaconv_kernel", kernel_buffer, in_channels * out_channels * kernel_size);
+ print_float_vector("adaconv_gain", gain_buffer, out_channels);
+#endif
+
+ /* calculate overlapping part using kernel from last frame */
+
+ for (i_out_channels = 0; i_out_channels < out_channels; i_out_channels++)
+ {
+ for (i_in_channels = 0; i_in_channels < in_channels; i_in_channels++)
+ {
+ OPUS_CLEAR(kernel0, ADACONV_MAX_KERNEL_SIZE);
+ OPUS_CLEAR(kernel1, ADACONV_MAX_KERNEL_SIZE);
+
+ OPUS_COPY(kernel0, hAdaConv->last_kernel + KERNEL_INDEX(i_out_channels, i_in_channels, 0), kernel_size);
+ OPUS_COPY(kernel1, kernel_buffer + KERNEL_INDEX(i_out_channels, i_in_channels, 0), kernel_size);
+ celt_pitch_xcorr(kernel0, p_input + i_in_channels * (frame_size + kernel_size) - left_padding, channel_buffer0, ADACONV_MAX_KERNEL_SIZE, overlap_size, arch);
+ celt_pitch_xcorr(kernel1, p_input + i_in_channels * (frame_size + kernel_size) - left_padding, channel_buffer1, ADACONV_MAX_KERNEL_SIZE, frame_size, arch);
+ for (i_sample = 0; i_sample < overlap_size; i_sample++)
+ {
+ output_buffer[i_sample + i_out_channels * frame_size] += window[i_sample] * channel_buffer0[i_sample];
+ output_buffer[i_sample + i_out_channels * frame_size] += (1.f - window[i_sample]) * channel_buffer1[i_sample];
+ }
+ for (i_sample = overlap_size; i_sample < frame_size; i_sample++)
+ {
+ output_buffer[i_sample + i_out_channels * frame_size] += channel_buffer1[i_sample];
+ }
+ }
+ }
+
+ OPUS_COPY(x_out, output_buffer, out_channels * frame_size);
+
+#ifdef DEBUG_NNDSP
+ print_float_vector("x_out", x_out, out_channels * frame_size);
+#endif
+
+ /* buffer update */
+ for (i_in_channels=0; i_in_channels < in_channels; i_in_channels ++)
+ {
+ OPUS_COPY(hAdaConv->history + i_in_channels * kernel_size, p_input + i_in_channels * (frame_size + kernel_size) + frame_size - kernel_size, kernel_size);
+ }
+ OPUS_COPY(hAdaConv->last_kernel, kernel_buffer, kernel_size * in_channels * out_channels);
+}
+
+void adacomb_process_frame(
+ AdaCombState* hAdaComb,
+ float *x_out,
+ const float *x_in,
+ const float *features,
+ const LinearLayer *kernel_layer,
+ const LinearLayer *gain_layer,
+ const LinearLayer *global_gain_layer,
+ int pitch_lag,
+ int feature_dim,
+ int frame_size,
+ int overlap_size,
+ int kernel_size,
+ int left_padding,
+ float filter_gain_a,
+ float filter_gain_b,
+ float log_gain_limit,
+ float *window,
+ int arch
+)
+{
+ float output_buffer[ADACOMB_MAX_FRAME_SIZE];
+ float output_buffer_last[ADACOMB_MAX_FRAME_SIZE];
+ float kernel_buffer[ADACOMB_MAX_KERNEL_SIZE];
+ float input_buffer[ADACOMB_MAX_FRAME_SIZE + ADACOMB_MAX_LAG + ADACOMB_MAX_KERNEL_SIZE];
+ float gain, global_gain;
+ float *p_input;
+ int i_sample;
+ float kernel[16];
+ float last_kernel[16];
+
+ (void) feature_dim; /* ToDo: figure out whether we might need this information */
+
+ OPUS_CLEAR(output_buffer, ADACOMB_MAX_FRAME_SIZE);
+ OPUS_CLEAR(kernel_buffer, ADACOMB_MAX_KERNEL_SIZE);
+ OPUS_CLEAR(input_buffer, ADACOMB_MAX_FRAME_SIZE + ADACOMB_MAX_LAG + ADACOMB_MAX_KERNEL_SIZE);
+
+ OPUS_COPY(input_buffer, hAdaComb->history, kernel_size + ADACOMB_MAX_LAG);
+ OPUS_COPY(input_buffer + kernel_size + ADACOMB_MAX_LAG, x_in, frame_size);
+ p_input = input_buffer + kernel_size + ADACOMB_MAX_LAG;
+
+ /* calculate new kernel and new gain */
+ compute_generic_dense(kernel_layer, kernel_buffer, features, ACTIVATION_LINEAR, arch);
+ compute_generic_dense(gain_layer, &gain, features, ACTIVATION_RELU, arch);
+ compute_generic_dense(global_gain_layer, &global_gain, features, ACTIVATION_TANH, arch);
+#ifdef DEBUG_NNDSP
+ print_float_vector("features", features, feature_dim);
+ print_float_vector("adacomb_kernel_raw", kernel_buffer, kernel_size);
+ print_float_vector("adacomb_gain_raw", &gain, 1);
+ print_float_vector("adacomb_global_gain_raw", &global_gain, 1);
+#endif
+ gain = exp(log_gain_limit - gain);
+ global_gain = exp(filter_gain_a * global_gain + filter_gain_b);
+ scale_kernel(kernel_buffer, 1, 1, kernel_size, &gain);
+
+#ifdef DEBUG_NNDSP
+ print_float_vector("adacomb_kernel", kernel_buffer, kernel_size);
+ print_float_vector("adacomb_gain", &gain, 1);
+#endif
+
+ OPUS_CLEAR(kernel, ADACOMB_MAX_KERNEL_SIZE);
+ OPUS_CLEAR(last_kernel, ADACOMB_MAX_KERNEL_SIZE);
+ OPUS_COPY(kernel, kernel_buffer, kernel_size);
+ OPUS_COPY(last_kernel, hAdaComb->last_kernel, kernel_size);
+
+ celt_pitch_xcorr(last_kernel, &p_input[- left_padding - hAdaComb->last_pitch_lag], output_buffer_last, ADACOMB_MAX_KERNEL_SIZE, overlap_size, arch);
+
+ celt_pitch_xcorr(kernel, &p_input[- left_padding - pitch_lag], output_buffer, ADACOMB_MAX_KERNEL_SIZE, frame_size, arch);
+ for (i_sample = 0; i_sample < overlap_size; i_sample++)
+ {
+ output_buffer[i_sample] = hAdaComb->last_global_gain * window[i_sample] * output_buffer_last[i_sample] + global_gain * (1.f - window[i_sample]) * output_buffer[i_sample];
+ }
+
+ for (i_sample = 0; i_sample < overlap_size; i_sample++)
+ {
+ output_buffer[i_sample] += (window[i_sample] * hAdaComb->last_global_gain + (1.f - window[i_sample]) * global_gain) * p_input[i_sample];
+ }
+
+ for (i_sample = overlap_size; i_sample < frame_size; i_sample++)
+ {
+ output_buffer[i_sample] = global_gain * (output_buffer[i_sample] + p_input[i_sample]);
+ }
+ OPUS_COPY(x_out, output_buffer, frame_size);
+
+#ifdef DEBUG_NNDSP
+ print_float_vector("x_out", x_out, frame_size);
+#endif
+
+ /* buffer update */
+ OPUS_COPY(hAdaComb->last_kernel, kernel_buffer, kernel_size);
+ OPUS_COPY(hAdaComb->history, p_input + frame_size - kernel_size - ADACOMB_MAX_LAG, kernel_size + ADACOMB_MAX_LAG);
+ hAdaComb->last_pitch_lag = pitch_lag;
+ hAdaComb->last_global_gain = global_gain;
+}
+
+
+void adashape_process_frame(
+ AdaShapeState *hAdaShape,
+ float *x_out,
+ const float *x_in,
+ const float *features,
+ const LinearLayer *alpha1f,
+ const LinearLayer *alpha1t,
+ const LinearLayer *alpha2,
+ int feature_dim,
+ int frame_size,
+ int avg_pool_k,
+ int arch
+)
+{
+ float in_buffer[ADASHAPE_MAX_INPUT_DIM + ADASHAPE_MAX_FRAME_SIZE];
+ float out_buffer[ADASHAPE_MAX_FRAME_SIZE];
+ float tmp_buffer[ADASHAPE_MAX_FRAME_SIZE];
+ int i, k;
+ int tenv_size;
+ float mean;
+ float *tenv;
+
+ celt_assert(frame_size % avg_pool_k == 0);
+ celt_assert(feature_dim + frame_size / avg_pool_k + 1 < ADASHAPE_MAX_INPUT_DIM);
+
+ tenv_size = frame_size / avg_pool_k;
+ tenv = in_buffer + feature_dim;
+ OPUS_CLEAR(tenv, tenv_size + 1);
+
+ OPUS_COPY(in_buffer, features, feature_dim);
+
+ /* calculate temporal envelope */
+ mean = 0;
+ for (i = 0; i < tenv_size; i++)
+ {
+ for (k = 0; k < avg_pool_k; k++)
+ {
+ tenv[i] += fabs(x_in[i * avg_pool_k + k]);
+ }
+ tenv[i] = log(tenv[i] / avg_pool_k + 1.52587890625e-05f);
+ mean += tenv[i];
+ }
+ mean /= tenv_size;
+ for (i = 0; i < tenv_size; i++)
+ {
+ tenv[i] -= mean;
+ }
+ tenv[tenv_size] = mean;
+#ifdef DEBUG_NNDSP
+ print_float_vector("tenv", tenv, tenv_size + 1);
+#endif
+
+ /* calculate temporal weights */
+#ifdef DEBUG_NNDSP
+ print_float_vector("alpha1_in", in_buffer, feature_dim + tenv_size + 1);
+#endif
+ compute_generic_conv1d(alpha1f, out_buffer, hAdaShape->conv_alpha1f_state, in_buffer, feature_dim, ACTIVATION_LINEAR, arch);
+ compute_generic_conv1d(alpha1t, tmp_buffer, hAdaShape->conv_alpha1t_state, tenv, tenv_size + 1, ACTIVATION_LINEAR, arch);
+#ifdef DEBUG_NNDSP
+ print_float_vector("alpha1_out", out_buffer, frame_size);
+#endif
+ /* compute leaky ReLU by hand. ToDo: try tanh activation */
+ for (i = 0; i < frame_size; i ++)
+ {
+ float tmp = out_buffer[i] + tmp_buffer[i];
+ in_buffer[i] = tmp >= 0 ? tmp : 0.2 * tmp;
+ }
+#ifdef DEBUG_NNDSP
+ print_float_vector("post_alpha1", in_buffer, frame_size);
+#endif
+ compute_generic_conv1d(alpha2, out_buffer, hAdaShape->conv_alpha2_state, in_buffer, frame_size, ACTIVATION_LINEAR, arch);
+
+ /* shape signal */
+ for (i = 0; i < frame_size; i ++)
+ {
+ x_out[i] = exp(out_buffer[i]) * x_in[i];
+ }
+
+}
diff --git a/dnn/nndsp.h b/dnn/nndsp.h
new file mode 100644
index 00000000..6021250f
--- /dev/null
+++ b/dnn/nndsp.h
@@ -0,0 +1,143 @@
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef NNDSP_H
+#define NNDSP_H
+
+#include "opus_types.h"
+#include "nnet.h"
+#include <string.h>
+
+
+#define ADACONV_MAX_KERNEL_SIZE 16
+#define ADACONV_MAX_INPUT_CHANNELS 2
+#define ADACONV_MAX_OUTPUT_CHANNELS 2
+#define ADACONV_MAX_FRAME_SIZE 80
+#define ADACONV_MAX_OVERLAP_SIZE 40
+
+#define ADACOMB_MAX_LAG 300
+#define ADACOMB_MAX_KERNEL_SIZE 16
+#define ADACOMB_MAX_FRAME_SIZE 80
+#define ADACOMB_MAX_OVERLAP_SIZE 40
+
+#define ADASHAPE_MAX_INPUT_DIM 512
+#define ADASHAPE_MAX_FRAME_SIZE 160
+
+/*#define DEBUG_NNDSP*/
+#ifdef DEBUG_NNDSP
+#include <stdio.h>
+#endif
+
+
+void print_float_vector(const char* name, const float *vec, int length);
+
+typedef struct {
+ float history[ADACONV_MAX_KERNEL_SIZE * ADACONV_MAX_INPUT_CHANNELS];
+ float last_kernel[ADACONV_MAX_KERNEL_SIZE * ADACONV_MAX_INPUT_CHANNELS * ADACONV_MAX_OUTPUT_CHANNELS];
+ float last_gain;
+} AdaConvState;
+
+
+typedef struct {
+ float history[ADACOMB_MAX_KERNEL_SIZE + ADACOMB_MAX_LAG];
+ float last_kernel[ADACOMB_MAX_KERNEL_SIZE];
+ float last_global_gain;
+ int last_pitch_lag;
+} AdaCombState;
+
+
+typedef struct {
+ float conv_alpha1f_state[ADASHAPE_MAX_INPUT_DIM];
+ float conv_alpha1t_state[ADASHAPE_MAX_INPUT_DIM];
+ float conv_alpha2_state[ADASHAPE_MAX_FRAME_SIZE];
+} AdaShapeState;
+
+void init_adaconv_state(AdaConvState *hAdaConv);
+
+void init_adacomb_state(AdaCombState *hAdaComb);
+
+void init_adashape_state(AdaShapeState *hAdaShape);
+
+void compute_overlap_window(float *window, int overlap_size);
+
+void adaconv_process_frame(
+ AdaConvState* hAdaConv,
+ float *x_out,
+ const float *x_in,
+ const float *features,
+ const LinearLayer *kernel_layer,
+ const LinearLayer *gain_layer,
+ int feature_dim, /* not strictly necessary */
+ int frame_size,
+ int overlap_size,
+ int in_channels,
+ int out_channels,
+ int kernel_size,
+ int left_padding,
+ float filter_gain_a,
+ float filter_gain_b,
+ float shape_gain,
+ float *window,
+ int arch
+);
+
+void adacomb_process_frame(
+ AdaCombState* hAdaComb,
+ float *x_out,
+ const float *x_in,
+ const float *features,
+ const LinearLayer *kernel_layer,
+ const LinearLayer *gain_layer,
+ const LinearLayer *global_gain_layer,
+ int pitch_lag,
+ int feature_dim,
+ int frame_size,
+ int overlap_size,
+ int kernel_size,
+ int left_padding,
+ float filter_gain_a,
+ float filter_gain_b,
+ float log_gain_limit,
+ float *window,
+ int arch
+);
+
+void adashape_process_frame(
+ AdaShapeState *hAdaShape,
+ float *x_out,
+ const float *x_in,
+ const float *features,
+ const LinearLayer *alpha1f,
+ const LinearLayer *alpha1t,
+ const LinearLayer *alpha2,
+ int feature_dim,
+ int frame_size,
+ int avg_pool_k,
+ int arch
+);
+
+#endif
diff --git a/dnn/nnet.c b/dnn/nnet.c
new file mode 100644
index 00000000..5e87f207
--- /dev/null
+++ b/dnn/nnet.c
@@ -0,0 +1,149 @@
+/* Copyright (c) 2018 Mozilla
+ 2008-2011 Octasic Inc.
+ 2012-2017 Jean-Marc Valin */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <math.h>
+#include "opus_types.h"
+#include "arch.h"
+#include "nnet.h"
+#include "dred_rdovae_constants.h"
+#include "plc_data.h"
+#include "fargan.h"
+#include "os_support.h"
+#include "vec.h"
+
+#ifdef ENABLE_OSCE
+#include "osce.h"
+#endif
+
+#ifdef NO_OPTIMIZATIONS
+#if defined(_MSC_VER)
+#pragma message ("Compiling without any vectorization. This code will be very slow")
+#else
+#warning Compiling without any vectorization. This code will be very slow
+#endif
+#endif
+
+
+#define SOFTMAX_HACK
+
+
+void compute_generic_dense(const LinearLayer *layer, float *output, const float *input, int activation, int arch)
+{
+ compute_linear(layer, output, input, arch);
+ compute_activation(output, output, layer->nb_outputs, activation, arch);
+}
+
+#ifdef ENABLE_OSCE
+#define MAX_RNN_NEURONS_ALL IMAX(IMAX(IMAX(FARGAN_MAX_RNN_NEURONS, PLC_MAX_RNN_UNITS), DRED_MAX_RNN_NEURONS), OSCE_MAX_RNN_NEURONS)
+#else
+#define MAX_RNN_NEURONS_ALL IMAX(IMAX(FARGAN_MAX_RNN_NEURONS, PLC_MAX_RNN_UNITS), DRED_MAX_RNN_NEURONS)
+#endif
+
+void compute_generic_gru(const LinearLayer *input_weights, const LinearLayer *recurrent_weights, float *state, const float *in, int arch)
+{
+ int i;
+ int N;
+ float zrh[3*MAX_RNN_NEURONS_ALL];
+ float recur[3*MAX_RNN_NEURONS_ALL];
+ float *z;
+ float *r;
+ float *h;
+ celt_assert(3*recurrent_weights->nb_inputs == recurrent_weights->nb_outputs);
+ celt_assert(input_weights->nb_outputs == recurrent_weights->nb_outputs);
+ N = recurrent_weights->nb_inputs;
+ z = zrh;
+ r = &zrh[N];
+ h = &zrh[2*N];
+ celt_assert(recurrent_weights->nb_outputs <= 3*MAX_RNN_NEURONS_ALL);
+ celt_assert(in != state);
+ compute_linear(input_weights, zrh, in, arch);
+ compute_linear(recurrent_weights, recur, state, arch);
+ for (i=0;i<2*N;i++)
+ zrh[i] += recur[i];
+ compute_activation(zrh, zrh, 2*N, ACTIVATION_SIGMOID, arch);
+ for (i=0;i<N;i++)
+ h[i] += recur[2*N+i]*r[i];
+ compute_activation(h, h, N, ACTIVATION_TANH, arch);
+ for (i=0;i<N;i++)
+ h[i] = z[i]*state[i] + (1-z[i])*h[i];
+ for (i=0;i<N;i++)
+ state[i] = h[i];
+}
+
+void compute_glu(const LinearLayer *layer, float *output, const float *input, int arch)
+{
+ int i;
+ float act2[MAX_INPUTS];
+ celt_assert(layer->nb_inputs == layer->nb_outputs);
+ compute_linear(layer, act2, input, arch);
+ compute_activation(act2, act2, layer->nb_outputs, ACTIVATION_SIGMOID, arch);
+ if (input == output) {
+ /* Give a vectorization hint to the compiler for the in-place case. */
+ for (i=0;i<layer->nb_outputs;i++) output[i] = output[i]*act2[i];
+ } else {
+ for (i=0;i<layer->nb_outputs;i++) output[i] = input[i]*act2[i];
+ }
+}
+
+#define MAX_CONV_INPUTS_ALL DRED_MAX_CONV_INPUTS
+
+void compute_generic_conv1d(const LinearLayer *layer, float *output, float *mem, const float *input, int input_size, int activation, int arch)
+{
+ float tmp[MAX_CONV_INPUTS_ALL];
+ celt_assert(input != output);
+ celt_assert(layer->nb_inputs <= MAX_CONV_INPUTS_ALL);
+ if (layer->nb_inputs!=input_size) OPUS_COPY(tmp, mem, layer->nb_inputs-input_size);
+ OPUS_COPY(&tmp[layer->nb_inputs-input_size], input, input_size);
+ compute_linear(layer, output, tmp, arch);
+ compute_activation(output, output, layer->nb_outputs, activation, arch);
+ if (layer->nb_inputs!=input_size) OPUS_COPY(mem, &tmp[input_size], layer->nb_inputs-input_size);
+}
+
+void compute_generic_conv1d_dilation(const LinearLayer *layer, float *output, float *mem, const float *input, int input_size, int dilation, int activation, int arch)
+{
+ float tmp[MAX_CONV_INPUTS_ALL];
+ int ksize = layer->nb_inputs/input_size;
+ int i;
+ celt_assert(input != output);
+ celt_assert(layer->nb_inputs <= MAX_CONV_INPUTS_ALL);
+ if (dilation==1) OPUS_COPY(tmp, mem, layer->nb_inputs-input_size);
+ else for (i=0;i<ksize-1;i++) OPUS_COPY(&tmp[i*input_size], &mem[i*input_size*dilation], input_size);
+ OPUS_COPY(&tmp[layer->nb_inputs-input_size], input, input_size);
+ compute_linear(layer, output, tmp, arch);
+ compute_activation(output, output, layer->nb_outputs, activation, arch);
+ if (dilation==1) OPUS_COPY(mem, &tmp[input_size], layer->nb_inputs-input_size);
+ else {
+ OPUS_COPY(mem, &mem[input_size], input_size*dilation*(ksize-1)-input_size);
+ OPUS_COPY(&mem[input_size*dilation*(ksize-1)-input_size], input, input_size);
+ }
+}
diff --git a/dnn/nnet.h b/dnn/nnet.h
new file mode 100644
index 00000000..7bc61337
--- /dev/null
+++ b/dnn/nnet.h
@@ -0,0 +1,163 @@
+/* Copyright (c) 2018 Mozilla
+ Copyright (c) 2017 Jean-Marc Valin */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifndef NNET_H_
+#define NNET_H_
+
+#include <stddef.h>
+#include "opus_types.h"
+
+#define ACTIVATION_LINEAR 0
+#define ACTIVATION_SIGMOID 1
+#define ACTIVATION_TANH 2
+#define ACTIVATION_RELU 3
+#define ACTIVATION_SOFTMAX 4
+#define ACTIVATION_SWISH 5
+
+#define WEIGHT_BLOB_VERSION 0
+#define WEIGHT_BLOCK_SIZE 64
+typedef struct {
+ const char *name;
+ int type;
+ int size;
+ const void *data;
+} WeightArray;
+
+#define WEIGHT_TYPE_float 0
+#define WEIGHT_TYPE_int 1
+#define WEIGHT_TYPE_qweight 2
+#define WEIGHT_TYPE_int8 3
+
+typedef struct {
+ char head[4];
+ int version;
+ int type;
+ int size;
+ int block_size;
+ char name[44];
+} WeightHead;
+
+/* Generic sparse affine transformation. */
+typedef struct {
+ const float *bias;
+ const float *subias;
+ const opus_int8 *weights;
+ const float *float_weights;
+ const int *weights_idx;
+ const float *diag;
+ const float *scale;
+ int nb_inputs;
+ int nb_outputs;
+} LinearLayer;
+
+/* Generic sparse affine transformation. */
+typedef struct {
+ const float *bias;
+ const float *float_weights;
+ int in_channels;
+ int out_channels;
+ int ktime;
+ int kheight;
+} Conv2dLayer;
+
+
+void compute_generic_dense(const LinearLayer *layer, float *output, const float *input, int activation, int arch);
+void compute_generic_gru(const LinearLayer *input_weights, const LinearLayer *recurrent_weights, float *state, const float *in, int arch);
+void compute_generic_conv1d(const LinearLayer *layer, float *output, float *mem, const float *input, int input_size, int activation, int arch);
+void compute_generic_conv1d_dilation(const LinearLayer *layer, float *output, float *mem, const float *input, int input_size, int dilation, int activation, int arch);
+void compute_glu(const LinearLayer *layer, float *output, const float *input, int arch);
+void compute_gated_activation(const LinearLayer *layer, float *output, const float *input, int activation, int arch);
+
+
+int parse_weights(WeightArray **list, const void *data, int len);
+
+
+extern const WeightArray lpcnet_arrays[];
+extern const WeightArray plcmodel_arrays[];
+extern const WeightArray rdovaeenc_arrays[];
+extern const WeightArray rdovaedec_arrays[];
+extern const WeightArray fwgan_arrays[];
+extern const WeightArray fargan_arrays[];
+extern const WeightArray pitchdnn_arrays[];
+extern const WeightArray lossgen_arrays[];
+
+int linear_init(LinearLayer *layer, const WeightArray *arrays,
+ const char *bias,
+ const char *subias,
+ const char *weights,
+ const char *float_weights,
+ const char *weights_idx,
+ const char *diag,
+ const char *scale,
+ int nb_inputs,
+ int nb_outputs);
+
+int conv2d_init(Conv2dLayer *layer, const WeightArray *arrays,
+ const char *bias,
+ const char *float_weights,
+ int in_channels,
+ int out_channels,
+ int ktime,
+ int kheight);
+
+
+void compute_linear_c(const LinearLayer *linear, float *out, const float *in);
+void compute_activation_c(float *output, const float *input, int N, int activation);
+void compute_conv2d_c(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation);
+
+
+#if defined(OPUS_ARM_MAY_HAVE_DOTPROD) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
+#include "arm/dnn_arm.h"
+#endif
+
+#if defined(OPUS_X86_MAY_HAVE_SSE2)
+#include "x86/dnn_x86.h"
+#endif
+
+#ifndef OVERRIDE_COMPUTE_LINEAR
+#define compute_linear(linear, out, in, arch) ((void)(arch),compute_linear_c(linear, out, in))
+#endif
+
+#ifndef OVERRIDE_COMPUTE_ACTIVATION
+#define compute_activation(output, input, N, activation, arch) ((void)(arch),compute_activation_c(output, input, N, activation))
+#endif
+
+#ifndef OVERRIDE_COMPUTE_CONV2D
+#define compute_conv2d(conv, out, mem, in, height, hstride, activation, arch) ((void)(arch),compute_conv2d_c(conv, out, mem, in, height, hstride, activation))
+#endif
+
+#if defined(__x86_64__) && !defined(OPUS_X86_MAY_HAVE_SSE4_1) && !defined(OPUS_X86_MAY_HAVE_AVX2)
+#if defined(_MSC_VER)
+#pragma message ("Only SSE and SSE2 are available. On newer machines, enable SSSE3/AVX/AVX2 to get better performance")
+#else
+#warning "Only SSE and SSE2 are available. On newer machines, enable SSSE3/AVX/AVX2 using -march= to get better performance"
+#endif
+#endif
+
+
+
+#endif /* NNET_H_ */
diff --git a/dnn/nnet_arch.h b/dnn/nnet_arch.h
new file mode 100644
index 00000000..694a3608
--- /dev/null
+++ b/dnn/nnet_arch.h
@@ -0,0 +1,247 @@
+/* Copyright (c) 2018-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifndef NNET_ARCH_H
+#define NNET_ARCH_H
+
+#include "nnet.h"
+#include "arch.h"
+#include "os_support.h"
+#include "vec.h"
+
+#define CAT_SUFFIX2(a,b) a ## b
+#define CAT_SUFFIX(a,b) CAT_SUFFIX2(a, b)
+
+#define RTCD_SUF(name) CAT_SUFFIX(name, RTCD_ARCH)
+
+/* Force vectorization on for DNN code because some of the loops rely on
+ compiler vectorization rather than explicitly using intrinsics. */
+#if OPUS_GNUC_PREREQ(5,1)
+#define GCC_POP_OPTIONS
+#pragma GCC push_options
+#pragma GCC optimize("tree-vectorize")
+#endif
+
+
+#define MAX_ACTIVATIONS (4096)
+
+static OPUS_INLINE void vec_swish(float *y, const float *x, int N)
+{
+ int i;
+ float tmp[MAX_ACTIVATIONS];
+ celt_assert(N <= MAX_ACTIVATIONS);
+ vec_sigmoid(tmp, x, N);
+ for (i=0;i<N;i++)
+ y[i] = x[i]*tmp[i];
+}
+
+static OPUS_INLINE float relu(float x)
+{
+ return x < 0 ? 0 : x;
+}
+
+/*#define HIGH_ACCURACY */
+
+void RTCD_SUF(compute_activation_)(float *output, const float *input, int N, int activation)
+{
+ int i;
+ if (activation == ACTIVATION_SIGMOID) {
+#ifdef HIGH_ACCURACY
+ for (int n=0; n<N; n++)
+ {
+ output[n] = 1.f / (1 + exp(-input[n]));
+ }
+#else
+ vec_sigmoid(output, input, N);
+#endif
+ } else if (activation == ACTIVATION_TANH) {
+#ifdef HIGH_ACCURACY
+ for (int n=0; n<N; n++)
+ {
+ output[n] = tanh(input[n]);
+ }
+#else
+ vec_tanh(output, input, N);
+#endif
+ } else if (activation == ACTIVATION_SWISH) {
+ vec_swish(output, input, N);
+ } else if (activation == ACTIVATION_RELU) {
+ for (i=0;i<N;i++)
+ output[i] = relu(input[i]);
+ } else if (activation == ACTIVATION_SOFTMAX) {
+#ifdef SOFTMAX_HACK
+ OPUS_COPY(output, input, N);
+ /*for (i=0;i<N;i++)
+ output[i] = input[i];*/
+#else
+ float sum = 0;
+ softmax(output, input, N);
+ for (i=0;i<N;i++) {
+ sum += output[i];
+ }
+ sum = 1.f/(sum+1e-30);
+ for (i=0;i<N;i++)
+ output[i] = sum*output[i];
+#endif
+ } else {
+ celt_assert(activation == ACTIVATION_LINEAR);
+ if (input != output) {
+ for (i=0;i<N;i++)
+ output[i] = input[i];
+ }
+ }
+}
+
+
+void RTCD_SUF(compute_linear_) (const LinearLayer *linear, float *out, const float *in)
+{
+ int i, M, N;
+ const float *bias;
+ celt_assert(in != out);
+ bias = linear->bias;
+ M = linear->nb_inputs;
+ N = linear->nb_outputs;
+ if (linear->float_weights != NULL) {
+ if (linear->weights_idx != NULL) sparse_sgemv8x4(out, linear->float_weights, linear->weights_idx, N, in);
+ else sgemv(out, linear->float_weights, N, M, N, in);
+ } else if (linear->weights != NULL) {
+ if (linear->weights_idx != NULL) sparse_cgemv8x4(out, linear->weights, linear->weights_idx, linear->scale, N, M, in);
+ else cgemv8x4(out, linear->weights, linear->scale, N, M, in);
+ /* Only use SU biases on for integer matrices on SU archs. */
+#ifdef USE_SU_BIAS
+ bias = linear->subias;
+#endif
+ }
+ else OPUS_CLEAR(out, N);
+ if (bias != NULL) {
+ for (i=0;i<N;i++) out[i] += bias[i];
+ }
+ if (linear->diag) {
+ /* Diag is only used for GRU recurrent weights. */
+ celt_assert(3*M == N);
+ for (i=0;i<M;i++) {
+ out[i] += linear->diag[i]*in[i];
+ out[i+M] += linear->diag[i+M]*in[i];
+ out[i+2*M] += linear->diag[i+2*M]*in[i];
+ }
+ }
+}
+
+/* Computes non-padded convolution for input [ ksize1 x in_channels x (len2+ksize2) ],
+ kernel [ out_channels x in_channels x ksize1 x ksize2 ],
+ storing the output as [ out_channels x len2 ].
+ We assume that the output dimension along the ksize1 axis is 1,
+ i.e. processing one frame at a time. */
+static void conv2d_float(float *out, const float *weights, int in_channels, int out_channels, int ktime, int kheight, const float *in, int height, int hstride)
+{
+ int i;
+ int in_stride;
+ in_stride = height+kheight-1;
+ for (i=0;i<out_channels;i++) {
+ int m;
+ OPUS_CLEAR(&out[i*hstride], height);
+ for (m=0;m<in_channels;m++) {
+ int t;
+ for (t=0;t<ktime;t++) {
+ int h;
+ for (h=0;h<kheight;h++) {
+ int j;
+ for (j=0;j<height;j++) {
+ out[i*hstride + j] += weights[i*in_channels*ktime*kheight + m*ktime*kheight + t*kheight + h] *
+ in[t*in_channels*in_stride + m*in_stride + j + h];
+ }
+ }
+ }
+ }
+ }
+}
+
+/* There's no intrinsics in this function (or the one above) because the gcc (and hopefully other compiler) auto-vectorizer is smart enough to
+ produce the right code by itself based on the compile flags. */
+static void conv2d_3x3_float(float *out, const float *weights, int in_channels, int out_channels, const float *in, int height, int hstride)
+{
+ int i;
+ int in_stride;
+ int kheight, ktime;
+ kheight = ktime = 3;
+ in_stride = height+kheight-1;
+ for (i=0;i<out_channels;i++) {
+ int m;
+ OPUS_CLEAR(&out[i*hstride], height);
+ for (m=0;m<in_channels;m++) {
+ int j;
+ for (j=0;j<height;j++) {
+ /* Unrolled version of previous function -- compiler will figure out the indexing simplifications. */
+ out[i*hstride + j] += weights[i*in_channels*ktime*kheight + m*ktime*kheight + 0*kheight + 0]*in[0*in_channels*in_stride + m*in_stride + j + 0]
+ + weights[i*in_channels*ktime*kheight + m*ktime*kheight + 0*kheight + 1]*in[0*in_channels*in_stride + m*in_stride + j + 1]
+ + weights[i*in_channels*ktime*kheight + m*ktime*kheight + 0*kheight + 2]*in[0*in_channels*in_stride + m*in_stride + j + 2]
+ + weights[i*in_channels*ktime*kheight + m*ktime*kheight + 1*kheight + 0]*in[1*in_channels*in_stride + m*in_stride + j + 0]
+ + weights[i*in_channels*ktime*kheight + m*ktime*kheight + 1*kheight + 1]*in[1*in_channels*in_stride + m*in_stride + j + 1]
+ + weights[i*in_channels*ktime*kheight + m*ktime*kheight + 1*kheight + 2]*in[1*in_channels*in_stride + m*in_stride + j + 2]
+ + weights[i*in_channels*ktime*kheight + m*ktime*kheight + 2*kheight + 0]*in[2*in_channels*in_stride + m*in_stride + j + 0]
+ + weights[i*in_channels*ktime*kheight + m*ktime*kheight + 2*kheight + 1]*in[2*in_channels*in_stride + m*in_stride + j + 1]
+ + weights[i*in_channels*ktime*kheight + m*ktime*kheight + 2*kheight + 2]*in[2*in_channels*in_stride + m*in_stride + j + 2];
+ }
+ }
+ }
+}
+
+#define MAX_CONV2D_INPUTS 8192
+
+void RTCD_SUF(compute_conv2d_)(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation)
+{
+ int i;
+ const float *bias;
+ float in_buf[MAX_CONV2D_INPUTS];
+ int time_stride;
+ celt_assert(in != out);
+ time_stride = conv->in_channels*(height+conv->kheight-1);
+ celt_assert(conv->ktime*time_stride <= MAX_CONV2D_INPUTS);
+ OPUS_COPY(in_buf, mem, (conv->ktime-1)*time_stride);
+ OPUS_COPY(&in_buf[(conv->ktime-1)*time_stride], in, time_stride);
+ OPUS_COPY(mem, &in_buf[time_stride], (conv->ktime-1)*time_stride);
+ bias = conv->bias;
+ if (conv->kheight == 3 && conv->ktime == 3)
+ conv2d_3x3_float(out, conv->float_weights, conv->in_channels, conv->out_channels, in_buf, height, hstride);
+ else
+ conv2d_float(out, conv->float_weights, conv->in_channels, conv->out_channels, conv->ktime, conv->kheight, in_buf, height, hstride);
+ if (bias != NULL) {
+ for (i=0;i<conv->out_channels;i++) {
+ int j;
+ for (j=0;j<height;j++) out[i*hstride+j] += bias[i];
+ }
+ }
+ for (i=0;i<conv->out_channels;i++) {
+ RTCD_SUF(compute_activation_)(&out[i*hstride], &out[i*hstride], height, activation);
+ }
+}
+
+#ifdef GCC_POP_OPTIONS
+#pragma GCC pop_options
+#endif
+
+#endif
diff --git a/dnn/nnet_default.c b/dnn/nnet_default.c
new file mode 100644
index 00000000..4316f0fb
--- /dev/null
+++ b/dnn/nnet_default.c
@@ -0,0 +1,35 @@
+/* Copyright (c) 2018-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#define RTCD_ARCH c
+
+#include "nnet_arch.h"
diff --git a/dnn/osce.c b/dnn/osce.c
new file mode 100644
index 00000000..c412d5a1
--- /dev/null
+++ b/dnn/osce.c
@@ -0,0 +1,1419 @@
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#include <math.h>
+#include "osce.h"
+#include "osce_features.h"
+#include "os_support.h"
+#include "nndsp.h"
+#include "float_cast.h"
+#include "arch.h"
+
+#ifdef OSCE_DEBUG
+#include <stdio.h>
+/*#define WRITE_FEATURES*/
+/*#define DEBUG_LACE*/
+/*#define DEBUG_NOLACE*/
+#define FINIT(fid, name, mode) do{if (fid == NULL) {fid = fopen(name, mode);}} while(0)
+#endif
+
+#ifdef ENABLE_OSCE_TRAINING_DATA
+#include <stdio.h>
+#endif
+
+#define CLIP(a, min, max) (((a) < (min) ? (min) : (a)) > (max) ? (max) : (a))
+
+extern const WeightArray lacelayers_arrays[];
+extern const WeightArray nolacelayers_arrays[];
+
+/* LACE */
+
+#ifndef DISABLE_LACE
+
+static void compute_lace_numbits_embedding(float *emb, float numbits, int dim, float min_val, float max_val, int logscale)
+{
+ float x;
+ (void) dim;
+
+ numbits = logscale ? log(numbits) : numbits;
+ x = CLIP(numbits, min_val, max_val) - (max_val + min_val) / 2;
+
+ emb[0] = sin(x * LACE_NUMBITS_SCALE_0 - 0.5f);
+ emb[1] = sin(x * LACE_NUMBITS_SCALE_1 - 0.5f);
+ emb[2] = sin(x * LACE_NUMBITS_SCALE_2 - 0.5f);
+ emb[3] = sin(x * LACE_NUMBITS_SCALE_3 - 0.5f);
+ emb[4] = sin(x * LACE_NUMBITS_SCALE_4 - 0.5f);
+ emb[5] = sin(x * LACE_NUMBITS_SCALE_5 - 0.5f);
+ emb[6] = sin(x * LACE_NUMBITS_SCALE_6 - 0.5f);
+ emb[7] = sin(x * LACE_NUMBITS_SCALE_7 - 0.5f);
+}
+
+
+static int init_lace(LACE *hLACE, const WeightArray *weights)
+{
+ int ret = 0;
+ OPUS_CLEAR(hLACE, 1);
+ celt_assert(weights != NULL);
+
+ ret = init_lacelayers(&hLACE->layers, weights);
+
+ compute_overlap_window(hLACE->window, LACE_OVERLAP_SIZE);
+
+ return ret;
+}
+
+static void reset_lace_state(LACEState *state)
+{
+ OPUS_CLEAR(state, 1);
+
+ init_adacomb_state(&state->cf1_state);
+ init_adacomb_state(&state->cf2_state);
+ init_adaconv_state(&state->af1_state);
+}
+
+static void lace_feature_net(
+ LACE *hLACE,
+ LACEState *state,
+ float *output,
+ const float *features,
+ const float *numbits,
+ const int *periods,
+ int arch
+)
+{
+ float input_buffer[4 * IMAX(LACE_COND_DIM, LACE_HIDDEN_FEATURE_DIM)];
+ float output_buffer[4 * IMAX(LACE_COND_DIM, LACE_HIDDEN_FEATURE_DIM)];
+ float numbits_embedded[2 * LACE_NUMBITS_EMBEDDING_DIM];
+ int i_subframe;
+
+ compute_lace_numbits_embedding(numbits_embedded, numbits[0], LACE_NUMBITS_EMBEDDING_DIM,
+ log(LACE_NUMBITS_RANGE_LOW), log(LACE_NUMBITS_RANGE_HIGH), 1);
+ compute_lace_numbits_embedding(numbits_embedded + LACE_NUMBITS_EMBEDDING_DIM, numbits[1], LACE_NUMBITS_EMBEDDING_DIM,
+ log(LACE_NUMBITS_RANGE_LOW), log(LACE_NUMBITS_RANGE_HIGH), 1);
+
+ /* scaling and dimensionality reduction */
+ for (i_subframe = 0; i_subframe < 4; i_subframe ++)
+ {
+ OPUS_COPY(input_buffer, features + i_subframe * LACE_NUM_FEATURES, LACE_NUM_FEATURES);
+ OPUS_COPY(input_buffer + LACE_NUM_FEATURES, hLACE->layers.lace_pitch_embedding.float_weights + periods[i_subframe] * LACE_PITCH_EMBEDDING_DIM, LACE_PITCH_EMBEDDING_DIM);
+ OPUS_COPY(input_buffer + LACE_NUM_FEATURES + LACE_PITCH_EMBEDDING_DIM, numbits_embedded, 2 * LACE_NUMBITS_EMBEDDING_DIM);
+
+ compute_generic_conv1d(
+ &hLACE->layers.lace_fnet_conv1,
+ output_buffer + i_subframe * LACE_HIDDEN_FEATURE_DIM,
+ NULL,
+ input_buffer,
+ LACE_NUM_FEATURES + LACE_PITCH_EMBEDDING_DIM + 2 * LACE_NUMBITS_EMBEDDING_DIM,
+ ACTIVATION_TANH,
+ arch);
+ }
+
+ /* subframe accumulation */
+ OPUS_COPY(input_buffer, output_buffer, 4 * LACE_HIDDEN_FEATURE_DIM);
+ compute_generic_conv1d(
+ &hLACE->layers.lace_fnet_conv2,
+ output_buffer,
+ state->feature_net_conv2_state,
+ input_buffer,
+ 4 * LACE_HIDDEN_FEATURE_DIM,
+ ACTIVATION_TANH,
+ arch
+ );
+
+ /* tconv upsampling */
+ OPUS_COPY(input_buffer, output_buffer, 4 * LACE_COND_DIM);
+ compute_generic_dense(
+ &hLACE->layers.lace_fnet_tconv,
+ output_buffer,
+ input_buffer,
+ ACTIVATION_TANH,
+ arch
+ );
+
+ /* GRU */
+ OPUS_COPY(input_buffer, output_buffer, 4 * LACE_COND_DIM);
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ compute_generic_gru(
+ &hLACE->layers.lace_fnet_gru_input,
+ &hLACE->layers.lace_fnet_gru_recurrent,
+ state->feature_net_gru_state,
+ input_buffer + i_subframe * LACE_COND_DIM,
+ arch
+ );
+ OPUS_COPY(output + i_subframe * LACE_COND_DIM, state->feature_net_gru_state, LACE_COND_DIM);
+ }
+}
+
+
+static void lace_process_20ms_frame(
+ LACE* hLACE,
+ LACEState *state,
+ float *x_out,
+ const float *x_in,
+ const float *features,
+ const float *numbits,
+ const int *periods,
+ int arch
+)
+{
+ float feature_buffer[4 * LACE_COND_DIM];
+ float output_buffer[4 * LACE_FRAME_SIZE];
+ int i_subframe, i_sample;
+
+#ifdef DEBUG_LACE
+ static FILE *f_features=NULL, *f_encfeatures=NULL, *f_xin=NULL, *f_xpreemph=NULL, *f_postcf1=NULL;
+ static FILE *f_postcf2=NULL, *f_postaf1=NULL, *f_xdeemph, *f_numbits, *f_periods;
+
+
+ FINIT(f_features, "debug/c_features.f32", "wb");
+ FINIT(f_encfeatures, "debug/c_encoded_features.f32", "wb");
+ FINIT(f_xin, "debug/c_x_in.f32", "wb");
+ FINIT(f_xpreemph, "debug/c_xpreemph.f32", "wb");
+ FINIT(f_xdeemph, "debug/c_xdeemph.f32", "wb");
+ FINIT(f_postcf1, "debug/c_post_cf1.f32", "wb");
+ FINIT(f_postcf2, "debug/c_post_cf2.f32", "wb");
+ FINIT(f_postaf1, "debug/c_post_af1.f32", "wb");
+ FINIT(f_numbits, "debug/c_numbits.f32", "wb");
+ FINIT(f_periods, "debug/c_periods.s32", "wb");
+
+ fwrite(x_in, sizeof(*x_in), 4 * LACE_FRAME_SIZE, f_xin);
+ fwrite(numbits, sizeof(*numbits), 2, f_numbits);
+ fwrite(periods, sizeof(*periods), 4, f_periods);
+#endif
+
+ /* pre-emphasis */
+ for (i_sample = 0; i_sample < 4 * LACE_FRAME_SIZE; i_sample ++)
+ {
+ output_buffer[i_sample] = x_in[i_sample] - LACE_PREEMPH * state->preemph_mem;
+ state->preemph_mem = x_in[i_sample];
+ }
+
+ /* run feature encoder */
+ lace_feature_net(hLACE, state, feature_buffer, features, numbits, periods, arch);
+#ifdef DEBUG_LACE
+ fwrite(features, sizeof(*features), 4 * LACE_NUM_FEATURES, f_features);
+ fwrite(feature_buffer, sizeof(*feature_buffer), 4 * LACE_COND_DIM, f_encfeatures);
+ fwrite(output_buffer, sizeof(float), 4 * LACE_FRAME_SIZE, f_xpreemph);
+#endif
+
+ /* 1st comb filtering stage */
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ adacomb_process_frame(
+ &state->cf1_state,
+ output_buffer + i_subframe * LACE_FRAME_SIZE,
+ output_buffer + i_subframe * LACE_FRAME_SIZE,
+ feature_buffer + i_subframe * LACE_COND_DIM,
+ &hLACE->layers.lace_cf1_kernel,
+ &hLACE->layers.lace_cf1_gain,
+ &hLACE->layers.lace_cf1_global_gain,
+ periods[i_subframe],
+ LACE_COND_DIM,
+ LACE_FRAME_SIZE,
+ LACE_OVERLAP_SIZE,
+ LACE_CF1_KERNEL_SIZE,
+ LACE_CF1_LEFT_PADDING,
+ LACE_CF1_FILTER_GAIN_A,
+ LACE_CF1_FILTER_GAIN_B,
+ LACE_CF1_LOG_GAIN_LIMIT,
+ hLACE->window,
+ arch);
+ }
+
+#ifdef DEBUG_LACE
+ fwrite(output_buffer, sizeof(float), 4 * LACE_FRAME_SIZE, f_postcf1);
+#endif
+
+ /* 2nd comb filtering stage */
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ adacomb_process_frame(
+ &state->cf2_state,
+ output_buffer + i_subframe * LACE_FRAME_SIZE,
+ output_buffer + i_subframe * LACE_FRAME_SIZE,
+ feature_buffer + i_subframe * LACE_COND_DIM,
+ &hLACE->layers.lace_cf2_kernel,
+ &hLACE->layers.lace_cf2_gain,
+ &hLACE->layers.lace_cf2_global_gain,
+ periods[i_subframe],
+ LACE_COND_DIM,
+ LACE_FRAME_SIZE,
+ LACE_OVERLAP_SIZE,
+ LACE_CF2_KERNEL_SIZE,
+ LACE_CF2_LEFT_PADDING,
+ LACE_CF2_FILTER_GAIN_A,
+ LACE_CF2_FILTER_GAIN_B,
+ LACE_CF2_LOG_GAIN_LIMIT,
+ hLACE->window,
+ arch);
+ }
+#ifdef DEBUG_LACE
+ fwrite(output_buffer, sizeof(float), 4 * LACE_FRAME_SIZE, f_postcf2);
+#endif
+
+ /* final adaptive filtering stage */
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ adaconv_process_frame(
+ &state->af1_state,
+ output_buffer + i_subframe * LACE_FRAME_SIZE,
+ output_buffer + i_subframe * LACE_FRAME_SIZE,
+ feature_buffer + i_subframe * LACE_COND_DIM,
+ &hLACE->layers.lace_af1_kernel,
+ &hLACE->layers.lace_af1_gain,
+ LACE_COND_DIM,
+ LACE_FRAME_SIZE,
+ LACE_OVERLAP_SIZE,
+ LACE_AF1_IN_CHANNELS,
+ LACE_AF1_OUT_CHANNELS,
+ LACE_AF1_KERNEL_SIZE,
+ LACE_AF1_LEFT_PADDING,
+ LACE_AF1_FILTER_GAIN_A,
+ LACE_AF1_FILTER_GAIN_B,
+ LACE_AF1_SHAPE_GAIN,
+ hLACE->window,
+ arch);
+ }
+#ifdef DEBUG_LACE
+ fwrite(output_buffer, sizeof(float), 4 * LACE_FRAME_SIZE, f_postaf1);
+#endif
+
+ /* de-emphasis */
+ for (i_sample = 0; i_sample < 4 * LACE_FRAME_SIZE; i_sample ++)
+ {
+ x_out[i_sample] = output_buffer[i_sample] + LACE_PREEMPH * state->deemph_mem;
+ state->deemph_mem = x_out[i_sample];
+ }
+#ifdef DEBUG_LACE
+ fwrite(x_out, sizeof(float), 4 * LACE_FRAME_SIZE, f_xdeemph);
+#endif
+}
+
+#endif /* #ifndef DISABLE_LACE */
+
+
+/* NoLACE */
+#ifndef DISABLE_NOLACE
+
+static void compute_nolace_numbits_embedding(float *emb, float numbits, int dim, float min_val, float max_val, int logscale)
+{
+ float x;
+ (void) dim;
+
+ numbits = logscale ? log(numbits) : numbits;
+ x = CLIP(numbits, min_val, max_val) - (max_val + min_val) / 2;
+
+ emb[0] = sin(x * NOLACE_NUMBITS_SCALE_0 - 0.5f);
+ emb[1] = sin(x * NOLACE_NUMBITS_SCALE_1 - 0.5f);
+ emb[2] = sin(x * NOLACE_NUMBITS_SCALE_2 - 0.5f);
+ emb[3] = sin(x * NOLACE_NUMBITS_SCALE_3 - 0.5f);
+ emb[4] = sin(x * NOLACE_NUMBITS_SCALE_4 - 0.5f);
+ emb[5] = sin(x * NOLACE_NUMBITS_SCALE_5 - 0.5f);
+ emb[6] = sin(x * NOLACE_NUMBITS_SCALE_6 - 0.5f);
+ emb[7] = sin(x * NOLACE_NUMBITS_SCALE_7 - 0.5f);
+}
+
+static int init_nolace(NoLACE *hNoLACE, const WeightArray *weights)
+{
+ int ret = 0;
+ OPUS_CLEAR(hNoLACE, 1);
+ celt_assert(weights != NULL);
+
+ ret = init_nolacelayers(&hNoLACE->layers, weights);
+
+ compute_overlap_window(hNoLACE->window, NOLACE_OVERLAP_SIZE);
+
+ return ret;
+}
+
+static void reset_nolace_state(NoLACEState *state)
+{
+ OPUS_CLEAR(state, 1);
+
+ init_adacomb_state(&state->cf1_state);
+ init_adacomb_state(&state->cf2_state);
+ init_adaconv_state(&state->af1_state);
+ init_adaconv_state(&state->af2_state);
+ init_adaconv_state(&state->af3_state);
+ init_adaconv_state(&state->af4_state);
+ init_adashape_state(&state->tdshape1_state);
+ init_adashape_state(&state->tdshape2_state);
+ init_adashape_state(&state->tdshape3_state);
+}
+
+static void nolace_feature_net(
+ NoLACE *hNoLACE,
+ NoLACEState *state,
+ float *output,
+ const float *features,
+ const float *numbits,
+ const int *periods,
+ int arch
+)
+{
+ float input_buffer[4 * IMAX(NOLACE_COND_DIM, NOLACE_HIDDEN_FEATURE_DIM)];
+ float output_buffer[4 * IMAX(NOLACE_COND_DIM, NOLACE_HIDDEN_FEATURE_DIM)];
+ float numbits_embedded[2 * NOLACE_NUMBITS_EMBEDDING_DIM];
+ int i_subframe;
+
+ compute_nolace_numbits_embedding(numbits_embedded, numbits[0], NOLACE_NUMBITS_EMBEDDING_DIM,
+ log(NOLACE_NUMBITS_RANGE_LOW), log(NOLACE_NUMBITS_RANGE_HIGH), 1);
+ compute_nolace_numbits_embedding(numbits_embedded + NOLACE_NUMBITS_EMBEDDING_DIM, numbits[1], NOLACE_NUMBITS_EMBEDDING_DIM,
+ log(NOLACE_NUMBITS_RANGE_LOW), log(NOLACE_NUMBITS_RANGE_HIGH), 1);
+
+ /* scaling and dimensionality reduction */
+ for (i_subframe = 0; i_subframe < 4; i_subframe ++)
+ {
+ OPUS_COPY(input_buffer, features + i_subframe * NOLACE_NUM_FEATURES, NOLACE_NUM_FEATURES);
+ OPUS_COPY(input_buffer + NOLACE_NUM_FEATURES, hNoLACE->layers.nolace_pitch_embedding.float_weights + periods[i_subframe] * NOLACE_PITCH_EMBEDDING_DIM, NOLACE_PITCH_EMBEDDING_DIM);
+ OPUS_COPY(input_buffer + NOLACE_NUM_FEATURES + NOLACE_PITCH_EMBEDDING_DIM, numbits_embedded, 2 * NOLACE_NUMBITS_EMBEDDING_DIM);
+
+ compute_generic_conv1d(
+ &hNoLACE->layers.nolace_fnet_conv1,
+ output_buffer + i_subframe * NOLACE_HIDDEN_FEATURE_DIM,
+ NULL,
+ input_buffer,
+ NOLACE_NUM_FEATURES + NOLACE_PITCH_EMBEDDING_DIM + 2 * NOLACE_NUMBITS_EMBEDDING_DIM,
+ ACTIVATION_TANH,
+ arch);
+ }
+
+ /* subframe accumulation */
+ OPUS_COPY(input_buffer, output_buffer, 4 * NOLACE_HIDDEN_FEATURE_DIM);
+ compute_generic_conv1d(
+ &hNoLACE->layers.nolace_fnet_conv2,
+ output_buffer,
+ state->feature_net_conv2_state,
+ input_buffer,
+ 4 * NOLACE_HIDDEN_FEATURE_DIM,
+ ACTIVATION_TANH,
+ arch
+ );
+
+ /* tconv upsampling */
+ OPUS_COPY(input_buffer, output_buffer, 4 * NOLACE_COND_DIM);
+ compute_generic_dense(
+ &hNoLACE->layers.nolace_fnet_tconv,
+ output_buffer,
+ input_buffer,
+ ACTIVATION_TANH,
+ arch
+ );
+
+ /* GRU */
+ OPUS_COPY(input_buffer, output_buffer, 4 * NOLACE_COND_DIM);
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ compute_generic_gru(
+ &hNoLACE->layers.nolace_fnet_gru_input,
+ &hNoLACE->layers.nolace_fnet_gru_recurrent,
+ state->feature_net_gru_state,
+ input_buffer + i_subframe * NOLACE_COND_DIM,
+ arch
+ );
+ OPUS_COPY(output + i_subframe * NOLACE_COND_DIM, state->feature_net_gru_state, NOLACE_COND_DIM);
+ }
+}
+
+
+static void nolace_process_20ms_frame(
+ NoLACE* hNoLACE,
+ NoLACEState *state,
+ float *x_out,
+ const float *x_in,
+ const float *features,
+ const float *numbits,
+ const int *periods,
+ int arch
+)
+{
+ float feature_buffer[4 * NOLACE_COND_DIM];
+ float feature_transform_buffer[4 * NOLACE_COND_DIM];
+ float x_buffer1[8 * NOLACE_FRAME_SIZE];
+ float x_buffer2[8 * NOLACE_FRAME_SIZE];
+ int i_subframe, i_sample;
+ NOLACELayers *layers = &hNoLACE->layers;
+
+#ifdef DEBUG_NOLACE
+ static FILE *f_features=NULL, *f_encfeatures=NULL, *f_xin=NULL, *f_xpreemph=NULL, *f_postcf1=NULL;
+ static FILE *f_postcf2=NULL, *f_postaf1=NULL, *f_xdeemph, *f_numbits, *f_periods;
+ static FILE *f_ffpostcf1, *f_fpostcf2, *f_fpostaf1;
+
+
+ FINIT(f_features, "debug/c_features.f32", "wb");
+ FINIT(f_encfeatures, "debug/c_encoded_features.f32", "wb");
+ FINIT(f_xin, "debug/c_x_in.f32", "wb");
+ FINIT(f_xpreemph, "debug/c_xpreemph.f32", "wb");
+ FINIT(f_xdeemph, "debug/c_xdeemph.f32", "wb");
+ FINIT(f_postcf1, "debug/c_post_cf1.f32", "wb");
+ FINIT(f_postcf2, "debug/c_post_cf2.f32", "wb");
+ FINIT(f_postaf1, "debug/c_post_af1.f32", "wb");
+ FINIT(f_numbits, "debug/c_numbits.f32", "wb");
+ FINIT(f_periods, "debug/c_periods.s32", "wb");
+
+ fwrite(x_in, sizeof(*x_in), 4 * NOLACE_FRAME_SIZE, f_xin);
+ fwrite(numbits, sizeof(*numbits), 2, f_numbits);
+ fwrite(periods, sizeof(*periods), 4, f_periods);
+#endif
+
+ /* pre-emphasis */
+ for (i_sample = 0; i_sample < 4 * NOLACE_FRAME_SIZE; i_sample ++)
+ {
+ x_buffer1[i_sample] = x_in[i_sample] - NOLACE_PREEMPH * state->preemph_mem;
+ state->preemph_mem = x_in[i_sample];
+ }
+
+ /* run feature encoder */
+ nolace_feature_net(hNoLACE, state, feature_buffer, features, numbits, periods, arch);
+#ifdef DEBUG_NOLACE
+ fwrite(features, sizeof(*features), 4 * NOLACE_NUM_FEATURES, f_features);
+ fwrite(feature_buffer, sizeof(*feature_buffer), 4 * NOLACE_COND_DIM, f_encfeatures);
+ fwrite(output_buffer, sizeof(float), 4 * NOLACE_FRAME_SIZE, f_xpreemph);
+#endif
+
+ /* 1st comb filtering stage */
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ /* modifies signal in place */
+ adacomb_process_frame(
+ &state->cf1_state,
+ x_buffer1 + i_subframe * NOLACE_FRAME_SIZE,
+ x_buffer1 + i_subframe * NOLACE_FRAME_SIZE,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ &hNoLACE->layers.nolace_cf1_kernel,
+ &hNoLACE->layers.nolace_cf1_gain,
+ &hNoLACE->layers.nolace_cf1_global_gain,
+ periods[i_subframe],
+ NOLACE_COND_DIM,
+ NOLACE_FRAME_SIZE,
+ NOLACE_OVERLAP_SIZE,
+ NOLACE_CF1_KERNEL_SIZE,
+ NOLACE_CF1_LEFT_PADDING,
+ NOLACE_CF1_FILTER_GAIN_A,
+ NOLACE_CF1_FILTER_GAIN_B,
+ NOLACE_CF1_LOG_GAIN_LIMIT,
+ hNoLACE->window,
+ arch);
+
+ compute_generic_conv1d(
+ &layers->nolace_post_cf1,
+ feature_transform_buffer + i_subframe * NOLACE_COND_DIM,
+ state->post_cf1_state,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ NOLACE_COND_DIM,
+ ACTIVATION_TANH,
+ arch);
+ }
+
+ /* update feature buffer */
+ OPUS_COPY(feature_buffer, feature_transform_buffer, 4 * NOLACE_COND_DIM);
+
+#ifdef DEBUG_NOLACE
+ fwrite(x_buffer1, sizeof(float), 4 * NOLACE_FRAME_SIZE, f_postcf1);
+#endif
+
+ /* 2nd comb filtering stage */
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ /* modifies signal in place */
+ adacomb_process_frame(
+ &state->cf2_state,
+ x_buffer1 + i_subframe * NOLACE_FRAME_SIZE,
+ x_buffer1 + i_subframe * NOLACE_FRAME_SIZE,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ &hNoLACE->layers.nolace_cf2_kernel,
+ &hNoLACE->layers.nolace_cf2_gain,
+ &hNoLACE->layers.nolace_cf2_global_gain,
+ periods[i_subframe],
+ NOLACE_COND_DIM,
+ NOLACE_FRAME_SIZE,
+ NOLACE_OVERLAP_SIZE,
+ NOLACE_CF2_KERNEL_SIZE,
+ NOLACE_CF2_LEFT_PADDING,
+ NOLACE_CF2_FILTER_GAIN_A,
+ NOLACE_CF2_FILTER_GAIN_B,
+ NOLACE_CF2_LOG_GAIN_LIMIT,
+ hNoLACE->window,
+ arch);
+
+ compute_generic_conv1d(
+ &layers->nolace_post_cf2,
+ feature_transform_buffer + i_subframe * NOLACE_COND_DIM,
+ state->post_cf2_state,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ NOLACE_COND_DIM,
+ ACTIVATION_TANH,
+ arch);
+ }
+
+ /* update feature buffer */
+ OPUS_COPY(feature_buffer, feature_transform_buffer, 4 * NOLACE_COND_DIM);
+
+#ifdef DEBUG_NOLACE
+ fwrite(x_buffer1, sizeof(float), 4 * NOLACE_FRAME_SIZE, f_postcf2);
+#endif
+
+ /* final adaptive filtering stage */
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ adaconv_process_frame(
+ &state->af1_state,
+ x_buffer2 + i_subframe * NOLACE_FRAME_SIZE * NOLACE_AF1_OUT_CHANNELS,
+ x_buffer1 + i_subframe * NOLACE_FRAME_SIZE,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ &hNoLACE->layers.nolace_af1_kernel,
+ &hNoLACE->layers.nolace_af1_gain,
+ NOLACE_COND_DIM,
+ NOLACE_FRAME_SIZE,
+ NOLACE_OVERLAP_SIZE,
+ NOLACE_AF1_IN_CHANNELS,
+ NOLACE_AF1_OUT_CHANNELS,
+ NOLACE_AF1_KERNEL_SIZE,
+ NOLACE_AF1_LEFT_PADDING,
+ NOLACE_AF1_FILTER_GAIN_A,
+ NOLACE_AF1_FILTER_GAIN_B,
+ NOLACE_AF1_SHAPE_GAIN,
+ hNoLACE->window,
+ arch);
+
+ compute_generic_conv1d(
+ &layers->nolace_post_af1,
+ feature_transform_buffer + i_subframe * NOLACE_COND_DIM,
+ state->post_af1_state,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ NOLACE_COND_DIM,
+ ACTIVATION_TANH,
+ arch);
+ }
+
+ /* update feature buffer */
+ OPUS_COPY(feature_buffer, feature_transform_buffer, 4 * NOLACE_COND_DIM);
+
+#ifdef DEBUG_NOLACE
+ fwrite(x_buffer2, sizeof(float), 4 * NOLACE_FRAME_SIZE * NOLACE_AF1_OUT_CHANNELS, f_postaf1);
+#endif
+
+ /* first shape-mix round */
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ celt_assert(NOLACE_AF1_OUT_CHANNELS == 2);
+ /* modifies second channel in place */
+ adashape_process_frame(
+ &state->tdshape1_state,
+ x_buffer2 + i_subframe * NOLACE_AF1_OUT_CHANNELS * NOLACE_FRAME_SIZE + NOLACE_FRAME_SIZE,
+ x_buffer2 + i_subframe * NOLACE_AF1_OUT_CHANNELS * NOLACE_FRAME_SIZE + NOLACE_FRAME_SIZE,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ &layers->nolace_tdshape1_alpha1_f,
+ &layers->nolace_tdshape1_alpha1_t,
+ &layers->nolace_tdshape1_alpha2,
+ NOLACE_TDSHAPE1_FEATURE_DIM,
+ NOLACE_TDSHAPE1_FRAME_SIZE,
+ NOLACE_TDSHAPE1_AVG_POOL_K,
+ arch
+ );
+
+ adaconv_process_frame(
+ &state->af2_state,
+ x_buffer1 + i_subframe * NOLACE_FRAME_SIZE * NOLACE_AF2_OUT_CHANNELS,
+ x_buffer2 + i_subframe * NOLACE_FRAME_SIZE * NOLACE_AF2_IN_CHANNELS,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ &hNoLACE->layers.nolace_af2_kernel,
+ &hNoLACE->layers.nolace_af2_gain,
+ NOLACE_COND_DIM,
+ NOLACE_FRAME_SIZE,
+ NOLACE_OVERLAP_SIZE,
+ NOLACE_AF2_IN_CHANNELS,
+ NOLACE_AF2_OUT_CHANNELS,
+ NOLACE_AF2_KERNEL_SIZE,
+ NOLACE_AF2_LEFT_PADDING,
+ NOLACE_AF2_FILTER_GAIN_A,
+ NOLACE_AF2_FILTER_GAIN_B,
+ NOLACE_AF2_SHAPE_GAIN,
+ hNoLACE->window,
+ arch);
+
+ compute_generic_conv1d(
+ &layers->nolace_post_af2,
+ feature_transform_buffer + i_subframe * NOLACE_COND_DIM,
+ state->post_af2_state,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ NOLACE_COND_DIM,
+ ACTIVATION_TANH,
+ arch);
+ }
+
+ /* update feature buffer */
+ OPUS_COPY(feature_buffer, feature_transform_buffer, 4 * NOLACE_COND_DIM);
+
+#ifdef DEBUG_NOLACE
+ fwrite(x_buffer1, sizeof(float), 4 * NOLACE_FRAME_SIZE * NOLACE_AF2_OUT_CHANNELS, f_postaf2);
+#endif
+
+ /* second shape-mix round */
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ celt_assert(NOLACE_AF2_OUT_CHANNELS == 2);
+ /* modifies second channel in place */
+ adashape_process_frame(
+ &state->tdshape2_state,
+ x_buffer1 + i_subframe * NOLACE_AF2_OUT_CHANNELS * NOLACE_FRAME_SIZE + NOLACE_FRAME_SIZE,
+ x_buffer1 + i_subframe * NOLACE_AF2_OUT_CHANNELS * NOLACE_FRAME_SIZE + NOLACE_FRAME_SIZE,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ &layers->nolace_tdshape2_alpha1_f,
+ &layers->nolace_tdshape2_alpha1_t,
+ &layers->nolace_tdshape2_alpha2,
+ NOLACE_TDSHAPE2_FEATURE_DIM,
+ NOLACE_TDSHAPE2_FRAME_SIZE,
+ NOLACE_TDSHAPE2_AVG_POOL_K,
+ arch
+ );
+
+ adaconv_process_frame(
+ &state->af3_state,
+ x_buffer2 + i_subframe * NOLACE_FRAME_SIZE * NOLACE_AF3_OUT_CHANNELS,
+ x_buffer1 + i_subframe * NOLACE_FRAME_SIZE * NOLACE_AF3_IN_CHANNELS,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ &hNoLACE->layers.nolace_af3_kernel,
+ &hNoLACE->layers.nolace_af3_gain,
+ NOLACE_COND_DIM,
+ NOLACE_FRAME_SIZE,
+ NOLACE_OVERLAP_SIZE,
+ NOLACE_AF3_IN_CHANNELS,
+ NOLACE_AF3_OUT_CHANNELS,
+ NOLACE_AF3_KERNEL_SIZE,
+ NOLACE_AF3_LEFT_PADDING,
+ NOLACE_AF3_FILTER_GAIN_A,
+ NOLACE_AF3_FILTER_GAIN_B,
+ NOLACE_AF3_SHAPE_GAIN,
+ hNoLACE->window,
+ arch);
+
+ compute_generic_conv1d(
+ &layers->nolace_post_af3,
+ feature_transform_buffer + i_subframe * NOLACE_COND_DIM,
+ state->post_af3_state,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ NOLACE_COND_DIM,
+ ACTIVATION_TANH,
+ arch);
+ }
+
+ /* update feature buffer */
+ OPUS_COPY(feature_buffer, feature_transform_buffer, 4 * NOLACE_COND_DIM);
+
+ /* third shape-mix round */
+ for (i_subframe = 0; i_subframe < 4; i_subframe++)
+ {
+ celt_assert(NOLACE_AF3_OUT_CHANNELS == 2);
+ /* modifies second channel in place */
+ adashape_process_frame(
+ &state->tdshape3_state,
+ x_buffer2 + i_subframe * NOLACE_AF3_OUT_CHANNELS * NOLACE_FRAME_SIZE + NOLACE_FRAME_SIZE,
+ x_buffer2 + i_subframe * NOLACE_AF3_OUT_CHANNELS * NOLACE_FRAME_SIZE + NOLACE_FRAME_SIZE,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ &layers->nolace_tdshape3_alpha1_f,
+ &layers->nolace_tdshape3_alpha1_t,
+ &layers->nolace_tdshape3_alpha2,
+ NOLACE_TDSHAPE3_FEATURE_DIM,
+ NOLACE_TDSHAPE3_FRAME_SIZE,
+ NOLACE_TDSHAPE3_AVG_POOL_K,
+ arch
+ );
+
+ adaconv_process_frame(
+ &state->af4_state,
+ x_buffer1 + i_subframe * NOLACE_FRAME_SIZE * NOLACE_AF4_OUT_CHANNELS,
+ x_buffer2 + i_subframe * NOLACE_FRAME_SIZE * NOLACE_AF4_IN_CHANNELS,
+ feature_buffer + i_subframe * NOLACE_COND_DIM,
+ &hNoLACE->layers.nolace_af4_kernel,
+ &hNoLACE->layers.nolace_af4_gain,
+ NOLACE_COND_DIM,
+ NOLACE_FRAME_SIZE,
+ NOLACE_OVERLAP_SIZE,
+ NOLACE_AF4_IN_CHANNELS,
+ NOLACE_AF4_OUT_CHANNELS,
+ NOLACE_AF4_KERNEL_SIZE,
+ NOLACE_AF4_LEFT_PADDING,
+ NOLACE_AF4_FILTER_GAIN_A,
+ NOLACE_AF4_FILTER_GAIN_B,
+ NOLACE_AF4_SHAPE_GAIN,
+ hNoLACE->window,
+ arch);
+
+ }
+
+
+ /* de-emphasis */
+ for (i_sample = 0; i_sample < 4 * NOLACE_FRAME_SIZE; i_sample ++)
+ {
+ x_out[i_sample] = x_buffer1[i_sample] + NOLACE_PREEMPH * state->deemph_mem;
+ state->deemph_mem = x_out[i_sample];
+ }
+#ifdef DEBUG_NOLACE
+ fwrite(x_out, sizeof(float), 4 * NOLACE_FRAME_SIZE, f_xdeemph);
+#endif
+}
+
+#endif /* #ifndef DISABLE_NOLACE */
+
+/* API */
+
+void osce_reset(silk_OSCE_struct *hOSCE, int method)
+{
+ OSCEState *state = &hOSCE->state;
+
+ OPUS_CLEAR(&hOSCE->features, 1);
+
+ switch(method)
+ {
+ case OSCE_METHOD_NONE:
+ break;
+#ifndef DISABLE_LACE
+ case OSCE_METHOD_LACE:
+ reset_lace_state(&state->lace);
+ break;
+#endif
+#ifndef DISABLE_NOLACE
+ case OSCE_METHOD_NOLACE:
+ reset_nolace_state(&state->nolace);
+ break;
+#endif
+ default:
+ celt_assert(0 && "method not defined"); /* Question: return error code? */
+ }
+ hOSCE->method = method;
+ hOSCE->features.reset = 2;
+}
+
+
+#if 0
+#include <stdio.h>
+static void print_float_array(FILE *fid, const char *name, const float *array, int n)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ {
+ fprintf(fid, "%s[%d]: %f\n", name, i, array[i]);
+ }
+}
+
+static void print_int_array(FILE *fid, const char *name, const int *array, int n)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ {
+ fprintf(fid, "%s[%d]: %d\n", name, i, array[i]);
+ }
+}
+
+static void print_int8_array(FILE *fid, const char *name, const opus_int8 *array, int n)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ {
+ fprintf(fid, "%s[%d]: %d\n", name, i, array[i]);
+ }
+}
+
+static void print_linear_layer(FILE *fid, const char *name, LinearLayer *layer)
+{
+ int i, n_in, n_out, n_total;
+ char tmp[256];
+
+ n_in = layer->nb_inputs;
+ n_out = layer->nb_outputs;
+ n_total = n_in * n_out;
+
+ fprintf(fid, "\nprinting layer %s...\n", name);
+ fprintf(fid, "%s.nb_inputs: %d\n%s.nb_outputs: %d\n", name, n_in, name, n_out);
+
+ if (layer->bias !=NULL){}
+ if (layer->subias !=NULL){}
+ if (layer->weights !=NULL){}
+ if (layer->float_weights !=NULL){}
+
+ if (layer->bias != NULL) {sprintf(tmp, "%s.bias", name); print_float_array(fid, tmp, layer->bias, n_out);}
+ if (layer->subias != NULL) {sprintf(tmp, "%s.subias", name); print_float_array(fid, tmp, layer->subias, n_out);}
+ if (layer->weights != NULL) {sprintf(tmp, "%s.weights", name); print_int8_array(fid, tmp, layer->weights, n_total);}
+ if (layer->float_weights != NULL) {sprintf(tmp, "%s.float_weights", name); print_float_array(fid, tmp, layer->float_weights, n_total);}
+ //if (layer->weights_idx != NULL) {sprintf(tmp, "%s.weights_idx", name); print_float_array(fid, tmp, layer->weights_idx, n_total);}
+ if (layer->diag != NULL) {sprintf(tmp, "%s.diag", name); print_float_array(fid, tmp, layer->diag, n_in);}
+ if (layer->scale != NULL) {sprintf(tmp, "%s.scale", name); print_float_array(fid, tmp, layer->scale, n_out);}
+
+}
+#endif
+
+int osce_load_models(OSCEModel *model, const void *data, int len)
+{
+ int ret = 0;
+ WeightArray *list;
+
+ if (data != NULL && len)
+ {
+ /* init from buffer */
+ parse_weights(&list, data, len);
+
+#ifndef DISABLE_LACE
+ if (ret == 0) {ret = init_lace(&model->lace, list);}
+#endif
+
+#ifndef DISABLE_NOLACE
+ if (ret == 0) {ret = init_nolace(&model->nolace, list);}
+#endif
+
+ free(list);
+ } else
+ {
+#ifdef USE_WEIGHTS_FILE
+ return -1;
+#else
+#ifndef DISABLE_LACE
+ if (ret == 0) {ret = init_lace(&model->lace, lacelayers_arrays);}
+#endif
+
+#ifndef DISABLE_NOLACE
+ if (ret == 0) {ret = init_nolace(&model->nolace, nolacelayers_arrays);}
+#endif
+
+#endif /* USE_WEIGHTS_FILE */
+ }
+
+ ret = ret ? -1 : 0;
+ return ret;
+}
+
+void osce_enhance_frame(
+ OSCEModel *model, /* I OSCE model struct */
+ silk_decoder_state *psDec, /* I/O Decoder state */
+ silk_decoder_control *psDecCtrl, /* I Decoder control */
+ opus_int16 xq[], /* I/O Decoded speech */
+ opus_int32 num_bits, /* I Size of SILK payload in bits */
+ int arch /* I Run-time architecture */
+)
+{
+ float in_buffer[320];
+ float out_buffer[320];
+ float features[4 * OSCE_FEATURE_DIM];
+ float numbits[2];
+ int periods[4];
+ int i;
+ int method;
+
+ /* enhancement only implemented for 20 ms frame at 16kHz */
+ if (psDec->fs_kHz != 16 || psDec->nb_subfr != 4)
+ {
+ osce_reset(&psDec->osce, psDec->osce.method);
+ return;
+ }
+
+ osce_calculate_features(psDec, psDecCtrl, features, numbits, periods, xq, num_bits);
+
+ /* scale input */
+ for (i = 0; i < 320; i++)
+ {
+ in_buffer[i] = ((float) xq[i]) * (1.f/32768.f);
+ }
+
+ if (model->loaded)
+ method = psDec->osce.method;
+ else
+ method = OSCE_METHOD_NONE;
+ switch(method)
+ {
+ case OSCE_METHOD_NONE:
+ OPUS_COPY(out_buffer, in_buffer, 320);
+ break;
+#ifndef DISABLE_LACE
+ case OSCE_METHOD_LACE:
+ lace_process_20ms_frame(&model->lace, &psDec->osce.state.lace, out_buffer, in_buffer, features, numbits, periods, arch);
+ break;
+#endif
+#ifndef DISABLE_NOLACE
+ case OSCE_METHOD_NOLACE:
+ nolace_process_20ms_frame(&model->nolace, &psDec->osce.state.nolace, out_buffer, in_buffer, features, numbits, periods, arch);
+ break;
+#endif
+ default:
+ celt_assert(0 && "method not defined");
+ }
+
+#ifdef ENABLE_OSCE_TRAINING_DATA
+ int k;
+
+ static FILE *flpc = NULL;
+ static FILE *fgain = NULL;
+ static FILE *fltp = NULL;
+ static FILE *fperiod = NULL;
+ static FILE *fnoisy16k = NULL;
+ static FILE* f_numbits = NULL;
+ static FILE* f_numbits_smooth = NULL;
+
+ if (flpc == NULL) {flpc = fopen("features_lpc.f32", "wb");}
+ if (fgain == NULL) {fgain = fopen("features_gain.f32", "wb");}
+ if (fltp == NULL) {fltp = fopen("features_ltp.f32", "wb");}
+ if (fperiod == NULL) {fperiod = fopen("features_period.s16", "wb");}
+ if (fnoisy16k == NULL) {fnoisy16k = fopen("noisy_16k.s16", "wb");}
+ if(f_numbits == NULL) {f_numbits = fopen("features_num_bits.s32", "wb");}
+ if (f_numbits_smooth == NULL) {f_numbits_smooth = fopen("features_num_bits_smooth.f32", "wb");}
+
+ fwrite(&num_bits, sizeof(num_bits), 1, f_numbits);
+ fwrite(&(psDec->osce.features.numbits_smooth), sizeof(psDec->osce.features.numbits_smooth), 1, f_numbits_smooth);
+
+ for (k = 0; k < psDec->nb_subfr; k++)
+ {
+ float tmp;
+ int16_t itmp;
+ float lpc_buffer[16] = {0};
+ opus_int16 *A_Q12, *B_Q14;
+
+ (void) num_bits;
+ (void) arch;
+
+ /* gain */
+ tmp = (float) psDecCtrl->Gains_Q16[k] / (1UL << 16);
+ fwrite(&tmp, sizeof(tmp), 1, fgain);
+
+ /* LPC */
+ A_Q12 = psDecCtrl->PredCoef_Q12[ k >> 1 ];
+ for (i = 0; i < psDec->LPC_order; i++)
+ {
+ lpc_buffer[i] = (float) A_Q12[i] / (1U << 12);
+ }
+ fwrite(lpc_buffer, sizeof(lpc_buffer[0]), 16, flpc);
+
+ /* LTP */
+ B_Q14 = &psDecCtrl->LTPCoef_Q14[ k * LTP_ORDER ];
+ for (i = 0; i < 5; i++)
+ {
+ tmp = (float) B_Q14[i] / (1U << 14);
+ fwrite(&tmp, sizeof(tmp), 1, fltp);
+ }
+
+ /* periods */
+ itmp = psDec->indices.signalType == TYPE_VOICED ? psDecCtrl->pitchL[ k ] : 0;
+ fwrite(&itmp, sizeof(itmp), 1, fperiod);
+ }
+
+ fwrite(xq, psDec->nb_subfr * psDec->subfr_length, sizeof(xq[0]), fnoisy16k);
+#endif
+
+ if (psDec->osce.features.reset > 1)
+ {
+ OPUS_COPY(out_buffer, in_buffer, 320);
+ psDec->osce.features.reset --;
+ }
+ else if (psDec->osce.features.reset)
+ {
+ osce_cross_fade_10ms(out_buffer, in_buffer, 320);
+ psDec->osce.features.reset = 0;
+ }
+
+ /* scale output */
+ for (i = 0; i < 320; i++)
+ {
+ float tmp = 32768.f * out_buffer[i];
+ if (tmp > 32767.f) tmp = 32767.f;
+ if (tmp < -32767.f) tmp = -32767.f;
+ xq[i] = float2int(tmp);
+ }
+
+}
+
+
+#if 0
+
+#include <stdio.h>
+
+void lace_feature_net_compare(
+ const char * prefix,
+ int num_frames,
+ LACE* hLACE
+)
+{
+ char in_feature_file[256];
+ char out_feature_file[256];
+ char numbits_file[256];
+ char periods_file[256];
+ char message[512];
+ int i_frame, i_feature;
+ float mse;
+ float in_features[4 * LACE_NUM_FEATURES];
+ float out_features[4 * LACE_COND_DIM];
+ float out_features2[4 * LACE_COND_DIM];
+ float numbits[2];
+ int periods[4];
+
+ init_lace(hLACE);
+
+ FILE *f_in_features, *f_out_features, *f_numbits, *f_periods;
+
+ strcpy(in_feature_file, prefix);
+ strcat(in_feature_file, "_in_features.f32");
+ f_in_features = fopen(in_feature_file, "rb");
+ if (f_in_features == NULL)
+ {
+ sprintf(message, "could not open file %s", in_feature_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(out_feature_file, prefix);
+ strcat(out_feature_file, "_out_features.f32");
+ f_out_features = fopen(out_feature_file, "rb");
+ if (f_out_features == NULL)
+ {
+ sprintf(message, "could not open file %s", out_feature_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(periods_file, prefix);
+ strcat(periods_file, "_periods.s32");
+ f_periods = fopen(periods_file, "rb");
+ if (f_periods == NULL)
+ {
+ sprintf(message, "could not open file %s", periods_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(numbits_file, prefix);
+ strcat(numbits_file, "_numbits.f32");
+ f_numbits = fopen(numbits_file, "rb");
+ if (f_numbits == NULL)
+ {
+ sprintf(message, "could not open file %s", numbits_file);
+ perror(message);
+ exit(1);
+ }
+
+ for (i_frame = 0; i_frame < num_frames; i_frame ++)
+ {
+ if(fread(in_features, sizeof(float), 4 * LACE_NUM_FEATURES, f_in_features) != 4 * LACE_NUM_FEATURES)
+ {
+ fprintf(stderr, "could not read frame %d from in_features\n", i_frame);
+ exit(1);
+ }
+ if(fread(out_features, sizeof(float), 4 * LACE_COND_DIM, f_out_features) != 4 * LACE_COND_DIM)
+ {
+ fprintf(stderr, "could not read frame %d from out_features\n", i_frame);
+ exit(1);
+ }
+ if(fread(periods, sizeof(int), 4, f_periods) != 4)
+ {
+ fprintf(stderr, "could not read frame %d from periods\n", i_frame);
+ exit(1);
+ }
+ if(fread(numbits, sizeof(float), 2, f_numbits) != 2)
+ {
+ fprintf(stderr, "could not read frame %d from numbits\n", i_frame);
+ exit(1);
+ }
+
+
+ lace_feature_net(hLACE, out_features2, in_features, numbits, periods);
+
+ float mse = 0;
+ for (int i = 0; i < 4 * LACE_COND_DIM; i ++)
+ {
+ mse += pow(out_features[i] - out_features2[i], 2);
+ }
+ mse /= (4 * LACE_COND_DIM);
+ printf("rmse: %f\n", sqrt(mse));
+
+ }
+
+ fclose(f_in_features);
+ fclose(f_out_features);
+ fclose(f_numbits);
+ fclose(f_periods);
+}
+
+
+void lace_demo(
+ char *prefix,
+ char *output
+)
+{
+ char feature_file[256];
+ char numbits_file[256];
+ char periods_file[256];
+ char x_in_file[256];
+ char message[512];
+ int i_frame;
+ float mse;
+ float features[4 * LACE_NUM_FEATURES];
+ float numbits[2];
+ int periods[4];
+ float x_in[4 * LACE_FRAME_SIZE];
+ int16_t x_out[4 * LACE_FRAME_SIZE];
+ float buffer[4 * LACE_FRAME_SIZE];
+ LACE hLACE;
+ int frame_counter = 0;
+ FILE *f_features, *f_numbits, *f_periods, *f_x_in, *f_x_out;
+
+ init_lace(&hLACE);
+
+ strcpy(feature_file, prefix);
+ strcat(feature_file, "_features.f32");
+ f_features = fopen(feature_file, "rb");
+ if (f_features == NULL)
+ {
+ sprintf(message, "could not open file %s", feature_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(x_in_file, prefix);
+ strcat(x_in_file, "_x_in.f32");
+ f_x_in = fopen(x_in_file, "rb");
+ if (f_x_in == NULL)
+ {
+ sprintf(message, "could not open file %s", x_in_file);
+ perror(message);
+ exit(1);
+ }
+
+ f_x_out = fopen(output, "wb");
+ if (f_x_out == NULL)
+ {
+ sprintf(message, "could not open file %s", output);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(periods_file, prefix);
+ strcat(periods_file, "_periods.s32");
+ f_periods = fopen(periods_file, "rb");
+ if (f_periods == NULL)
+ {
+ sprintf(message, "could not open file %s", periods_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(numbits_file, prefix);
+ strcat(numbits_file, "_numbits.f32");
+ f_numbits = fopen(numbits_file, "rb");
+ if (f_numbits == NULL)
+ {
+ sprintf(message, "could not open file %s", numbits_file);
+ perror(message);
+ exit(1);
+ }
+
+ printf("processing %s\n", prefix);
+
+ while (fread(x_in, sizeof(float), 4 * LACE_FRAME_SIZE, f_x_in) == 4 * LACE_FRAME_SIZE)
+ {
+ printf("\rframe: %d", frame_counter++);
+ if(fread(features, sizeof(float), 4 * LACE_NUM_FEATURES, f_features) != 4 * LACE_NUM_FEATURES)
+ {
+ fprintf(stderr, "could not read frame %d from features\n", i_frame);
+ exit(1);
+ }
+ if(fread(periods, sizeof(int), 4, f_periods) != 4)
+ {
+ fprintf(stderr, "could not read frame %d from periods\n", i_frame);
+ exit(1);
+ }
+ if(fread(numbits, sizeof(float), 2, f_numbits) != 2)
+ {
+ fprintf(stderr, "could not read frame %d from numbits\n", i_frame);
+ exit(1);
+ }
+
+ lace_process_20ms_frame(
+ &hLACE,
+ buffer,
+ x_in,
+ features,
+ numbits,
+ periods
+ );
+
+ for (int n=0; n < 4 * LACE_FRAME_SIZE; n ++)
+ {
+ float tmp = (1UL<<15) * buffer[n];
+ tmp = CLIP(tmp, -32768, 32767);
+ x_out[n] = (int16_t) round(tmp);
+ }
+
+ fwrite(x_out, sizeof(int16_t), 4 * LACE_FRAME_SIZE, f_x_out);
+ }
+ printf("\ndone!\n");
+
+ fclose(f_features);
+ fclose(f_numbits);
+ fclose(f_periods);
+ fclose(f_x_in);
+ fclose(f_x_out);
+}
+
+void nolace_demo(
+ char *prefix,
+ char *output
+)
+{
+ char feature_file[256];
+ char numbits_file[256];
+ char periods_file[256];
+ char x_in_file[256];
+ char message[512];
+ int i_frame;
+ float mse;
+ float features[4 * LACE_NUM_FEATURES];
+ float numbits[2];
+ int periods[4];
+ float x_in[4 * LACE_FRAME_SIZE];
+ int16_t x_out[4 * LACE_FRAME_SIZE];
+ float buffer[4 * LACE_FRAME_SIZE];
+ NoLACE hNoLACE;
+ int frame_counter = 0;
+ FILE *f_features, *f_numbits, *f_periods, *f_x_in, *f_x_out;
+
+ init_nolace(&hNoLACE);
+
+ strcpy(feature_file, prefix);
+ strcat(feature_file, "_features.f32");
+ f_features = fopen(feature_file, "rb");
+ if (f_features == NULL)
+ {
+ sprintf(message, "could not open file %s", feature_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(x_in_file, prefix);
+ strcat(x_in_file, "_x_in.f32");
+ f_x_in = fopen(x_in_file, "rb");
+ if (f_x_in == NULL)
+ {
+ sprintf(message, "could not open file %s", x_in_file);
+ perror(message);
+ exit(1);
+ }
+
+ f_x_out = fopen(output, "wb");
+ if (f_x_out == NULL)
+ {
+ sprintf(message, "could not open file %s", output);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(periods_file, prefix);
+ strcat(periods_file, "_periods.s32");
+ f_periods = fopen(periods_file, "rb");
+ if (f_periods == NULL)
+ {
+ sprintf(message, "could not open file %s", periods_file);
+ perror(message);
+ exit(1);
+ }
+
+ strcpy(numbits_file, prefix);
+ strcat(numbits_file, "_numbits.f32");
+ f_numbits = fopen(numbits_file, "rb");
+ if (f_numbits == NULL)
+ {
+ sprintf(message, "could not open file %s", numbits_file);
+ perror(message);
+ exit(1);
+ }
+
+ printf("processing %s\n", prefix);
+
+ while (fread(x_in, sizeof(float), 4 * LACE_FRAME_SIZE, f_x_in) == 4 * LACE_FRAME_SIZE)
+ {
+ printf("\rframe: %d", frame_counter++);
+ if(fread(features, sizeof(float), 4 * LACE_NUM_FEATURES, f_features) != 4 * LACE_NUM_FEATURES)
+ {
+ fprintf(stderr, "could not read frame %d from features\n", i_frame);
+ exit(1);
+ }
+ if(fread(periods, sizeof(int), 4, f_periods) != 4)
+ {
+ fprintf(stderr, "could not read frame %d from periods\n", i_frame);
+ exit(1);
+ }
+ if(fread(numbits, sizeof(float), 2, f_numbits) != 2)
+ {
+ fprintf(stderr, "could not read frame %d from numbits\n", i_frame);
+ exit(1);
+ }
+
+ nolace_process_20ms_frame(
+ &hNoLACE,
+ buffer,
+ x_in,
+ features,
+ numbits,
+ periods
+ );
+
+ for (int n=0; n < 4 * LACE_FRAME_SIZE; n ++)
+ {
+ float tmp = (1UL<<15) * buffer[n];
+ tmp = CLIP(tmp, -32768, 32767);
+ x_out[n] = (int16_t) round(tmp);
+ }
+
+ fwrite(x_out, sizeof(int16_t), 4 * LACE_FRAME_SIZE, f_x_out);
+ }
+ printf("\ndone!\n");
+
+ fclose(f_features);
+ fclose(f_numbits);
+ fclose(f_periods);
+ fclose(f_x_in);
+ fclose(f_x_out);
+}
+
+
+int main()
+{
+#if 0
+ LACE hLACE;
+
+ lace_feature_net_compare("testvec2/lace", 5, &hLACE);
+
+ lace_demo("testdata/test9", "out_lace_c_9kbps.pcm");
+ lace_demo("testdata/test6", "out_lace_c_6kbps.pcm");
+#endif
+ nolace_demo("testdata/test9", "out_nolace_c_9kbps.pcm");
+
+}
+#endif
+
+/*gcc -I ../include -I . -I ../silk -I ../celt osce.c nndsp.c lace_data.c nolace_data.c nnet.c parse_lpcnet_weights.c -lm -o lacetest*/
diff --git a/dnn/osce.h b/dnn/osce.h
new file mode 100644
index 00000000..1aadfad4
--- /dev/null
+++ b/dnn/osce.h
@@ -0,0 +1,84 @@
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef OSCE_H
+#define OSCE_H
+
+
+#include "opus_types.h"
+/*#include "osce_config.h"*/
+#ifndef DISABLE_LACE
+#include "lace_data.h"
+#endif
+#ifndef DISABLE_NOLACE
+#include "nolace_data.h"
+#endif
+#include "nndsp.h"
+#include "nnet.h"
+#include "osce_structs.h"
+#include "structs.h"
+
+#define OSCE_METHOD_NONE 0
+#ifndef DISABLE_LACE
+#define OSCE_METHOD_LACE 1
+#endif
+#ifndef DISABLE_NOLACE
+#define OSCE_METHOD_NOLACE 2
+#endif
+
+#if !defined(DISABLE_NOLACE)
+#define OSCE_DEFAULT_METHOD OSCE_METHOD_NOLACE
+#define OSCE_MAX_RNN_NEURONS NOLACE_FNET_GRU_STATE_SIZE
+#elif !defined(DISABLE_LACE)
+#define OSCE_DEFAULT_METHOD OSCE_METHOD_LACE
+#define OSCE_MAX_RNN_NEURONS LACE_FNET_GRU_STATE_SIZE
+#else
+#define OSCE_DEFAULT_METHOD OSCE_METHOD_NONE
+#define OSCE_MAX_RNN_NEURONS 0
+#endif
+
+
+
+
+/* API */
+
+
+void osce_enhance_frame(
+ OSCEModel *model, /* I OSCE model struct */
+ silk_decoder_state *psDec, /* I/O Decoder state */
+ silk_decoder_control *psDecCtrl, /* I Decoder control */
+ opus_int16 xq[], /* I/O Decoded speech */
+ opus_int32 num_bits, /* I Size of SILK payload in bits */
+ int arch /* I Run-time architecture */
+);
+
+
+int osce_load_models(OSCEModel *hModel, const void *data, int len);
+void osce_reset(silk_OSCE_struct *hOSCE, int method);
+
+
+#endif
diff --git a/dnn/osce_config.h b/dnn/osce_config.h
new file mode 100644
index 00000000..bc187bb3
--- /dev/null
+++ b/dnn/osce_config.h
@@ -0,0 +1,60 @@
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef OSCE_CONFIG
+#define OSCE_CONFIG
+
+#define OSCE_FEATURES_MAX_HISTORY 350
+#define OSCE_FEATURE_DIM 93
+#define OSCE_MAX_FEATURE_FRAMES 4
+
+#define OSCE_CLEAN_SPEC_NUM_BANDS 64
+#define OSCE_NOISY_SPEC_NUM_BANDS 18
+
+#define OSCE_NO_PITCH_VALUE 7
+
+#define OSCE_PREEMPH 0.85f
+
+#define OSCE_PITCH_HANGOVER 0
+
+#define OSCE_CLEAN_SPEC_START 0
+#define OSCE_CLEAN_SPEC_LENGTH 64
+
+#define OSCE_NOISY_CEPSTRUM_START 64
+#define OSCE_NOISY_CEPSTRUM_LENGTH 18
+
+#define OSCE_ACORR_START 82
+#define OSCE_ACORR_LENGTH 5
+
+#define OSCE_LTP_START 87
+#define OSCE_LTP_LENGTH 5
+
+#define OSCE_LOG_GAIN_START 92
+#define OSCE_LOG_GAIN_LENGTH 1
+
+
+#endif
diff --git a/dnn/osce_features.c b/dnn/osce_features.c
new file mode 100644
index 00000000..bcd48016
--- /dev/null
+++ b/dnn/osce_features.c
@@ -0,0 +1,454 @@
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define OSCE_SPEC_WINDOW_SIZE 320
+#define OSCE_SPEC_NUM_FREQS 161
+
+
+/*DEBUG*/
+/*#define WRITE_FEATURES*/
+/*#define DEBUG_PRING*/
+/*******/
+
+#include "stack_alloc.h"
+#include "osce_features.h"
+#include "kiss_fft.h"
+#include "os_support.h"
+#include "osce.h"
+#include "freq.h"
+
+
+#if defined(WRITE_FEATURES) || defined(DEBUG_PRING)
+#include <stdio.h>
+#include <stdlib.h>
+#endif
+
+static const int center_bins_clean[64] = {
+ 0, 2, 5, 8, 10, 12, 15, 18,
+ 20, 22, 25, 28, 30, 33, 35, 38,
+ 40, 42, 45, 48, 50, 52, 55, 58,
+ 60, 62, 65, 68, 70, 73, 75, 78,
+ 80, 82, 85, 88, 90, 92, 95, 98,
+ 100, 102, 105, 108, 110, 112, 115, 118,
+ 120, 122, 125, 128, 130, 132, 135, 138,
+ 140, 142, 145, 148, 150, 152, 155, 160
+};
+
+static const int center_bins_noisy[18] = {
+ 0, 4, 8, 12, 16, 20, 24, 28,
+ 32, 40, 48, 56, 64, 80, 96, 112,
+ 136, 160
+};
+
+static const float band_weights_clean[64] = {
+ 0.666666666667f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.400000000000f, 0.400000000000f, 0.400000000000f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.400000000000f, 0.400000000000f, 0.400000000000f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.333333333333f, 0.400000000000f,
+ 0.500000000000f, 0.400000000000f, 0.250000000000f, 0.333333333333f
+};
+
+static const float band_weights_noisy[18] = {
+ 0.400000000000f, 0.250000000000f, 0.250000000000f, 0.250000000000f,
+ 0.250000000000f, 0.250000000000f, 0.250000000000f, 0.250000000000f,
+ 0.166666666667f, 0.125000000000f, 0.125000000000f, 0.125000000000f,
+ 0.083333333333f, 0.062500000000f, 0.062500000000f, 0.050000000000f,
+ 0.041666666667f, 0.080000000000f
+};
+
+static float osce_window[OSCE_SPEC_WINDOW_SIZE] = {
+ 0.004908718808f, 0.014725683311f, 0.024541228523f, 0.034354408400f, 0.044164277127f,
+ 0.053969889210f, 0.063770299562f, 0.073564563600f, 0.083351737332f, 0.093130877450f,
+ 0.102901041421f, 0.112661287575f, 0.122410675199f, 0.132148264628f, 0.141873117332f,
+ 0.151584296010f, 0.161280864678f, 0.170961888760f, 0.180626435180f, 0.190273572448f,
+ 0.199902370753f, 0.209511902052f, 0.219101240157f, 0.228669460829f, 0.238215641862f,
+ 0.247738863176f, 0.257238206902f, 0.266712757475f, 0.276161601717f, 0.285583828929f,
+ 0.294978530977f, 0.304344802381f, 0.313681740399f, 0.322988445118f, 0.332264019538f,
+ 0.341507569661f, 0.350718204573f, 0.359895036535f, 0.369037181064f, 0.378143757022f,
+ 0.387213886697f, 0.396246695891f, 0.405241314005f, 0.414196874117f, 0.423112513073f,
+ 0.431987371563f, 0.440820594212f, 0.449611329655f, 0.458358730621f, 0.467061954019f,
+ 0.475720161014f, 0.484332517110f, 0.492898192230f, 0.501416360796f, 0.509886201809f,
+ 0.518306898929f, 0.526677640552f, 0.534997619887f, 0.543266035038f, 0.551482089078f,
+ 0.559644990127f, 0.567753951426f, 0.575808191418f, 0.583806933818f, 0.591749407690f,
+ 0.599634847523f, 0.607462493302f, 0.615231590581f, 0.622941390558f, 0.630591150148f,
+ 0.638180132051f, 0.645707604824f, 0.653172842954f, 0.660575126926f, 0.667913743292f,
+ 0.675187984742f, 0.682397150168f, 0.689540544737f, 0.696617479953f, 0.703627273726f,
+ 0.710569250438f, 0.717442741007f, 0.724247082951f, 0.730981620454f, 0.737645704427f,
+ 0.744238692572f, 0.750759949443f, 0.757208846506f, 0.763584762206f, 0.769887082016f,
+ 0.776115198508f, 0.782268511401f, 0.788346427627f, 0.794348361383f, 0.800273734191f,
+ 0.806121974951f, 0.811892519997f, 0.817584813152f, 0.823198305781f, 0.828732456844f,
+ 0.834186732948f, 0.839560608398f, 0.844853565250f, 0.850065093356f, 0.855194690420f,
+ 0.860241862039f, 0.865206121757f, 0.870086991109f, 0.874883999665f, 0.879596685080f,
+ 0.884224593137f, 0.888767277786f, 0.893224301196f, 0.897595233788f, 0.901879654283f,
+ 0.906077149740f, 0.910187315596f, 0.914209755704f, 0.918144082372f, 0.921989916403f,
+ 0.925746887127f, 0.929414632439f, 0.932992798835f, 0.936481041442f, 0.939879024058f,
+ 0.943186419177f, 0.946402908026f, 0.949528180593f, 0.952561935658f, 0.955503880820f,
+ 0.958353732530f, 0.961111216112f, 0.963776065795f, 0.966348024735f, 0.968826845041f,
+ 0.971212287799f, 0.973504123096f, 0.975702130039f, 0.977806096779f, 0.979815820533f,
+ 0.981731107599f, 0.983551773378f, 0.985277642389f, 0.986908548290f, 0.988444333892f,
+ 0.989884851171f, 0.991229961288f, 0.992479534599f, 0.993633450666f, 0.994691598273f,
+ 0.995653875433f, 0.996520189401f, 0.997290456679f, 0.997964603026f, 0.998542563469f,
+ 0.999024282300f, 0.999409713092f, 0.999698818696f, 0.999891571247f, 0.999987952167f,
+ 0.999987952167f, 0.999891571247f, 0.999698818696f, 0.999409713092f, 0.999024282300f,
+ 0.998542563469f, 0.997964603026f, 0.997290456679f, 0.996520189401f, 0.995653875433f,
+ 0.994691598273f, 0.993633450666f, 0.992479534599f, 0.991229961288f, 0.989884851171f,
+ 0.988444333892f, 0.986908548290f, 0.985277642389f, 0.983551773378f, 0.981731107599f,
+ 0.979815820533f, 0.977806096779f, 0.975702130039f, 0.973504123096f, 0.971212287799f,
+ 0.968826845041f, 0.966348024735f, 0.963776065795f, 0.961111216112f, 0.958353732530f,
+ 0.955503880820f, 0.952561935658f, 0.949528180593f, 0.946402908026f, 0.943186419177f,
+ 0.939879024058f, 0.936481041442f, 0.932992798835f, 0.929414632439f, 0.925746887127f,
+ 0.921989916403f, 0.918144082372f, 0.914209755704f, 0.910187315596f, 0.906077149740f,
+ 0.901879654283f, 0.897595233788f, 0.893224301196f, 0.888767277786f, 0.884224593137f,
+ 0.879596685080f, 0.874883999665f, 0.870086991109f, 0.865206121757f, 0.860241862039f,
+ 0.855194690420f, 0.850065093356f, 0.844853565250f, 0.839560608398f, 0.834186732948f,
+ 0.828732456844f, 0.823198305781f, 0.817584813152f, 0.811892519997f, 0.806121974951f,
+ 0.800273734191f, 0.794348361383f, 0.788346427627f, 0.782268511401f, 0.776115198508f,
+ 0.769887082016f, 0.763584762206f, 0.757208846506f, 0.750759949443f, 0.744238692572f,
+ 0.737645704427f, 0.730981620454f, 0.724247082951f, 0.717442741007f, 0.710569250438f,
+ 0.703627273726f, 0.696617479953f, 0.689540544737f, 0.682397150168f, 0.675187984742f,
+ 0.667913743292f, 0.660575126926f, 0.653172842954f, 0.645707604824f, 0.638180132051f,
+ 0.630591150148f, 0.622941390558f, 0.615231590581f, 0.607462493302f, 0.599634847523f,
+ 0.591749407690f, 0.583806933818f, 0.575808191418f, 0.567753951426f, 0.559644990127f,
+ 0.551482089078f, 0.543266035038f, 0.534997619887f, 0.526677640552f, 0.518306898929f,
+ 0.509886201809f, 0.501416360796f, 0.492898192230f, 0.484332517110f, 0.475720161014f,
+ 0.467061954019f, 0.458358730621f, 0.449611329655f, 0.440820594212f, 0.431987371563f,
+ 0.423112513073f, 0.414196874117f, 0.405241314005f, 0.396246695891f, 0.387213886697f,
+ 0.378143757022f, 0.369037181064f, 0.359895036535f, 0.350718204573f, 0.341507569661f,
+ 0.332264019538f, 0.322988445118f, 0.313681740399f, 0.304344802381f, 0.294978530977f,
+ 0.285583828929f, 0.276161601717f, 0.266712757475f, 0.257238206902f, 0.247738863176f,
+ 0.238215641862f, 0.228669460829f, 0.219101240157f, 0.209511902052f, 0.199902370753f,
+ 0.190273572448f, 0.180626435180f, 0.170961888760f, 0.161280864678f, 0.151584296010f,
+ 0.141873117332f, 0.132148264628f, 0.122410675199f, 0.112661287575f, 0.102901041421f,
+ 0.093130877450f, 0.083351737332f, 0.073564563600f, 0.063770299562f, 0.053969889210f,
+ 0.044164277127f, 0.034354408400f, 0.024541228523f, 0.014725683311f, 0.004908718808f
+};
+
+static void apply_filterbank(float *x_out, float *x_in, const int *center_bins, const float* band_weights, int num_bands)
+{
+ int b, i;
+ float frac;
+
+ celt_assert(x_in != x_out)
+
+ x_out[0] = 0;
+ for (b = 0; b < num_bands - 1; b++)
+ {
+ x_out[b+1] = 0;
+ for (i = center_bins[b]; i < center_bins[b+1]; i++)
+ {
+ frac = (float) (center_bins[b+1] - i) / (center_bins[b+1] - center_bins[b]);
+ x_out[b] += band_weights[b] * frac * x_in[i];
+ x_out[b+1] += band_weights[b+1] * (1 - frac) * x_in[i];
+
+ }
+ }
+ x_out[num_bands - 1] += band_weights[num_bands - 1] * x_in[center_bins[num_bands - 1]];
+#ifdef DEBUG_PRINT
+ for (b = 0; b < num_bands; b++)
+ {
+ printf("band[%d]: %f\n", b, x_out[b]);
+ }
+#endif
+}
+
+
+static void mag_spec_320_onesided(float *out, float *in)
+{
+ celt_assert(OSCE_SPEC_WINDOW_SIZE == 320);
+ kiss_fft_cpx buffer[OSCE_SPEC_WINDOW_SIZE];
+ int k;
+ forward_transform(buffer, in);
+
+ for (k = 0; k < OSCE_SPEC_NUM_FREQS; k++)
+ {
+ out[k] = OSCE_SPEC_WINDOW_SIZE * sqrt(buffer[k].r * buffer[k].r + buffer[k].i * buffer[k].i);
+#ifdef DEBUG_PRINT
+ printf("magspec[%d]: %f\n", k, out[k]);
+#endif
+ }
+}
+
+
+static void calculate_log_spectrum_from_lpc(float *spec, opus_int16 *a_q12, int lpc_order)
+{
+ float buffer[OSCE_SPEC_WINDOW_SIZE] = {0};
+ int i;
+
+ /* zero expansion */
+ buffer[0] = 1;
+ for (i = 0; i < lpc_order; i++)
+ {
+ buffer[i+1] = - (float)a_q12[i] / (1U << 12);
+ }
+
+ /* calculate and invert magnitude spectrum */
+ mag_spec_320_onesided(buffer, buffer);
+
+ for (i = 0; i < OSCE_SPEC_NUM_FREQS; i++)
+ {
+ buffer[i] = 1.f / (buffer[i] + 1e-9f);
+ }
+
+ /* apply filterbank */
+ apply_filterbank(spec, buffer, center_bins_clean, band_weights_clean, OSCE_CLEAN_SPEC_NUM_BANDS);
+
+ /* log and scaling */
+ for (i = 0; i < OSCE_CLEAN_SPEC_NUM_BANDS; i++)
+ {
+ spec[i] = 0.3f * log(spec[i] + 1e-9f);
+ }
+}
+
+static void calculate_cepstrum(float *cepstrum, float *signal)
+{
+ float buffer[OSCE_SPEC_WINDOW_SIZE];
+ float *spec = &buffer[OSCE_SPEC_NUM_FREQS + 3];
+ int n;
+
+ celt_assert(cepstrum != signal)
+
+ for (n = 0; n < OSCE_SPEC_WINDOW_SIZE; n++)
+ {
+ buffer[n] = osce_window[n] * signal[n];
+ }
+
+ /* calculate magnitude spectrum */
+ mag_spec_320_onesided(buffer, buffer);
+
+ /* accumulate bands */
+ apply_filterbank(spec, buffer, center_bins_noisy, band_weights_noisy, OSCE_NOISY_SPEC_NUM_BANDS);
+
+ /* log domain conversion */
+ for (n = 0; n < OSCE_NOISY_SPEC_NUM_BANDS; n++)
+ {
+ spec[n] = log(spec[n] + 1e-9f);
+#ifdef DEBUG_PRINT
+ printf("logspec[%d]: %f\n", n, spec[n]);
+#endif
+ }
+
+ /* DCT-II (orthonormal) */
+ celt_assert(OSCE_NOISY_SPEC_NUM_BANDS == NB_BANDS);
+ dct(cepstrum, spec);
+}
+
+static void calculate_acorr(float *acorr, float *signal, int lag)
+{
+ int n, k;
+ celt_assert(acorr != signal)
+
+ for (k = -2; k <= 2; k++)
+ {
+ acorr[k+2] = 0;
+ float xx = 0;
+ float xy = 0;
+ float yy = 0;
+ for (n = 0; n < 80; n++)
+ {
+ /* obviously wasteful -> fix later */
+ xx += signal[n] * signal[n];
+ yy += signal[n - lag + k] * signal[n - lag + k];
+ xy += signal[n] * signal[n - lag + k];
+ }
+ acorr[k+2] = xy / sqrt(xx * yy + 1e-9f);
+ }
+}
+
+static int pitch_postprocessing(OSCEFeatureState *psFeatures, int lag, int type)
+{
+ int new_lag;
+ int modulus;
+
+#ifdef OSCE_HANGOVER_BUGFIX
+#define TESTBIT 1
+#else
+#define TESTBIT 0
+#endif
+
+ modulus = OSCE_PITCH_HANGOVER;
+ if (modulus == 0) modulus ++;
+
+ /* hangover is currently disabled to reflect a bug in the python code. ToDo: re-evaluate hangover */
+ if (type != TYPE_VOICED && psFeatures->last_type == TYPE_VOICED && TESTBIT)
+ /* enter hangover */
+ {
+ new_lag = OSCE_NO_PITCH_VALUE;
+ if (psFeatures->pitch_hangover_count < OSCE_PITCH_HANGOVER)
+ {
+ new_lag = psFeatures->last_lag;
+ psFeatures->pitch_hangover_count = (psFeatures->pitch_hangover_count + 1) % modulus;
+ }
+ }
+ else if (type != TYPE_VOICED && psFeatures->pitch_hangover_count && TESTBIT)
+ /* continue hangover */
+ {
+ new_lag = psFeatures->last_lag;
+ psFeatures->pitch_hangover_count = (psFeatures->pitch_hangover_count + 1) % modulus;
+ }
+ else if (type != TYPE_VOICED)
+ /* unvoiced frame after hangover */
+ {
+ new_lag = OSCE_NO_PITCH_VALUE;
+ psFeatures->pitch_hangover_count = 0;
+ }
+ else
+ /* voiced frame: update last_lag */
+ {
+ new_lag = lag;
+ psFeatures->last_lag = lag;
+ psFeatures->pitch_hangover_count = 0;
+ }
+
+ /* buffer update */
+ psFeatures->last_type = type;
+
+ /* with the current setup this should never happen (but who knows...) */
+ celt_assert(new_lag)
+
+ return new_lag;
+}
+
+void osce_calculate_features(
+ silk_decoder_state *psDec, /* I/O Decoder state */
+ silk_decoder_control *psDecCtrl, /* I Decoder control */
+ float *features, /* O input features */
+ float *numbits, /* O numbits and smoothed numbits */
+ int *periods, /* O pitch lags on subframe basis */
+ const opus_int16 xq[], /* I Decoded speech */
+ opus_int32 num_bits /* I Size of SILK payload in bits */
+)
+{
+ int num_subframes, num_samples;
+ float buffer[OSCE_FEATURES_MAX_HISTORY + OSCE_MAX_FEATURE_FRAMES * 80];
+ float *frame, *pfeatures;
+ OSCEFeatureState *psFeatures;
+ int i, n, k;
+#ifdef WRITE_FEATURES
+ static FILE *f_feat = NULL;
+ if (f_feat == NULL)
+ {
+ f_feat = fopen("assembled_features.f32", "wb");
+ }
+#endif
+
+ /*OPUS_CLEAR(buffer, 1);*/
+ memset(buffer, 0, sizeof(buffer));
+
+ num_subframes = psDec->nb_subfr;
+ num_samples = num_subframes * 80;
+ psFeatures = &psDec->osce.features;
+
+ /* smooth bit count */
+ psFeatures->numbits_smooth = 0.9f * psFeatures->numbits_smooth + 0.1f * num_bits;
+ numbits[0] = num_bits;
+ numbits[1] = psFeatures->numbits_smooth;
+
+ for (n = 0; n < num_samples; n++)
+ {
+ buffer[OSCE_FEATURES_MAX_HISTORY + n] = (float) xq[n] / (1U<<15);
+ }
+ OPUS_COPY(buffer, psFeatures->signal_history, OSCE_FEATURES_MAX_HISTORY);
+
+ for (k = 0; k < num_subframes; k++)
+ {
+ pfeatures = features + k * OSCE_FEATURE_DIM;
+ frame = &buffer[OSCE_FEATURES_MAX_HISTORY + k * 80];
+ memset(pfeatures, 0, OSCE_FEATURE_DIM); /* precaution */
+
+ /* clean spectrum from lpcs (update every other frame) */
+ if (k % 2 == 0)
+ {
+ calculate_log_spectrum_from_lpc(pfeatures + OSCE_CLEAN_SPEC_START, psDecCtrl->PredCoef_Q12[k >> 1], psDec->LPC_order);
+ }
+ else
+ {
+ OPUS_COPY(pfeatures + OSCE_CLEAN_SPEC_START, pfeatures + OSCE_CLEAN_SPEC_START - OSCE_FEATURE_DIM, OSCE_CLEAN_SPEC_LENGTH);
+ }
+
+ /* noisy cepstrum from signal (update every other frame) */
+ if (k % 2 == 0)
+ {
+ calculate_cepstrum(pfeatures + OSCE_NOISY_CEPSTRUM_START, frame - 160);
+ }
+ else
+ {
+ OPUS_COPY(pfeatures + OSCE_NOISY_CEPSTRUM_START, pfeatures + OSCE_NOISY_CEPSTRUM_START - OSCE_FEATURE_DIM, OSCE_NOISY_CEPSTRUM_LENGTH);
+ }
+
+ /* pitch hangover and zero value replacement */
+ periods[k] = pitch_postprocessing(psFeatures, psDecCtrl->pitchL[k], psDec->indices.signalType);
+
+ /* auto-correlation around pitch lag */
+ calculate_acorr(pfeatures + OSCE_ACORR_START, frame, periods[k]);
+
+ /* ltp */
+ celt_assert(OSCE_LTP_LENGTH == LTP_ORDER)
+ for (i = 0; i < OSCE_LTP_LENGTH; i++)
+ {
+ pfeatures[OSCE_LTP_START + i] = (float) psDecCtrl->LTPCoef_Q14[k * LTP_ORDER + i] / (1U << 14);
+ }
+
+ /* frame gain */
+ pfeatures[OSCE_LOG_GAIN_START] = log((float) psDecCtrl->Gains_Q16[k] / (1UL << 16) + 1e-9f);
+
+#ifdef WRITE_FEATURES
+ fwrite(pfeatures, sizeof(*pfeatures), 93, f_feat);
+#endif
+ }
+
+ /* buffer update */
+ OPUS_COPY(psFeatures->signal_history, &buffer[num_samples], OSCE_FEATURES_MAX_HISTORY);
+}
+
+
+void osce_cross_fade_10ms(float *x_enhanced, float *x_in, int length)
+{
+ int i;
+ celt_assert(length >= 160);
+
+ for (i = 0; i < 160; i++)
+ {
+ x_enhanced[i] = osce_window[i] * x_enhanced[i] + (1.f - osce_window[i]) * x_in[i];
+ }
+
+
+}
diff --git a/dnn/osce_features.h b/dnn/osce_features.h
new file mode 100644
index 00000000..1eeb5677
--- /dev/null
+++ b/dnn/osce_features.h
@@ -0,0 +1,50 @@
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef OSCE_FEATURES_H
+#define OSCE_FEATURES_H
+
+
+#include "structs.h"
+#include "opus_types.h"
+
+#define OSCE_NUMBITS_BUGFIX
+
+void osce_calculate_features(
+ silk_decoder_state *psDec, /* I/O Decoder state */
+ silk_decoder_control *psDecCtrl, /* I Decoder control */
+ float *features, /* O input features */
+ float *numbits, /* O numbits and smoothed numbits */
+ int *periods, /* O pitch lags on subframe basis */
+ const opus_int16 xq[], /* I Decoded speech */
+ opus_int32 num_bits /* I Size of SILK payload in bits */
+);
+
+
+void osce_cross_fade_10ms(float *x_enhanced, float *x_in, int length);
+
+#endif
diff --git a/dnn/osce_structs.h b/dnn/osce_structs.h
new file mode 100644
index 00000000..6358681f
--- /dev/null
+++ b/dnn/osce_structs.h
@@ -0,0 +1,125 @@
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef OSCE_STRUCTS_H
+#define OSCE_STRUCTS_H
+
+#include "opus_types.h"
+#include "osce_config.h"
+#ifndef DISABLE_LACE
+#include "lace_data.h"
+#endif
+#ifndef DISABLE_NOLACE
+#include "nolace_data.h"
+#endif
+#include "nndsp.h"
+#include "nnet.h"
+
+/* feature calculation */
+
+typedef struct {
+ float numbits_smooth;
+ int pitch_hangover_count;
+ int last_lag;
+ int last_type;
+ float signal_history[OSCE_FEATURES_MAX_HISTORY];
+ int reset;
+} OSCEFeatureState;
+
+
+#ifndef DISABLE_LACE
+/* LACE */
+typedef struct {
+ float feature_net_conv2_state[LACE_FNET_CONV2_STATE_SIZE];
+ float feature_net_gru_state[LACE_COND_DIM];
+ AdaCombState cf1_state;
+ AdaCombState cf2_state;
+ AdaConvState af1_state;
+ float preemph_mem;
+ float deemph_mem;
+} LACEState;
+
+typedef struct
+{
+ LACELayers layers;
+ float window[LACE_OVERLAP_SIZE];
+} LACE;
+
+#endif /* #ifndef DISABLE_LACE */
+
+
+#ifndef DISABLE_NOLACE
+/* NoLACE */
+typedef struct {
+ float feature_net_conv2_state[NOLACE_FNET_CONV2_STATE_SIZE];
+ float feature_net_gru_state[NOLACE_COND_DIM];
+ float post_cf1_state[NOLACE_COND_DIM];
+ float post_cf2_state[NOLACE_COND_DIM];
+ float post_af1_state[NOLACE_COND_DIM];
+ float post_af2_state[NOLACE_COND_DIM];
+ float post_af3_state[NOLACE_COND_DIM];
+ AdaCombState cf1_state;
+ AdaCombState cf2_state;
+ AdaConvState af1_state;
+ AdaConvState af2_state;
+ AdaConvState af3_state;
+ AdaConvState af4_state;
+ AdaShapeState tdshape1_state;
+ AdaShapeState tdshape2_state;
+ AdaShapeState tdshape3_state;
+ float preemph_mem;
+ float deemph_mem;
+} NoLACEState;
+
+typedef struct {
+ NOLACELayers layers;
+ float window[LACE_OVERLAP_SIZE];
+} NoLACE;
+
+#endif /* #ifndef DISABLE_NOLACE */
+
+/* OSCEModel */
+typedef struct {
+ int loaded;
+#ifndef DISABLE_LACE
+ LACE lace;
+#endif
+#ifndef DISABLE_NOLACE
+ NoLACE nolace;
+#endif
+} OSCEModel;
+
+typedef union {
+#ifndef DISABLE_LACE
+ LACEState lace;
+#endif
+#ifndef DISABLE_NOLACE
+ NoLACEState nolace;
+#endif
+} OSCEState;
+
+#endif
diff --git a/dnn/parse_lpcnet_weights.c b/dnn/parse_lpcnet_weights.c
new file mode 100644
index 00000000..01ab7f8e
--- /dev/null
+++ b/dnn/parse_lpcnet_weights.c
@@ -0,0 +1,238 @@
+/* Copyright (c) 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include "nnet.h"
+#include "os_support.h"
+
+#define SPARSE_BLOCK_SIZE 32
+
+int parse_record(const void **data, int *len, WeightArray *array) {
+ WeightHead *h = (WeightHead *)*data;
+ if (*len < WEIGHT_BLOCK_SIZE) return -1;
+ if (h->block_size < h->size) return -1;
+ if (h->block_size > *len-WEIGHT_BLOCK_SIZE) return -1;
+ if (h->name[sizeof(h->name)-1] != 0) return -1;
+ if (h->size < 0) return -1;
+ array->name = h->name;
+ array->type = h->type;
+ array->size = h->size;
+ array->data = (void*)((unsigned char*)(*data)+WEIGHT_BLOCK_SIZE);
+
+ *data = (void*)((unsigned char*)*data + h->block_size+WEIGHT_BLOCK_SIZE);
+ *len -= h->block_size+WEIGHT_BLOCK_SIZE;
+ return array->size;
+}
+
+int parse_weights(WeightArray **list, const void *data, int len)
+{
+ int nb_arrays=0;
+ int capacity=20;
+ *list = opus_alloc(capacity*sizeof(WeightArray));
+ while (len > 0) {
+ int ret;
+ WeightArray array = {NULL, 0, 0, 0};
+ ret = parse_record(&data, &len, &array);
+ if (ret > 0) {
+ if (nb_arrays+1 >= capacity) {
+ /* Make sure there's room for the ending NULL element too. */
+ capacity = capacity*3/2;
+ *list = opus_realloc(*list, capacity*sizeof(WeightArray));
+ }
+ (*list)[nb_arrays++] = array;
+ } else {
+ opus_free(*list);
+ *list = NULL;
+ return -1;
+ }
+ }
+ (*list)[nb_arrays].name=NULL;
+ return nb_arrays;
+}
+
+static const void *find_array_entry(const WeightArray *arrays, const char *name) {
+ while (arrays->name && strcmp(arrays->name, name) != 0) arrays++;
+ return arrays;
+}
+
+static const void *find_array_check(const WeightArray *arrays, const char *name, int size) {
+ const WeightArray *a = find_array_entry(arrays, name);
+ if (a->name && a->size == size) return a->data;
+ else return NULL;
+}
+
+static const void *opt_array_check(const WeightArray *arrays, const char *name, int size, int *error) {
+ const WeightArray *a = find_array_entry(arrays, name);
+ *error = (a->name != NULL && a->size != size);
+ if (a->name && a->size == size) return a->data;
+ else return NULL;
+}
+
+static const void *find_idx_check(const WeightArray *arrays, const char *name, int nb_in, int nb_out, int *total_blocks) {
+ int remain;
+ const int *idx;
+ const WeightArray *a = find_array_entry(arrays, name);
+ *total_blocks = 0;
+ if (a == NULL) return NULL;
+ idx = a->data;
+ remain = a->size/sizeof(int);
+ while (remain > 0) {
+ int nb_blocks;
+ int i;
+ nb_blocks = *idx++;
+ if (remain < nb_blocks+1) return NULL;
+ for (i=0;i<nb_blocks;i++) {
+ int pos = *idx++;
+ if (pos+3 >= nb_in || (pos&0x3)) return NULL;
+ }
+ nb_out -= 8;
+ remain -= nb_blocks+1;
+ *total_blocks += nb_blocks;
+ }
+ if (nb_out != 0) return NULL;
+ return a->data;
+}
+
+int linear_init(LinearLayer *layer, const WeightArray *arrays,
+ const char *bias,
+ const char *subias,
+ const char *weights,
+ const char *float_weights,
+ const char *weights_idx,
+ const char *diag,
+ const char *scale,
+ int nb_inputs,
+ int nb_outputs)
+{
+ int err;
+ layer->bias = NULL;
+ layer->subias = NULL;
+ layer->weights = NULL;
+ layer->float_weights = NULL;
+ layer->weights_idx = NULL;
+ layer->diag = NULL;
+ layer->scale = NULL;
+ if (bias != NULL) {
+ if ((layer->bias = find_array_check(arrays, bias, nb_outputs*sizeof(layer->bias[0]))) == NULL) return 1;
+ }
+ if (subias != NULL) {
+ if ((layer->subias = find_array_check(arrays, subias, nb_outputs*sizeof(layer->subias[0]))) == NULL) return 1;
+ }
+ if (weights_idx != NULL) {
+ int total_blocks;
+ if ((layer->weights_idx = find_idx_check(arrays, weights_idx, nb_inputs, nb_outputs, &total_blocks)) == NULL) return 1;
+ if (weights != NULL) {
+ if ((layer->weights = find_array_check(arrays, weights, SPARSE_BLOCK_SIZE*total_blocks*sizeof(layer->weights[0]))) == NULL) return 1;
+ }
+ if (float_weights != NULL) {
+ layer->float_weights = opt_array_check(arrays, float_weights, SPARSE_BLOCK_SIZE*total_blocks*sizeof(layer->float_weights[0]), &err);
+ if (err) return 1;
+ }
+ } else {
+ if (weights != NULL) {
+ if ((layer->weights = find_array_check(arrays, weights, nb_inputs*nb_outputs*sizeof(layer->weights[0]))) == NULL) return 1;
+ }
+ if (float_weights != NULL) {
+ layer->float_weights = opt_array_check(arrays, float_weights, nb_inputs*nb_outputs*sizeof(layer->float_weights[0]), &err);
+ if (err) return 1;
+ }
+ }
+ if (diag != NULL) {
+ if ((layer->diag = find_array_check(arrays, diag, nb_outputs*sizeof(layer->diag[0]))) == NULL) return 1;
+ }
+ if (weights != NULL) {
+ if ((layer->scale = find_array_check(arrays, scale, nb_outputs*sizeof(layer->scale[0]))) == NULL) return 1;
+ }
+ layer->nb_inputs = nb_inputs;
+ layer->nb_outputs = nb_outputs;
+ return 0;
+}
+
+int conv2d_init(Conv2dLayer *layer, const WeightArray *arrays,
+ const char *bias,
+ const char *float_weights,
+ int in_channels,
+ int out_channels,
+ int ktime,
+ int kheight)
+{
+ int err;
+ layer->bias = NULL;
+ layer->float_weights = NULL;
+ if (bias != NULL) {
+ if ((layer->bias = find_array_check(arrays, bias, out_channels*sizeof(layer->bias[0]))) == NULL) return 1;
+ }
+ if (float_weights != NULL) {
+ layer->float_weights = opt_array_check(arrays, float_weights, in_channels*out_channels*ktime*kheight*sizeof(layer->float_weights[0]), &err);
+ if (err) return 1;
+ }
+ layer->in_channels = in_channels;
+ layer->out_channels = out_channels;
+ layer->ktime = ktime;
+ layer->kheight = kheight;
+ return 0;
+}
+
+
+
+#if 0
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <stdio.h>
+
+int main()
+{
+ int fd;
+ void *data;
+ int len;
+ int nb_arrays;
+ int i;
+ WeightArray *list;
+ struct stat st;
+ const char *filename = "weights_blob.bin";
+ stat(filename, &st);
+ len = st.st_size;
+ fd = open(filename, O_RDONLY);
+ data = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0);
+ printf("size is %d\n", len);
+ nb_arrays = parse_weights(&list, data, len);
+ for (i=0;i<nb_arrays;i++) {
+ printf("found %s: size %d\n", list[i].name, list[i].size);
+ }
+ printf("%p\n", list[i].name);
+ opus_free(list);
+ munmap(data, len);
+ close(fd);
+ return 0;
+}
+#endif
diff --git a/dnn/pitchdnn.c b/dnn/pitchdnn.c
new file mode 100644
index 00000000..5cf96e7f
--- /dev/null
+++ b/dnn/pitchdnn.c
@@ -0,0 +1,79 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+#include "pitchdnn.h"
+#include "os_support.h"
+#include "nnet.h"
+#include "lpcnet_private.h"
+
+
+float compute_pitchdnn(
+ PitchDNNState *st,
+ const float *if_features,
+ const float *xcorr_features,
+ int arch
+ )
+{
+ float if1_out[DENSE_IF_UPSAMPLER_1_OUT_SIZE];
+ float downsampler_in[NB_XCORR_FEATURES + DENSE_IF_UPSAMPLER_2_OUT_SIZE];
+ float downsampler_out[DENSE_DOWNSAMPLER_OUT_SIZE];
+ float conv1_tmp1[(NB_XCORR_FEATURES + 2)*8] = {0};
+ float conv1_tmp2[(NB_XCORR_FEATURES + 2)*8] = {0};
+ float output[DENSE_FINAL_UPSAMPLER_OUT_SIZE];
+ int i;
+ int pos=0;
+ float maxval=-1;
+ float sum=0;
+ float count=0;
+ PitchDNN *model = &st->model;
+ /* IF */
+ compute_generic_dense(&model->dense_if_upsampler_1, if1_out, if_features, ACTIVATION_TANH, arch);
+ compute_generic_dense(&model->dense_if_upsampler_2, &downsampler_in[NB_XCORR_FEATURES], if1_out, ACTIVATION_TANH, arch);
+ /* xcorr*/
+ OPUS_COPY(&conv1_tmp1[1], xcorr_features, NB_XCORR_FEATURES);
+ compute_conv2d(&model->conv2d_1, &conv1_tmp2[1], st->xcorr_mem1, conv1_tmp1, NB_XCORR_FEATURES, NB_XCORR_FEATURES+2, ACTIVATION_TANH, arch);
+ compute_conv2d(&model->conv2d_2, downsampler_in, st->xcorr_mem2, conv1_tmp2, NB_XCORR_FEATURES, NB_XCORR_FEATURES, ACTIVATION_TANH, arch);
+
+ compute_generic_dense(&model->dense_downsampler, downsampler_out, downsampler_in, ACTIVATION_TANH, arch);
+ compute_generic_gru(&model->gru_1_input, &model->gru_1_recurrent, st->gru_state, downsampler_out, arch);
+ compute_generic_dense(&model->dense_final_upsampler, output, st->gru_state, ACTIVATION_LINEAR, arch);
+ for (i=0;i<180;i++) {
+ if (output[i] > maxval) {
+ pos = i;
+ maxval = output[i];
+ }
+ }
+ for (i=IMAX(0, pos-2); i<=IMIN(179, pos+2); i++) {
+ float p = exp(output[i]);
+ sum += p*i;
+ count += p;
+ }
+ /*printf("%d %f\n", pos, sum/count);*/
+ return (1.f/60.f)*(sum/count) - 1.5;
+ /*return 256.f/pow(2.f, (1.f/60.f)*i);*/
+}
+
+
+void pitchdnn_init(PitchDNNState *st)
+{
+ int ret;
+ OPUS_CLEAR(st, 1);
+#ifndef USE_WEIGHTS_FILE
+ ret = init_pitchdnn(&st->model, pitchdnn_arrays);
+#else
+ ret = 0;
+#endif
+ celt_assert(ret == 0);
+}
+
+int pitchdnn_load_model(PitchDNNState *st, const void *data, int len) {
+ WeightArray *list;
+ int ret;
+ parse_weights(&list, data, len);
+ ret = init_pitchdnn(&st->model, list);
+ opus_free(list);
+ if (ret == 0) return 0;
+ else return -1;
+}
diff --git a/dnn/pitchdnn.h b/dnn/pitchdnn.h
new file mode 100644
index 00000000..25fa3a4b
--- /dev/null
+++ b/dnn/pitchdnn.h
@@ -0,0 +1,34 @@
+#ifndef PITCHDNN_H
+#define PITCHDNN_H
+
+
+typedef struct PitchDNN PitchDNN;
+
+#include "pitchdnn_data.h"
+
+#define PITCH_MIN_PERIOD 32
+#define PITCH_MAX_PERIOD 256
+
+#define NB_XCORR_FEATURES (PITCH_MAX_PERIOD-PITCH_MIN_PERIOD)
+
+
+typedef struct {
+ PitchDNN model;
+ float gru_state[GRU_1_STATE_SIZE];
+ float xcorr_mem1[(NB_XCORR_FEATURES + 2)*2];
+ float xcorr_mem2[(NB_XCORR_FEATURES + 2)*2*8];
+ float xcorr_mem3[(NB_XCORR_FEATURES + 2)*2*8];
+} PitchDNNState;
+
+
+void pitchdnn_init(PitchDNNState *st);
+int pitchdnn_load_model(PitchDNNState *st, const void *data, int len);
+
+float compute_pitchdnn(
+ PitchDNNState *st,
+ const float *if_features,
+ const float *xcorr_features,
+ int arch
+ );
+
+#endif
diff --git a/dnn/tansig_table.h b/dnn/tansig_table.h
new file mode 100644
index 00000000..ebec7e3a
--- /dev/null
+++ b/dnn/tansig_table.h
@@ -0,0 +1,50 @@
+/* This file is auto-generated by gen_tables */
+
+#ifndef TANSIG_TABLE_H
+#define TANSIG_TABLE_H
+
+static const float tansig_table[201] = {
+0.000000f, 0.039979f, 0.079830f, 0.119427f, 0.158649f,
+0.197375f, 0.235496f, 0.272905f, 0.309507f, 0.345214f,
+0.379949f, 0.413644f, 0.446244f, 0.477700f, 0.507977f,
+0.537050f, 0.564900f, 0.591519f, 0.616909f, 0.641077f,
+0.664037f, 0.685809f, 0.706419f, 0.725897f, 0.744277f,
+0.761594f, 0.777888f, 0.793199f, 0.807569f, 0.821040f,
+0.833655f, 0.845456f, 0.856485f, 0.866784f, 0.876393f,
+0.885352f, 0.893698f, 0.901468f, 0.908698f, 0.915420f,
+0.921669f, 0.927473f, 0.932862f, 0.937863f, 0.942503f,
+0.946806f, 0.950795f, 0.954492f, 0.957917f, 0.961090f,
+0.964028f, 0.966747f, 0.969265f, 0.971594f, 0.973749f,
+0.975743f, 0.977587f, 0.979293f, 0.980869f, 0.982327f,
+0.983675f, 0.984921f, 0.986072f, 0.987136f, 0.988119f,
+0.989027f, 0.989867f, 0.990642f, 0.991359f, 0.992020f,
+0.992631f, 0.993196f, 0.993718f, 0.994199f, 0.994644f,
+0.995055f, 0.995434f, 0.995784f, 0.996108f, 0.996407f,
+0.996682f, 0.996937f, 0.997172f, 0.997389f, 0.997590f,
+0.997775f, 0.997946f, 0.998104f, 0.998249f, 0.998384f,
+0.998508f, 0.998623f, 0.998728f, 0.998826f, 0.998916f,
+0.999000f, 0.999076f, 0.999147f, 0.999213f, 0.999273f,
+0.999329f, 0.999381f, 0.999428f, 0.999472f, 0.999513f,
+0.999550f, 0.999585f, 0.999617f, 0.999646f, 0.999673f,
+0.999699f, 0.999722f, 0.999743f, 0.999763f, 0.999781f,
+0.999798f, 0.999813f, 0.999828f, 0.999841f, 0.999853f,
+0.999865f, 0.999875f, 0.999885f, 0.999893f, 0.999902f,
+0.999909f, 0.999916f, 0.999923f, 0.999929f, 0.999934f,
+0.999939f, 0.999944f, 0.999948f, 0.999952f, 0.999956f,
+0.999959f, 0.999962f, 0.999965f, 0.999968f, 0.999970f,
+0.999973f, 0.999975f, 0.999977f, 0.999978f, 0.999980f,
+0.999982f, 0.999983f, 0.999984f, 0.999986f, 0.999987f,
+0.999988f, 0.999989f, 0.999990f, 0.999990f, 0.999991f,
+0.999992f, 0.999992f, 0.999993f, 0.999994f, 0.999994f,
+0.999994f, 0.999995f, 0.999995f, 0.999996f, 0.999996f,
+0.999996f, 0.999997f, 0.999997f, 0.999997f, 0.999997f,
+0.999997f, 0.999998f, 0.999998f, 0.999998f, 0.999998f,
+0.999998f, 0.999998f, 0.999999f, 0.999999f, 0.999999f,
+0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f,
+0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f,
+1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f,
+1.000000f, 1.000000f, 1.000000f, 1.000000f, 1.000000f,
+1.000000f,
+};
+
+#endif /*TANSIG_TABLE_H*/
diff --git a/dnn/test_vec.c b/dnn/test_vec.c
new file mode 100644
index 00000000..d14d2502
--- /dev/null
+++ b/dnn/test_vec.c
@@ -0,0 +1,128 @@
+#include <stdio.h>
+#include <math.h>
+#include "opus_types.h"
+#include "arch.h"
+#include "common.h"
+#include "tansig_table.h"
+
+#define LPCNET_TEST
+
+// we need to call two versions of each functions that have the same
+// name, so use #defines to temp rename them
+
+#define lpcnet_exp2 lpcnet_exp2_fast
+#define tansig_approx tansig_approx_fast
+#define sigmoid_approx sigmoid_approx_fast
+#define softmax softmax_fast
+#define vec_tanh vec_tanh_fast
+#define vec_sigmoid vec_sigmoid_fast
+#define sgemv_accum16 sgemv_accum16_fast
+#define sparse_sgemv_accum16 sparse_sgemv_accum16_fast
+
+#ifdef __AVX__
+#include "vec_avx.h"
+#ifdef __AVX2__
+const char simd[]="AVX2";
+#else
+const char simd[]="AVX";
+#endif
+#elif __ARM_NEON__
+#include "vec_neon.h"
+const char simd[]="NEON";
+#else
+const char simd[]="none";
+
+#endif
+
+#undef lpcnet_exp2
+#undef tansig_approx
+#undef sigmoid_approx
+#undef softmax
+#undef vec_tanh
+#undef vec_sigmoid
+#undef sgemv_accum16
+#undef sparse_sgemv_accum16
+#include "vec.h"
+
+#define ROW_STEP 16
+#define ROWS ROW_STEP*10
+#define COLS 2
+#define ENTRIES 2
+
+int test_sgemv_accum16() {
+ float weights[ROWS*COLS];
+ float x[COLS];
+ float out[ROWS], out_fast[ROWS];
+ int i;
+
+ printf("sgemv_accum16.....................: ");
+ for(i=0; i<ROWS*COLS; i++) {
+ weights[i] = i;
+ }
+ for(i=0; i<ROWS; i++) {
+ out[i] = 0;
+ out_fast[i] = 0;
+ }
+
+ for(i=0; i<COLS; i++) {
+ x[i] = i+1;
+ }
+
+ sgemv_accum16(out, weights, ROWS, COLS, 1, x);
+ sgemv_accum16_fast(out_fast, weights, ROWS, COLS, 1, x);
+
+ for(i=0; i<ROWS; i++) {
+ if (out[i] != out_fast[i]) {
+ printf("fail\n");
+ for(i=0; i<ROWS; i++) {
+ printf("%d %f %f\n", i, out[i], out_fast[i]);
+ if (out[i] != out_fast[i])
+ return 1;
+ }
+ }
+ }
+
+ printf("pass\n");
+ return 0;
+}
+
+
+int test_sparse_sgemv_accum16() {
+ int rows = ROW_STEP*ENTRIES;
+ int indx[] = {1,0,2,0,1};
+ float w[ROW_STEP*(1+2)];
+ float x[ENTRIES] = {1,2};
+ float out[ROW_STEP*(1+2)], out_fast[ROW_STEP*(1+2)];
+ int i;
+
+ printf("sparse_sgemv_accum16..............: ");
+ for(i=0; i<ROW_STEP*(1+2); i++) {
+ w[i] = i;
+ out[i] = 0;
+ out_fast[i] = 0;
+ }
+
+ sparse_sgemv_accum16(out, w, rows, indx, x);
+ sparse_sgemv_accum16_fast(out_fast, w, rows, indx, x);
+
+ for(i=0; i<ROW_STEP*ENTRIES; i++) {
+ if (out[i] != out_fast[i]) {
+ printf("fail\n");
+ for(i=0; i<ROW_STEP*ENTRIES; i++) {
+ printf("%d %f %f\n", i, out[i], out_fast[i]);
+ if (out[i] != out_fast[i])
+ return 1;
+ }
+ }
+ }
+
+ printf("pass\n");
+ return 0;
+}
+
+int main() {
+ printf("testing vector routines on SIMD: %s\n", simd);
+ int test1 = test_sgemv_accum16();
+ int test2 = test_sparse_sgemv_accum16();
+ return test1 || test2;
+}
diff --git a/dnn/torch/dnntools/dnntools/__init__.py b/dnn/torch/dnntools/dnntools/__init__.py
new file mode 100644
index 00000000..117597ab
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/__init__.py
@@ -0,0 +1,2 @@
+from . import quantization
+from . import sparsification \ No newline at end of file
diff --git a/dnn/torch/dnntools/dnntools/quantization/__init__.py b/dnn/torch/dnntools/dnntools/quantization/__init__.py
new file mode 100644
index 00000000..3b46a2e0
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/quantization/__init__.py
@@ -0,0 +1 @@
+from .softquant import soft_quant, remove_soft_quant \ No newline at end of file
diff --git a/dnn/torch/dnntools/dnntools/quantization/softquant.py b/dnn/torch/dnntools/dnntools/quantization/softquant.py
new file mode 100644
index 00000000..877c6450
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/quantization/softquant.py
@@ -0,0 +1,113 @@
+import torch
+
+@torch.no_grad()
+def compute_optimal_scale(weight):
+ with torch.no_grad():
+ n_out, n_in = weight.shape
+ assert n_in % 4 == 0
+ if n_out % 8:
+ # add padding
+ pad = n_out - n_out % 8
+ weight = torch.cat((weight, torch.zeros((pad, n_in), dtype=weight.dtype, device=weight.device)), dim=0)
+
+ weight_max_abs, _ = torch.max(torch.abs(weight), dim=1)
+ weight_max_sum, _ = torch.max(torch.abs(weight[:, : n_in : 2] + weight[:, 1 : n_in : 2]), dim=1)
+ scale_max = weight_max_abs / 127
+ scale_sum = weight_max_sum / 129
+
+ scale = torch.maximum(scale_max, scale_sum)
+
+ return scale[:n_out]
+
+@torch.no_grad()
+def q_scaled_noise(module, weight):
+ if isinstance(module, torch.nn.Conv1d):
+ w = weight.permute(0, 2, 1).flatten(1)
+ noise = torch.rand_like(w) - 0.5
+ noise[w == 0] = 0 # ignore zero entries from sparsification
+ scale = compute_optimal_scale(w)
+ noise = noise * scale.unsqueeze(-1)
+ noise = noise.reshape(weight.size(0), weight.size(2), weight.size(1)).permute(0, 2, 1)
+ elif isinstance(module, torch.nn.ConvTranspose1d):
+ i, o, k = weight.shape
+ w = weight.permute(2, 1, 0).reshape(k * o, i)
+ noise = torch.rand_like(w) - 0.5
+ noise[w == 0] = 0 # ignore zero entries from sparsification
+ scale = compute_optimal_scale(w)
+ noise = noise * scale.unsqueeze(-1)
+ noise = noise.reshape(k, o, i).permute(2, 1, 0)
+ elif len(weight.shape) == 2:
+ noise = torch.rand_like(weight) - 0.5
+ noise[weight == 0] = 0 # ignore zero entries from sparsification
+ scale = compute_optimal_scale(weight)
+ noise = noise * scale.unsqueeze(-1)
+ else:
+ raise ValueError('unknown quantization setting')
+
+ return noise
+
+class SoftQuant:
+ name: str
+
+ def __init__(self, names: str, scale: float) -> None:
+ self.names = names
+ self.quantization_noise = None
+ self.scale = scale
+
+ def __call__(self, module, inputs, *args, before=True):
+ if not module.training: return
+
+ if before:
+ self.quantization_noise = dict()
+ for name in self.names:
+ weight = getattr(module, name)
+ if self.scale is None:
+ self.quantization_noise[name] = q_scaled_noise(module, weight)
+ else:
+ self.quantization_noise[name] = \
+ self.scale * (torch.rand_like(weight) - 0.5)
+ with torch.no_grad():
+ weight.data[:] = weight + self.quantization_noise[name]
+ else:
+ for name in self.names:
+ weight = getattr(module, name)
+ with torch.no_grad():
+ weight.data[:] = weight - self.quantization_noise[name]
+ self.quantization_noise = None
+
+ def apply(module, names=['weight'], scale=None):
+ fn = SoftQuant(names, scale)
+
+ for name in names:
+ if not hasattr(module, name):
+ raise ValueError("")
+
+ fn_before = lambda *x : fn(*x, before=True)
+ fn_after = lambda *x : fn(*x, before=False)
+ setattr(fn_before, 'sqm', fn)
+ setattr(fn_after, 'sqm', fn)
+
+
+ module.register_forward_pre_hook(fn_before)
+ module.register_forward_hook(fn_after)
+
+ module
+
+ return fn
+
+
+def soft_quant(module, names=['weight'], scale=None):
+ fn = SoftQuant.apply(module, names, scale)
+ return module
+
+def remove_soft_quant(module, names=['weight']):
+ for k, hook in module._forward_pre_hooks.items():
+ if hasattr(hook, 'sqm'):
+ if isinstance(hook.sqm, SoftQuant) and hook.sqm.names == names:
+ del module._forward_pre_hooks[k]
+ for k, hook in module._forward_hooks.items():
+ if hasattr(hook, 'sqm'):
+ if isinstance(hook.sqm, SoftQuant) and hook.sqm.names == names:
+ del module._forward_hooks[k]
+
+ return module \ No newline at end of file
diff --git a/dnn/torch/dnntools/dnntools/relegance/__init__.py b/dnn/torch/dnntools/dnntools/relegance/__init__.py
new file mode 100644
index 00000000..cee0143b
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/relegance/__init__.py
@@ -0,0 +1,2 @@
+from .relegance import relegance_gradient_weighting, relegance_create_tconv_kernel, relegance_map_relevance_to_input_domain, relegance_resize_relevance_to_input_size
+from .meta_critic import MetaCritic \ No newline at end of file
diff --git a/dnn/torch/dnntools/dnntools/relegance/meta_critic.py b/dnn/torch/dnntools/dnntools/relegance/meta_critic.py
new file mode 100644
index 00000000..1af0f8ff
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/relegance/meta_critic.py
@@ -0,0 +1,85 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+
+class MetaCritic():
+ def __init__(self, normalize=False, gamma=0.9, beta=0.0, joint_stats=False):
+ """ Class for assessing relevance of discriminator scores
+
+ Args:
+ gamma (float, optional): update rate for tracking discriminator stats. Defaults to 0.9.
+ beta (float, optional): Miminum confidence related threshold. Defaults to 0.0.
+ """
+ self.normalize = normalize
+ self.gamma = gamma
+ self.beta = beta
+ self.joint_stats = joint_stats
+
+ self.disc_stats = dict()
+
+ def __call__(self, disc_id, real_scores, generated_scores):
+ """ calculates relevance from normalized scores
+
+ Args:
+ disc_id (any valid key): id for tracking discriminator statistics
+ real_scores (torch.tensor): scores for real data
+ generated_scores (torch.tensor): scores for generated data; expecting device to match real_scores.device
+
+ Returns:
+ torch.tensor: output-domain relevance
+ """
+
+ if self.normalize:
+ real_std = torch.std(real_scores.detach()).cpu().item()
+ gen_std = torch.std(generated_scores.detach()).cpu().item()
+ std = (real_std**2 + gen_std**2) ** .5
+ mean = torch.mean(real_scores.detach()).cpu().item() - torch.mean(generated_scores.detach()).cpu().item()
+
+ key = 0 if self.joint_stats else disc_id
+
+ if key in self.disc_stats:
+ self.disc_stats[key]['std'] = self.gamma * self.disc_stats[key]['std'] + (1 - self.gamma) * std
+ self.disc_stats[key]['mean'] = self.gamma * self.disc_stats[key]['mean'] + (1 - self.gamma) * mean
+ else:
+ self.disc_stats[key] = {
+ 'std': std + 1e-5,
+ 'mean': mean
+ }
+
+ std = self.disc_stats[key]['std']
+ mean = self.disc_stats[key]['mean']
+ else:
+ mean, std = 0, 1
+
+ relevance = torch.relu((real_scores - generated_scores - mean) / std + mean - self.beta)
+
+ if False: print(f"relevance({disc_id}): {relevance.min()=} {relevance.max()=} {relevance.mean()=}")
+
+ return relevance \ No newline at end of file
diff --git a/dnn/torch/dnntools/dnntools/relegance/relegance.py b/dnn/torch/dnntools/dnntools/relegance/relegance.py
new file mode 100644
index 00000000..29c5be23
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/relegance/relegance.py
@@ -0,0 +1,449 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+import torch.nn.functional as F
+
+
+def view_one_hot(index, length):
+ vec = length * [1]
+ vec[index] = -1
+ return vec
+
+def create_smoothing_kernel(widths, gamma=1.5):
+ """ creates a truncated gaussian smoothing kernel for the given widths
+
+ Parameters:
+ -----------
+ widths: list[Int] or torch.LongTensor
+ specifies the shape of the smoothing kernel, entries must be > 0.
+
+ gamma: float, optional
+ decay factor for gaussian relative to kernel size
+
+ Returns:
+ --------
+ kernel: torch.FloatTensor
+ """
+
+ widths = torch.LongTensor(widths)
+ num_dims = len(widths)
+
+ assert(widths.min() > 0)
+
+ centers = widths.float() / 2 - 0.5
+ sigmas = gamma * (centers + 1)
+
+ vals = []
+
+ vals= [((torch.arange(widths[i]) - centers[i]) / sigmas[i]) ** 2 for i in range(num_dims)]
+ vals = sum([vals[i].view(view_one_hot(i, num_dims)) for i in range(num_dims)])
+
+ kernel = torch.exp(- vals)
+ kernel = kernel / kernel.sum()
+
+ return kernel
+
+
+def create_partition_kernel(widths, strides):
+ """ creates a partition kernel for mapping a convolutional network output back to the input domain
+
+ Given a fully convolutional network with receptive field of shape widths and the given strides, this
+ function construncts an intorpolation kernel whose tranlations by multiples of the given strides form
+ a partition of one on the input domain.
+
+ Parameter:
+ ----------
+ widths: list[Int] or torch.LongTensor
+ shape of receptive field
+
+ strides: list[Int] or torch.LongTensor
+ total strides of convolutional network
+
+ Returns:
+ kernel: torch.FloatTensor
+ """
+
+ num_dims = len(widths)
+ assert num_dims == len(strides) and num_dims in {1, 2, 3}
+
+ convs = {1 : F.conv1d, 2 : F.conv2d, 3 : F.conv3d}
+
+ widths = torch.LongTensor(widths)
+ strides = torch.LongTensor(strides)
+
+ proto_kernel = torch.ones(torch.minimum(strides, widths).tolist())
+
+ # create interpolation kernel eta
+ eta_widths = widths - strides + 1
+ if eta_widths.min() <= 0:
+ print("[create_partition_kernel] warning: receptive field does not cover input domain")
+ eta_widths = torch.maximum(eta_widths, torch.ones_like(eta_widths))
+
+
+ eta = create_smoothing_kernel(eta_widths).view(1, 1, *eta_widths.tolist())
+
+ padding = torch.repeat_interleave(eta_widths - 1, 2, 0).tolist()[::-1] # ordering of dimensions for padding and convolution functions is reversed in torch
+ padded_proto_kernel = F.pad(proto_kernel, padding)
+ padded_proto_kernel = padded_proto_kernel.view(1, 1, *padded_proto_kernel.shape)
+ kernel = convs[num_dims](padded_proto_kernel, eta)
+
+ return kernel
+
+
+def receptive_field(conv_model, input_shape, output_position):
+ """ estimates boundaries of receptive field connected to output_position via autograd
+
+ Parameters:
+ -----------
+ conv_model: nn.Module or autograd function
+ function or model implementing fully convolutional model
+
+ input_shape: List[Int]
+ input shape ignoring batch dimension, i.e. [num_channels, dim1, dim2, ...]
+
+ output_position: List[Int]
+ output position for which the receptive field is determined; the function raises an exception
+ if output_position is out of bounds for the given input_shape.
+
+ Returns:
+ --------
+ low: List[Int]
+ start indices of receptive field
+
+ high: List[Int]
+ stop indices of receptive field
+
+ """
+
+ x = torch.randn((1,) + tuple(input_shape), requires_grad=True)
+ y = conv_model(x)
+
+ # collapse channels and remove batch dimension
+ y = torch.sum(y, 1)[0]
+
+ # create mask
+ mask = torch.zeros_like(y)
+ index = [torch.tensor(i) for i in output_position]
+ try:
+ mask.index_put_(index, torch.tensor(1, dtype=mask.dtype))
+ except IndexError:
+ raise ValueError('output_position out of bounds')
+
+ (mask * y).sum().backward()
+
+ # sum over channels and remove batch dimension
+ grad = torch.sum(x.grad, dim=1)[0]
+ tmp = torch.nonzero(grad, as_tuple=True)
+ low = [t.min().item() for t in tmp]
+ high = [t.max().item() for t in tmp]
+
+ return low, high
+
+def estimate_conv_parameters(model, num_channels, num_dims, width, max_stride=10):
+ """ attempts to estimate receptive field size, strides and left paddings for given model
+
+
+ Parameters:
+ -----------
+ model: nn.Module or autograd function
+ fully convolutional model for which parameters are estimated
+
+ num_channels: Int
+ number of input channels for model
+
+ num_dims: Int
+ number of input dimensions for model (without channel dimension)
+
+ width: Int
+ width of the input tensor (a hyper-square) on which the receptive fields are derived via autograd
+
+ max_stride: Int, optional
+ assumed maximal stride of the model for any dimension, when set too low the function may fail for
+ any value of width
+
+ Returns:
+ --------
+ receptive_field_size: List[Int]
+ receptive field size in all dimension
+
+ strides: List[Int]
+ stride in all dimensions
+
+ left_paddings: List[Int]
+ left padding in all dimensions; this is relevant for aligning the receptive field on the input plane
+
+ Raises:
+ -------
+ ValueError, KeyError
+
+ """
+
+ input_shape = [num_channels] + num_dims * [width]
+ output_position1 = num_dims * [width // (2 * max_stride)]
+ output_position2 = num_dims * [width // (2 * max_stride) + 1]
+
+ low1, high1 = receptive_field(model, input_shape, output_position1)
+ low2, high2 = receptive_field(model, input_shape, output_position2)
+
+ widths1 = [h - l + 1 for l, h in zip(low1, high1)]
+ widths2 = [h - l + 1 for l, h in zip(low2, high2)]
+
+ if not all([w1 - w2 == 0 for w1, w2 in zip(widths1, widths2)]) or not all([l1 != l2 for l1, l2 in zip(low1, low2)]):
+ raise ValueError("[estimate_strides]: widths to small to determine strides")
+
+ receptive_field_size = widths1
+ strides = [l2 - l1 for l1, l2 in zip(low1, low2)]
+ left_paddings = [s * p - l for l, s, p in zip(low1, strides, output_position1)]
+
+ return receptive_field_size, strides, left_paddings
+
+def inspect_conv_model(model, num_channels, num_dims, max_width=10000, width_hint=None, stride_hint=None, verbose=False):
+ """ determines size of receptive field, strides and padding probabilistically
+
+
+ Parameters:
+ -----------
+ model: nn.Module or autograd function
+ fully convolutional model for which parameters are estimated
+
+ num_channels: Int
+ number of input channels for model
+
+ num_dims: Int
+ number of input dimensions for model (without channel dimension)
+
+ max_width: Int
+ maximum width of the input tensor (a hyper-square) on which the receptive fields are derived via autograd
+
+ verbose: bool, optional
+ if true, the function prints parameters for individual trials
+
+ Returns:
+ --------
+ receptive_field_size: List[Int]
+ receptive field size in all dimension
+
+ strides: List[Int]
+ stride in all dimensions
+
+ left_paddings: List[Int]
+ left padding in all dimensions; this is relevant for aligning the receptive field on the input plane
+
+ Raises:
+ -------
+ ValueError
+
+ """
+
+ max_stride = max_width // 2
+ stride = max_stride // 100
+ width = max_width // 100
+
+ if width_hint is not None: width = 2 * width_hint
+ if stride_hint is not None: stride = stride_hint
+
+ did_it = False
+ while width < max_width and stride < max_stride:
+ try:
+ if verbose: print(f"[inspect_conv_model] trying parameters {width=}, {stride=}")
+ receptive_field_size, strides, left_paddings = estimate_conv_parameters(model, num_channels, num_dims, width, stride)
+ did_it = True
+ except:
+ pass
+
+ if did_it: break
+
+ width *= 2
+ if width >= max_width and stride < max_stride:
+ stride *= 2
+ width = 2 * stride
+
+ if not did_it:
+ raise ValueError(f'could not determine conv parameter with given max_width={max_width}')
+
+ return receptive_field_size, strides, left_paddings
+
+
+class GradWeight(torch.autograd.Function):
+ def __init__(self):
+ super().__init__()
+
+ @staticmethod
+ def forward(ctx, x, weight):
+ ctx.save_for_backward(weight)
+ return x.clone()
+
+ @staticmethod
+ def backward(ctx, grad_output):
+ weight, = ctx.saved_tensors
+
+ grad_input = grad_output * weight
+
+ return grad_input, None
+
+
+# API
+
+def relegance_gradient_weighting(x, weight):
+ """
+
+ Args:
+ x (torch.tensor): input tensor
+ weight (torch.tensor or None): weight tensor for gradients of x; if None, no gradient weighting will be applied in backward pass
+
+ Returns:
+ torch.tensor: the unmodified input tensor x
+
+ Raises:
+ RuntimeError: if estimation of parameters fails due to exceeded compute budget
+ """
+ if weight is None:
+ return x
+ else:
+ return GradWeight.apply(x, weight)
+
+
+
+def relegance_create_tconv_kernel(model, num_channels, num_dims, width_hint=None, stride_hint=None, verbose=False):
+ """ creates parameters for mapping back output domain relevance to input tomain
+
+ Args:
+ model (nn.Module or autograd.Function): fully convolutional model
+ num_channels (int): number of input channels to model
+ num_dims (int): number of input dimensions of model (without channel and batch dimension)
+ width_hint(int or None): optional hint at maximal width of receptive field
+ stride_hint(int or None): optional hint at maximal stride
+
+ Returns:
+ dict: contains kernel, kernel dimensions, strides and left paddings for transposed convolution
+ """
+
+ max_width = int(100000 / (10 ** num_dims))
+
+ did_it = False
+ try:
+ receptive_field_size, strides, left_paddings = inspect_conv_model(model, num_channels, num_dims, max_width=max_width, width_hint=width_hint, stride_hint=stride_hint, verbose=verbose)
+ did_it = True
+ except:
+ # try once again with larger max_width
+ max_width *= 10
+
+ # crash if exception is raised
+ try:
+ if not did_it: receptive_field_size, strides, left_paddings = inspect_conv_model(model, num_channels, num_dims, max_width=max_width, width_hint=width_hint, stride_hint=stride_hint, verbose=verbose)
+ except:
+ raise RuntimeError("could not determine parameters within given compute budget")
+
+ partition_kernel = create_partition_kernel(receptive_field_size, strides)
+ partition_kernel = torch.repeat_interleave(partition_kernel, num_channels, 1)
+
+ tconv_parameters = {
+ 'kernel': partition_kernel,
+ 'receptive_field_shape': receptive_field_size,
+ 'stride': strides,
+ 'left_padding': left_paddings,
+ 'num_dims': num_dims
+ }
+
+ return tconv_parameters
+
+
+
+def relegance_map_relevance_to_input_domain(od_relevance, tconv_parameters):
+ """ maps output-domain relevance to input-domain relevance via transpose convolution
+
+ Args:
+ od_relevance (torch.tensor): output-domain relevance
+ tconv_parameters (dict): parameter dict as created by relegance_create_tconv_kernel
+
+ Returns:
+ torch.tensor: input-domain relevance. The tensor is left aligned, i.e. the all-zero index of the output corresponds to the all-zero index of the discriminator input.
+ Otherwise, the size of the output tensor does not need to match the size of the discriminator input. Use relegance_resize_relevance_to_input_size for a
+ convenient way to adjust the output to the correct size.
+
+ Raises:
+ ValueError: if number of dimensions is not supported
+ """
+
+ kernel = tconv_parameters['kernel'].to(od_relevance.device)
+ rf_shape = tconv_parameters['receptive_field_shape']
+ stride = tconv_parameters['stride']
+ left_padding = tconv_parameters['left_padding']
+
+ num_dims = len(kernel.shape) - 2
+
+ # repeat boundary values
+ od_padding = [rf_shape[i//2] // stride[i//2] + 1 for i in range(2 * num_dims)]
+ padded_od_relevance = F.pad(od_relevance, od_padding[::-1], mode='replicate')
+ od_padding = od_padding[::2]
+
+ # apply mapping and left trimming
+ if num_dims == 1:
+ id_relevance = F.conv_transpose1d(padded_od_relevance, kernel, stride=stride)
+ id_relevance = id_relevance[..., left_padding[0] + stride[0] * od_padding[0] :]
+ elif num_dims == 2:
+ id_relevance = F.conv_transpose2d(padded_od_relevance, kernel, stride=stride)
+ id_relevance = id_relevance[..., left_padding[0] + stride[0] * od_padding[0] :, left_padding[1] + stride[1] * od_padding[1]:]
+ elif num_dims == 3:
+ id_relevance = F.conv_transpose2d(padded_od_relevance, kernel, stride=stride)
+ id_relevance = id_relevance[..., left_padding[0] + stride[0] * od_padding[0] :, left_padding[1] + stride[1] * od_padding[1]:, left_padding[2] + stride[2] * od_padding[2] :]
+ else:
+ raise ValueError(f'[relegance_map_to_input_domain] error: num_dims = {num_dims} not supported')
+
+ return id_relevance
+
+
+def relegance_resize_relevance_to_input_size(reference_input, relevance):
+ """ adjusts size of relevance tensor to reference input size
+
+ Args:
+ reference_input (torch.tensor): discriminator input tensor for reference
+ relevance (torch.tensor): input-domain relevance corresponding to input tensor reference_input
+
+ Returns:
+ torch.tensor: resized relevance
+
+ Raises:
+ ValueError: if number of dimensions is not supported
+ """
+ resized_relevance = torch.zeros_like(reference_input)
+
+ num_dims = len(reference_input.shape) - 2
+ with torch.no_grad():
+ if num_dims == 1:
+ resized_relevance[:] = relevance[..., : min(reference_input.size(-1), relevance.size(-1))]
+ elif num_dims == 2:
+ resized_relevance[:] = relevance[..., : min(reference_input.size(-2), relevance.size(-2)), : min(reference_input.size(-1), relevance.size(-1))]
+ elif num_dims == 3:
+ resized_relevance[:] = relevance[..., : min(reference_input.size(-3), relevance.size(-3)), : min(reference_input.size(-2), relevance.size(-2)), : min(reference_input.size(-1), relevance.size(-1))]
+ else:
+ raise ValueError(f'[relegance_map_to_input_domain] error: num_dims = {num_dims} not supported')
+
+ return resized_relevance \ No newline at end of file
diff --git a/dnn/torch/dnntools/dnntools/sparsification/__init__.py b/dnn/torch/dnntools/dnntools/sparsification/__init__.py
new file mode 100644
index 00000000..fcc91746
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/sparsification/__init__.py
@@ -0,0 +1,6 @@
+from .gru_sparsifier import GRUSparsifier
+from .conv1d_sparsifier import Conv1dSparsifier
+from .conv_transpose1d_sparsifier import ConvTranspose1dSparsifier
+from .linear_sparsifier import LinearSparsifier
+from .common import sparsify_matrix, calculate_gru_flops_per_step
+from .utils import mark_for_sparsification, create_sparsifier \ No newline at end of file
diff --git a/dnn/torch/dnntools/dnntools/sparsification/base_sparsifier.py b/dnn/torch/dnntools/dnntools/sparsification/base_sparsifier.py
new file mode 100644
index 00000000..dd62f40b
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/sparsification/base_sparsifier.py
@@ -0,0 +1,58 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+class BaseSparsifier:
+ def __init__(self, task_list, start, stop, interval, exponent=3):
+
+ # just copying parameters...
+ self.start = start
+ self.stop = stop
+ self.interval = interval
+ self.exponent = exponent
+ self.task_list = task_list
+
+ # ... and setting counter to 0
+ self.step_counter = 0
+
+ def step(self, verbose=False):
+ # compute current interpolation factor
+ self.step_counter += 1
+
+ if self.step_counter < self.start:
+ return
+ elif self.step_counter < self.stop:
+ # update only every self.interval-th interval
+ if self.step_counter % self.interval:
+ return
+
+ alpha = ((self.stop - self.step_counter) / (self.stop - self.start)) ** self.exponent
+ else:
+ alpha = 0
+
+ self.sparsify(alpha, verbose=verbose) \ No newline at end of file
diff --git a/dnn/torch/dnntools/dnntools/sparsification/common.py b/dnn/torch/dnntools/dnntools/sparsification/common.py
new file mode 100644
index 00000000..47181800
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/sparsification/common.py
@@ -0,0 +1,123 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+
+debug=True
+
+def sparsify_matrix(matrix : torch.tensor, density : float, block_size, keep_diagonal : bool=False, return_mask : bool=False):
+ """ sparsifies matrix with specified block size
+
+ Parameters:
+ -----------
+ matrix : torch.tensor
+ matrix to sparsify
+ density : int
+ target density
+ block_size : [int, int]
+ block size dimensions
+ keep_diagonal : bool
+ If true, the diagonal will be kept. This option requires block_size[0] == block_size[1] and defaults to False
+ """
+
+ m, n = matrix.shape
+ m1, n1 = block_size
+
+ if m % m1 or n % n1:
+ raise ValueError(f"block size {(m1, n1)} does not divide matrix size {(m, n)}")
+
+ # extract diagonal if keep_diagonal = True
+ if keep_diagonal:
+ if m != n:
+ raise ValueError("Attempting to sparsify non-square matrix with keep_diagonal=True")
+
+ to_spare = torch.diag(torch.diag(matrix))
+ matrix = matrix - to_spare
+ else:
+ to_spare = torch.zeros_like(matrix)
+
+ # calculate energy in sub-blocks
+ x = torch.reshape(matrix, (m // m1, m1, n // n1, n1))
+ x = x ** 2
+ block_energies = torch.sum(torch.sum(x, dim=3), dim=1)
+
+ number_of_blocks = (m * n) // (m1 * n1)
+ number_of_survivors = round(number_of_blocks * density)
+
+ # masking threshold
+ if number_of_survivors == 0:
+ threshold = 0
+ else:
+ threshold = torch.sort(torch.flatten(block_energies)).values[-number_of_survivors]
+
+ # create mask
+ mask = torch.ones_like(block_energies)
+ mask[block_energies < threshold] = 0
+ mask = torch.repeat_interleave(mask, m1, dim=0)
+ mask = torch.repeat_interleave(mask, n1, dim=1)
+
+ # perform masking
+ masked_matrix = mask * matrix + to_spare
+
+ if return_mask:
+ return masked_matrix, mask
+ else:
+ return masked_matrix
+
+def calculate_gru_flops_per_step(gru, sparsification_dict=dict(), drop_input=False):
+ input_size = gru.input_size
+ hidden_size = gru.hidden_size
+ flops = 0
+
+ input_density = (
+ sparsification_dict.get('W_ir', [1])[0]
+ + sparsification_dict.get('W_in', [1])[0]
+ + sparsification_dict.get('W_iz', [1])[0]
+ ) / 3
+
+ recurrent_density = (
+ sparsification_dict.get('W_hr', [1])[0]
+ + sparsification_dict.get('W_hn', [1])[0]
+ + sparsification_dict.get('W_hz', [1])[0]
+ ) / 3
+
+ # input matrix vector multiplications
+ if not drop_input:
+ flops += 2 * 3 * input_size * hidden_size * input_density
+
+ # recurrent matrix vector multiplications
+ flops += 2 * 3 * hidden_size * hidden_size * recurrent_density
+
+ # biases
+ flops += 6 * hidden_size
+
+ # activations estimated by 10 flops per activation
+ flops += 30 * hidden_size
+
+ return flops
diff --git a/dnn/torch/dnntools/dnntools/sparsification/conv1d_sparsifier.py b/dnn/torch/dnntools/dnntools/sparsification/conv1d_sparsifier.py
new file mode 100644
index 00000000..1ac51d0d
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/sparsification/conv1d_sparsifier.py
@@ -0,0 +1,133 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+
+from .base_sparsifier import BaseSparsifier
+from .common import sparsify_matrix, debug
+
+
+class Conv1dSparsifier(BaseSparsifier):
+ def __init__(self, task_list, start, stop, interval, exponent=3):
+ """ Sparsifier for torch.nn.GRUs
+
+ Parameters:
+ -----------
+ task_list : list
+ task_list contains a list of tuples (conv1d, params), where conv1d is an instance
+ of torch.nn.Conv1d and params is a tuple (density, [m, n]),
+ where density is the target density in [0, 1], [m, n] is the shape sub-blocks to which
+ sparsification is applied.
+
+ start : int
+ training step after which sparsification will be started.
+
+ stop : int
+ training step after which sparsification will be completed.
+
+ interval : int
+ sparsification interval for steps between start and stop. After stop sparsification will be
+ carried out after every call to GRUSparsifier.step()
+
+ exponent : float
+ Interpolation exponent for sparsification interval. In step i sparsification will be carried out
+ with density (alpha + target_density * (1 * alpha)), where
+ alpha = ((stop - i) / (start - stop)) ** exponent
+
+ Example:
+ --------
+ >>> import torch
+ >>> conv = torch.nn.Conv1d(8, 16, 8)
+ >>> params = (0.2, [8, 4])
+ >>> sparsifier = Conv1dSparsifier([(conv, params)], 0, 100, 50)
+ >>> for i in range(100):
+ ... sparsifier.step()
+ """
+ super().__init__(task_list, start, stop, interval, exponent=3)
+
+ self.last_mask = None
+
+
+ def sparsify(self, alpha, verbose=False):
+ """ carries out sparsification step
+
+ Call this function after optimizer.step in your
+ training loop.
+
+ Parameters:
+ ----------
+ alpha : float
+ density interpolation parameter (1: dense, 0: target density)
+ verbose : bool
+ if true, densities are printed out
+
+ Returns:
+ --------
+ None
+
+ """
+
+ with torch.no_grad():
+ for conv, params in self.task_list:
+ # reshape weight
+ if hasattr(conv, 'weight_v'):
+ weight = conv.weight_v
+ else:
+ weight = conv.weight
+ i, o, k = weight.shape
+ w = weight.permute(0, 2, 1).flatten(1)
+ target_density, block_size = params
+ density = alpha + (1 - alpha) * target_density
+ w, new_mask = sparsify_matrix(w, density, block_size, return_mask=True)
+ w = w.reshape(i, k, o).permute(0, 2, 1)
+ weight[:] = w
+
+ if self.last_mask is not None:
+ if not torch.all(self.last_mask * new_mask == new_mask) and debug:
+ print("weight resurrection in conv.weight")
+
+ self.last_mask = new_mask
+
+ if verbose:
+ print(f"conv1d_sparsier[{self.step_counter}]: {density=}")
+
+
+if __name__ == "__main__":
+ print("Testing sparsifier")
+
+ import torch
+ conv = torch.nn.Conv1d(8, 16, 8)
+ params = (0.2, [8, 4])
+
+ sparsifier = Conv1dSparsifier([(conv, params)], 0, 100, 5)
+
+ for i in range(100):
+ sparsifier.step(verbose=True)
+
+ print(conv.weight)
diff --git a/dnn/torch/dnntools/dnntools/sparsification/conv_transpose1d_sparsifier.py b/dnn/torch/dnntools/dnntools/sparsification/conv_transpose1d_sparsifier.py
new file mode 100644
index 00000000..6d9398f2
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/sparsification/conv_transpose1d_sparsifier.py
@@ -0,0 +1,134 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+
+
+from .base_sparsifier import BaseSparsifier
+from .common import sparsify_matrix, debug
+
+
+class ConvTranspose1dSparsifier(BaseSparsifier):
+ def __init__(self, task_list, start, stop, interval, exponent=3):
+ """ Sparsifier for torch.nn.GRUs
+
+ Parameters:
+ -----------
+ task_list : list
+ task_list contains a list of tuples (conv1d, params), where conv1d is an instance
+ of torch.nn.Conv1d and params is a tuple (density, [m, n]),
+ where density is the target density in [0, 1], [m, n] is the shape sub-blocks to which
+ sparsification is applied.
+
+ start : int
+ training step after which sparsification will be started.
+
+ stop : int
+ training step after which sparsification will be completed.
+
+ interval : int
+ sparsification interval for steps between start and stop. After stop sparsification will be
+ carried out after every call to GRUSparsifier.step()
+
+ exponent : float
+ Interpolation exponent for sparsification interval. In step i sparsification will be carried out
+ with density (alpha + target_density * (1 * alpha)), where
+ alpha = ((stop - i) / (start - stop)) ** exponent
+
+ Example:
+ --------
+ >>> import torch
+ >>> conv = torch.nn.ConvTranspose1d(8, 16, 8)
+ >>> params = (0.2, [8, 4])
+ >>> sparsifier = ConvTranspose1dSparsifier([(conv, params)], 0, 100, 50)
+ >>> for i in range(100):
+ ... sparsifier.step()
+ """
+
+ super().__init__(task_list, start, stop, interval, exponent=3)
+
+ self.last_mask = None
+
+ def sparsify(self, alpha, verbose=False):
+ """ carries out sparsification step
+
+ Call this function after optimizer.step in your
+ training loop.
+
+ Parameters:
+ ----------
+ alpha : float
+ density interpolation parameter (1: dense, 0: target density)
+ verbose : bool
+ if true, densities are printed out
+
+ Returns:
+ --------
+ None
+
+ """
+
+ with torch.no_grad():
+ for conv, params in self.task_list:
+ # reshape weight
+ if hasattr(conv, 'weight_v'):
+ weight = conv.weight_v
+ else:
+ weight = conv.weight
+ i, o, k = weight.shape
+ w = weight.permute(2, 1, 0).reshape(k * o, i)
+ target_density, block_size = params
+ density = alpha + (1 - alpha) * target_density
+ w, new_mask = sparsify_matrix(w, density, block_size, return_mask=True)
+ w = w.reshape(k, o, i).permute(2, 1, 0)
+ weight[:] = w
+
+ if self.last_mask is not None:
+ if not torch.all(self.last_mask * new_mask == new_mask) and debug:
+ print("weight resurrection in conv.weight")
+
+ self.last_mask = new_mask
+
+ if verbose:
+ print(f"convtrans1d_sparsier[{self.step_counter}]: {density=}")
+
+
+if __name__ == "__main__":
+ print("Testing sparsifier")
+
+ import torch
+ conv = torch.nn.ConvTranspose1d(8, 16, 4, 4)
+ params = (0.2, [8, 4])
+
+ sparsifier = ConvTranspose1dSparsifier([(conv, params)], 0, 100, 5)
+
+ for i in range(100):
+ sparsifier.step(verbose=True)
+
+ print(conv.weight)
diff --git a/dnn/torch/dnntools/dnntools/sparsification/gru_sparsifier.py b/dnn/torch/dnntools/dnntools/sparsification/gru_sparsifier.py
new file mode 100644
index 00000000..417b04be
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/sparsification/gru_sparsifier.py
@@ -0,0 +1,178 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+
+from .base_sparsifier import BaseSparsifier
+from .common import sparsify_matrix, debug
+
+
+class GRUSparsifier(BaseSparsifier):
+ def __init__(self, task_list, start, stop, interval, exponent=3):
+ """ Sparsifier for torch.nn.GRUs
+
+ Parameters:
+ -----------
+ task_list : list
+ task_list contains a list of tuples (gru, sparsify_dict), where gru is an instance
+ of torch.nn.GRU and sparsify_dic is a dictionary with keys in {'W_ir', 'W_iz', 'W_in',
+ 'W_hr', 'W_hz', 'W_hn'} corresponding to the input and recurrent weights for the reset,
+ update, and new gate. The values of sparsify_dict are tuples (density, [m, n], keep_diagonal),
+ where density is the target density in [0, 1], [m, n] is the shape sub-blocks to which
+ sparsification is applied and keep_diagonal is a bool variable indicating whether the diagonal
+ should be kept.
+
+ start : int
+ training step after which sparsification will be started.
+
+ stop : int
+ training step after which sparsification will be completed.
+
+ interval : int
+ sparsification interval for steps between start and stop. After stop sparsification will be
+ carried out after every call to GRUSparsifier.step()
+
+ exponent : float
+ Interpolation exponent for sparsification interval. In step i sparsification will be carried out
+ with density (alpha + target_density * (1 * alpha)), where
+ alpha = ((stop - i) / (start - stop)) ** exponent
+
+ Example:
+ --------
+ >>> import torch
+ >>> gru = torch.nn.GRU(10, 20)
+ >>> sparsify_dict = {
+ ... 'W_ir' : (0.5, [2, 2], False),
+ ... 'W_iz' : (0.6, [2, 2], False),
+ ... 'W_in' : (0.7, [2, 2], False),
+ ... 'W_hr' : (0.1, [4, 4], True),
+ ... 'W_hz' : (0.2, [4, 4], True),
+ ... 'W_hn' : (0.3, [4, 4], True),
+ ... }
+ >>> sparsifier = GRUSparsifier([(gru, sparsify_dict)], 0, 100, 50)
+ >>> for i in range(100):
+ ... sparsifier.step()
+ """
+ super().__init__(task_list, start, stop, interval, exponent=3)
+
+ self.last_masks = {key : None for key in ['W_ir', 'W_in', 'W_iz', 'W_hr', 'W_hn', 'W_hz']}
+
+ def sparsify(self, alpha, verbose=False):
+ """ carries out sparsification step
+
+ Call this function after optimizer.step in your
+ training loop.
+
+ Parameters:
+ ----------
+ alpha : float
+ density interpolation parameter (1: dense, 0: target density)
+ verbose : bool
+ if true, densities are printed out
+
+ Returns:
+ --------
+ None
+
+ """
+
+ with torch.no_grad():
+ for gru, params in self.task_list:
+ hidden_size = gru.hidden_size
+
+ # input weights
+ for i, key in enumerate(['W_ir', 'W_iz', 'W_in']):
+ if key in params:
+ if hasattr(gru, 'weight_ih_l0_v'):
+ weight = gru.weight_ih_l0_v
+ else:
+ weight = gru.weight_ih_l0
+ density = alpha + (1 - alpha) * params[key][0]
+ if verbose:
+ print(f"[{self.step_counter}]: {key} density: {density}")
+
+ weight[i * hidden_size : (i+1) * hidden_size, : ], new_mask = sparsify_matrix(
+ weight[i * hidden_size : (i + 1) * hidden_size, : ],
+ density, # density
+ params[key][1], # block_size
+ params[key][2], # keep_diagonal (might want to set this to False)
+ return_mask=True
+ )
+
+ if type(self.last_masks[key]) != type(None):
+ if not torch.all(self.last_masks[key] * new_mask == new_mask) and debug:
+ print("weight resurrection in weight_ih_l0_v")
+
+ self.last_masks[key] = new_mask
+
+ # recurrent weights
+ for i, key in enumerate(['W_hr', 'W_hz', 'W_hn']):
+ if key in params:
+ if hasattr(gru, 'weight_hh_l0_v'):
+ weight = gru.weight_hh_l0_v
+ else:
+ weight = gru.weight_hh_l0
+ density = alpha + (1 - alpha) * params[key][0]
+ if verbose:
+ print(f"[{self.step_counter}]: {key} density: {density}")
+ weight[i * hidden_size : (i+1) * hidden_size, : ], new_mask = sparsify_matrix(
+ weight[i * hidden_size : (i + 1) * hidden_size, : ],
+ density,
+ params[key][1], # block_size
+ params[key][2], # keep_diagonal (might want to set this to False)
+ return_mask=True
+ )
+
+ if type(self.last_masks[key]) != type(None):
+ if not torch.all(self.last_masks[key] * new_mask == new_mask) and True:
+ print("weight resurrection in weight_hh_l0_v")
+
+ self.last_masks[key] = new_mask
+
+
+
+if __name__ == "__main__":
+ print("Testing sparsifier")
+
+ gru = torch.nn.GRU(10, 20)
+ sparsify_dict = {
+ 'W_ir' : (0.5, [2, 2], False),
+ 'W_iz' : (0.6, [2, 2], False),
+ 'W_in' : (0.7, [2, 2], False),
+ 'W_hr' : (0.1, [4, 4], True),
+ 'W_hz' : (0.2, [4, 4], True),
+ 'W_hn' : (0.3, [4, 4], True),
+ }
+
+ sparsifier = GRUSparsifier([(gru, sparsify_dict)], 0, 100, 10)
+
+ for i in range(100):
+ sparsifier.step(verbose=True)
+
+ print(gru.weight_hh_l0)
diff --git a/dnn/torch/dnntools/dnntools/sparsification/linear_sparsifier.py b/dnn/torch/dnntools/dnntools/sparsification/linear_sparsifier.py
new file mode 100644
index 00000000..59251ddd
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/sparsification/linear_sparsifier.py
@@ -0,0 +1,128 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+
+from .base_sparsifier import BaseSparsifier
+from .common import sparsify_matrix
+
+
+class LinearSparsifier(BaseSparsifier):
+ def __init__(self, task_list, start, stop, interval, exponent=3):
+ """ Sparsifier for torch.nn.GRUs
+
+ Parameters:
+ -----------
+ task_list : list
+ task_list contains a list of tuples (linear, params), where linear is an instance
+ of torch.nn.Linear and params is a tuple (density, [m, n]),
+ where density is the target density in [0, 1], [m, n] is the shape sub-blocks to which
+ sparsification is applied.
+
+ start : int
+ training step after which sparsification will be started.
+
+ stop : int
+ training step after which sparsification will be completed.
+
+ interval : int
+ sparsification interval for steps between start and stop. After stop sparsification will be
+ carried out after every call to GRUSparsifier.step()
+
+ exponent : float
+ Interpolation exponent for sparsification interval. In step i sparsification will be carried out
+ with density (alpha + target_density * (1 * alpha)), where
+ alpha = ((stop - i) / (start - stop)) ** exponent
+
+ Example:
+ --------
+ >>> import torch
+ >>> linear = torch.nn.Linear(8, 16)
+ >>> params = (0.2, [8, 4])
+ >>> sparsifier = LinearSparsifier([(linear, params)], 0, 100, 50)
+ >>> for i in range(100):
+ ... sparsifier.step()
+ """
+
+ super().__init__(task_list, start, stop, interval, exponent=3)
+
+ self.last_mask = None
+
+ def sparsify(self, alpha, verbose=False):
+ """ carries out sparsification step
+
+ Call this function after optimizer.step in your
+ training loop.
+
+ Parameters:
+ ----------
+ alpha : float
+ density interpolation parameter (1: dense, 0: target density)
+ verbose : bool
+ if true, densities are printed out
+
+ Returns:
+ --------
+ None
+
+ """
+
+ with torch.no_grad():
+ for linear, params in self.task_list:
+ if hasattr(linear, 'weight_v'):
+ weight = linear.weight_v
+ else:
+ weight = linear.weight
+ target_density, block_size = params
+ density = alpha + (1 - alpha) * target_density
+ weight[:], new_mask = sparsify_matrix(weight, density, block_size, return_mask=True)
+
+ if self.last_mask is not None:
+ if not torch.all(self.last_mask * new_mask == new_mask) and debug:
+ print("weight resurrection in conv.weight")
+
+ self.last_mask = new_mask
+
+ if verbose:
+ print(f"linear_sparsifier[{self.step_counter}]: {density=}")
+
+
+if __name__ == "__main__":
+ print("Testing sparsifier")
+
+ import torch
+ linear = torch.nn.Linear(8, 16)
+ params = (0.2, [4, 2])
+
+ sparsifier = LinearSparsifier([(linear, params)], 0, 100, 5)
+
+ for i in range(100):
+ sparsifier.step(verbose=True)
+
+ print(linear.weight)
diff --git a/dnn/torch/dnntools/dnntools/sparsification/utils.py b/dnn/torch/dnntools/dnntools/sparsification/utils.py
new file mode 100644
index 00000000..42f22353
--- /dev/null
+++ b/dnn/torch/dnntools/dnntools/sparsification/utils.py
@@ -0,0 +1,64 @@
+import torch
+
+from dnntools.sparsification import GRUSparsifier, LinearSparsifier, Conv1dSparsifier, ConvTranspose1dSparsifier
+
+def mark_for_sparsification(module, params):
+ setattr(module, 'sparsify', True)
+ setattr(module, 'sparsification_params', params)
+ return module
+
+def create_sparsifier(module, start, stop, interval):
+ sparsifier_list = []
+ for m in module.modules():
+ if hasattr(m, 'sparsify'):
+ if isinstance(m, torch.nn.GRU):
+ sparsifier_list.append(
+ GRUSparsifier([(m, m.sparsification_params)], start, stop, interval)
+ )
+ elif isinstance(m, torch.nn.Linear):
+ sparsifier_list.append(
+ LinearSparsifier([(m, m.sparsification_params)], start, stop, interval)
+ )
+ elif isinstance(m, torch.nn.Conv1d):
+ sparsifier_list.append(
+ Conv1dSparsifier([(m, m.sparsification_params)], start, stop, interval)
+ )
+ elif isinstance(m, torch.nn.ConvTranspose1d):
+ sparsifier_list.append(
+ ConvTranspose1dSparsifier([(m, m.sparsification_params)], start, stop, interval)
+ )
+ else:
+ print(f"[create_sparsifier] warning: module {m} marked for sparsification but no suitable sparsifier exists.")
+
+ def sparsify(verbose=False):
+ for sparsifier in sparsifier_list:
+ sparsifier.step(verbose)
+
+ return sparsify
+
+
+def count_parameters(model, verbose=False):
+ total = 0
+ for name, p in model.named_parameters():
+ count = torch.ones_like(p).sum().item()
+
+ if verbose:
+ print(f"{name}: {count} parameters")
+
+ total += count
+
+ return total
+
+def estimate_nonzero_parameters(module):
+ num_zero_parameters = 0
+ if hasattr(module, 'sparsify'):
+ params = module.sparsification_params
+ if isinstance(module, torch.nn.Conv1d) or isinstance(module, torch.nn.ConvTranspose1d):
+ num_zero_parameters = torch.ones_like(module.weight).sum().item() * (1 - params[0])
+ elif isinstance(module, torch.nn.GRU):
+ num_zero_parameters = module.input_size * module.hidden_size * (3 - params['W_ir'][0] - params['W_iz'][0] - params['W_in'][0])
+ num_zero_parameters += module.hidden_size * module.hidden_size * (3 - params['W_hr'][0] - params['W_hz'][0] - params['W_hn'][0])
+ elif isinstance(module, torch.nn.Linear):
+ num_zero_parameters = module.in_features * module.out_features * params[0]
+ else:
+ raise ValueError(f'unknown sparsification method for module of type {type(module)}')
diff --git a/dnn/torch/dnntools/requirements.txt b/dnn/torch/dnntools/requirements.txt
new file mode 100644
index 00000000..08ed5eeb
--- /dev/null
+++ b/dnn/torch/dnntools/requirements.txt
@@ -0,0 +1 @@
+torch \ No newline at end of file
diff --git a/dnn/torch/dnntools/setup.py b/dnn/torch/dnntools/setup.py
new file mode 100644
index 00000000..bc4ef3f1
--- /dev/null
+++ b/dnn/torch/dnntools/setup.py
@@ -0,0 +1,48 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+#!/usr/bin/env/python
+import os
+from setuptools import setup
+
+lib_folder = os.path.dirname(os.path.realpath(__file__))
+
+with open(os.path.join(lib_folder, 'requirements.txt'), 'r') as f:
+ install_requires = list(f.read().splitlines())
+
+print(install_requires)
+
+setup(name='dnntools',
+ version='1.0',
+ author='Jan Buethe',
+ author_email='jbuethe@amazon.de',
+ description='Non-Standard tools for deep neural network training with PyTorch',
+ packages=['dnntools', 'dnntools.sparsification', 'dnntools.quantization'],
+ install_requires=install_requires
+ )
diff --git a/dnn/torch/fargan/adv_train_fargan.py b/dnn/torch/fargan/adv_train_fargan.py
new file mode 100644
index 00000000..c2977644
--- /dev/null
+++ b/dnn/torch/fargan/adv_train_fargan.py
@@ -0,0 +1,277 @@
+import os
+import argparse
+import random
+import numpy as np
+import sys
+import math as m
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+import tqdm
+
+import fargan
+from dataset import FARGANDataset
+from stft_loss import *
+
+source_dir = os.path.split(os.path.abspath(__file__))[0]
+sys.path.append(os.path.join(source_dir, "../osce/"))
+
+import models as osce_models
+
+
+def fmap_loss(scores_real, scores_gen):
+ num_discs = len(scores_real)
+ loss_feat = 0
+ for k in range(num_discs):
+ num_layers = len(scores_gen[k]) - 1
+ f = 4 / num_discs / num_layers
+ for l in range(num_layers):
+ loss_feat += f * F.l1_loss(scores_gen[k][l], scores_real[k][l].detach())
+
+ return loss_feat
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('features', type=str, help='path to feature file in .f32 format')
+parser.add_argument('signal', type=str, help='path to signal file in .s16 format')
+parser.add_argument('output', type=str, help='path to output folder')
+
+parser.add_argument('--suffix', type=str, help="model name suffix", default="")
+parser.add_argument('--cuda-visible-devices', type=str, help="comma separates list of cuda visible device indices, default: CUDA_VISIBLE_DEVICES", default=None)
+
+
+model_group = parser.add_argument_group(title="model parameters")
+model_group.add_argument('--cond-size', type=int, help="first conditioning size, default: 256", default=256)
+model_group.add_argument('--gamma', type=float, help="Use A(z/gamma), default: 0.9", default=0.9)
+
+training_group = parser.add_argument_group(title="training parameters")
+training_group.add_argument('--batch-size', type=int, help="batch size, default: 128", default=128)
+training_group.add_argument('--lr', type=float, help='learning rate, default: 5e-4', default=5e-4)
+training_group.add_argument('--epochs', type=int, help='number of training epochs, default: 50', default=50)
+training_group.add_argument('--sequence-length', type=int, help='sequence length, default: 60', default=60)
+training_group.add_argument('--lr-decay', type=float, help='learning rate decay factor, default: 0.0', default=0.0)
+training_group.add_argument('--initial-checkpoint', type=str, help='initial checkpoint to start training from, default: None', default=None)
+training_group.add_argument('--reg-weight', type=float, help='regression loss weight, default: 1.0', default=1.0)
+training_group.add_argument('--fmap-weight', type=float, help='feature matchin loss weight, default: 1.0', default=1.)
+
+args = parser.parse_args()
+
+if args.cuda_visible_devices != None:
+ os.environ['CUDA_VISIBLE_DEVICES'] = args.cuda_visible_devices
+
+# checkpoints
+checkpoint_dir = os.path.join(args.output, 'checkpoints')
+checkpoint = dict()
+os.makedirs(checkpoint_dir, exist_ok=True)
+
+
+# training parameters
+batch_size = args.batch_size
+lr = args.lr
+epochs = args.epochs
+sequence_length = args.sequence_length
+lr_decay = args.lr_decay
+
+adam_betas = [0.8, 0.99]
+adam_eps = 1e-8
+features_file = args.features
+signal_file = args.signal
+
+# model parameters
+cond_size = args.cond_size
+
+
+checkpoint['batch_size'] = batch_size
+checkpoint['lr'] = lr
+checkpoint['lr_decay'] = lr_decay
+checkpoint['epochs'] = epochs
+checkpoint['sequence_length'] = sequence_length
+checkpoint['adam_betas'] = adam_betas
+
+
+device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
+
+checkpoint['model_args'] = ()
+checkpoint['model_kwargs'] = {'cond_size': cond_size, 'gamma': args.gamma}
+print(checkpoint['model_kwargs'])
+model = fargan.FARGAN(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+
+
+#discriminator
+disc_name = 'fdmresdisc'
+disc = osce_models.model_dict[disc_name](
+ architecture='free',
+ design='f_down',
+ fft_sizes_16k=[2**n for n in range(6, 12)],
+ freq_roi=[0, 7400],
+ max_channels=256,
+ noise_gain=0.0
+)
+
+if type(args.initial_checkpoint) != type(None):
+ checkpoint = torch.load(args.initial_checkpoint, map_location='cpu')
+ model.load_state_dict(checkpoint['state_dict'], strict=False)
+
+checkpoint['state_dict'] = model.state_dict()
+
+
+dataset = FARGANDataset(features_file, signal_file, sequence_length=sequence_length)
+dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, drop_last=True, num_workers=4)
+
+
+optimizer = torch.optim.AdamW(model.parameters(), lr=lr, betas=adam_betas, eps=adam_eps)
+optimizer_disc = torch.optim.AdamW([p for p in disc.parameters() if p.requires_grad], lr=lr, betas=adam_betas, eps=adam_eps)
+
+
+# learning rate scheduler
+scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer=optimizer, lr_lambda=lambda x : 1 / (1 + lr_decay * x))
+scheduler_disc = torch.optim.lr_scheduler.LambdaLR(optimizer=optimizer_disc, lr_lambda=lambda x : 1 / (1 + lr_decay * x))
+
+states = None
+
+spect_loss = MultiResolutionSTFTLoss(device).to(device)
+
+for param in model.parameters():
+ param.requires_grad = False
+
+batch_count = 0
+if __name__ == '__main__':
+ model.to(device)
+ disc.to(device)
+
+ for epoch in range(1, epochs + 1):
+
+ m_r = 0
+ m_f = 0
+ s_r = 1
+ s_f = 1
+
+ running_cont_loss = 0
+ running_disc_loss = 0
+ running_gen_loss = 0
+ running_fmap_loss = 0
+ running_reg_loss = 0
+ running_wc = 0
+
+ print(f"training epoch {epoch}...")
+ with tqdm.tqdm(dataloader, unit='batch') as tepoch:
+ for i, (features, periods, target, lpc) in enumerate(tepoch):
+ if epoch == 1 and i == 400:
+ for param in model.parameters():
+ param.requires_grad = True
+ for param in model.cond_net.parameters():
+ param.requires_grad = False
+ for param in model.sig_net.cond_gain_dense.parameters():
+ param.requires_grad = False
+
+ optimizer.zero_grad()
+ features = features.to(device)
+ #lpc = lpc.to(device)
+ #lpc = lpc*(args.gamma**torch.arange(1,17, device=device))
+ #lpc = fargan.interp_lpc(lpc, 4)
+ periods = periods.to(device)
+ if True:
+ target = target[:, :sequence_length*160]
+ #lpc = lpc[:,:sequence_length*4,:]
+ features = features[:,:sequence_length+4,:]
+ periods = periods[:,:sequence_length+4]
+ else:
+ target=target[::2, :]
+ #lpc=lpc[::2,:]
+ features=features[::2,:]
+ periods=periods[::2,:]
+ target = target.to(device)
+ #target = fargan.analysis_filter(target, lpc[:,:,:], nb_subframes=1, gamma=args.gamma)
+
+ #nb_pre = random.randrange(1, 6)
+ nb_pre = 2
+ pre = target[:, :nb_pre*160]
+ output, _ = model(features, periods, target.size(1)//160 - nb_pre, pre=pre, states=None)
+ output = torch.cat([pre, output], -1)
+
+
+ # discriminator update
+ scores_gen = disc(output.detach().unsqueeze(1))
+ scores_real = disc(target.unsqueeze(1))
+
+ disc_loss = 0
+ for scale in scores_gen:
+ disc_loss += ((scale[-1]) ** 2).mean()
+ m_f = 0.9 * m_f + 0.1 * scale[-1].detach().mean().cpu().item()
+ s_f = 0.9 * s_f + 0.1 * scale[-1].detach().std().cpu().item()
+
+ for scale in scores_real:
+ disc_loss += ((1 - scale[-1]) ** 2).mean()
+ m_r = 0.9 * m_r + 0.1 * scale[-1].detach().mean().cpu().item()
+ s_r = 0.9 * s_r + 0.1 * scale[-1].detach().std().cpu().item()
+
+ disc_loss = 0.5 * disc_loss / len(scores_gen)
+ winning_chance = 0.5 * m.erfc( (m_r - m_f) / m.sqrt(2 * (s_f**2 + s_r**2)) )
+ running_wc += winning_chance
+
+ disc.zero_grad()
+ disc_loss.backward()
+ optimizer_disc.step()
+
+ # model update
+ scores_gen = disc(output.unsqueeze(1))
+ if False: # todo: check whether that makes a difference
+ with torch.no_grad():
+ scores_real = disc(target.unsqueeze(1))
+
+ cont_loss = fargan.sig_loss(target[:, nb_pre*160:nb_pre*160+80], output[:, nb_pre*160:nb_pre*160+80])
+ specc_loss = spect_loss(output, target.detach())
+ reg_loss = (.00*cont_loss + specc_loss)
+
+ loss_gen = 0
+ for scale in scores_gen:
+ loss_gen += ((1 - scale[-1]) ** 2).mean() / len(scores_gen)
+
+ feat_loss = args.fmap_weight * fmap_loss(scores_real, scores_gen)
+
+ reg_weight = args.reg_weight# + 15./(1 + (batch_count/7600.))
+ gen_loss = reg_weight * reg_loss + feat_loss + loss_gen
+
+ model.zero_grad()
+
+
+ gen_loss.backward()
+ optimizer.step()
+
+ #model.clip_weights()
+
+ scheduler.step()
+ scheduler_disc.step()
+
+ running_cont_loss += cont_loss.detach().cpu().item()
+ running_gen_loss += loss_gen.detach().cpu().item()
+ running_disc_loss += disc_loss.detach().cpu().item()
+ running_fmap_loss += feat_loss.detach().cpu().item()
+ running_reg_loss += reg_loss.detach().cpu().item()
+
+
+
+ tepoch.set_postfix(cont_loss=f"{running_cont_loss/(i+1):8.5f}",
+ reg_weight=f"{reg_weight:8.5f}",
+ gen_loss=f"{running_gen_loss/(i+1):8.5f}",
+ disc_loss=f"{running_disc_loss/(i+1):8.5f}",
+ fmap_loss=f"{running_fmap_loss/(i+1):8.5f}",
+ reg_loss=f"{running_reg_loss/(i+1):8.5f}",
+ wc = f"{running_wc/(i+1):8.5f}",
+ )
+ batch_count = batch_count + 1
+
+ # save checkpoint
+ checkpoint_path = os.path.join(checkpoint_dir, f'fargan{args.suffix}_adv_{epoch}.pth')
+ checkpoint['state_dict'] = model.state_dict()
+ checkpoint['disc_sate_dict'] = disc.state_dict()
+ checkpoint['loss'] = {
+ 'cont': running_cont_loss / len(dataloader),
+ 'gen': running_gen_loss / len(dataloader),
+ 'disc': running_disc_loss / len(dataloader),
+ 'fmap': running_fmap_loss / len(dataloader),
+ 'reg': running_reg_loss / len(dataloader)
+ }
+ checkpoint['epoch'] = epoch
+ torch.save(checkpoint, checkpoint_path)
diff --git a/dnn/torch/fargan/dataset.py b/dnn/torch/fargan/dataset.py
new file mode 100644
index 00000000..2dfbb0b5
--- /dev/null
+++ b/dnn/torch/fargan/dataset.py
@@ -0,0 +1,61 @@
+import torch
+import numpy as np
+import fargan
+
+class FARGANDataset(torch.utils.data.Dataset):
+ def __init__(self,
+ feature_file,
+ signal_file,
+ frame_size=160,
+ sequence_length=15,
+ lookahead=1,
+ nb_used_features=20,
+ nb_features=36):
+
+ self.frame_size = frame_size
+ self.sequence_length = sequence_length
+ self.lookahead = lookahead
+ self.nb_features = nb_features
+ self.nb_used_features = nb_used_features
+ pcm_chunk_size = self.frame_size*self.sequence_length
+
+ self.data = np.memmap(signal_file, dtype='int16', mode='r')
+ #self.data = self.data[1::2]
+ self.nb_sequences = len(self.data)//(pcm_chunk_size)-4
+ self.data = self.data[(4-self.lookahead)*self.frame_size:]
+ self.data = self.data[:self.nb_sequences*pcm_chunk_size]
+
+
+ #self.data = np.reshape(self.data, (self.nb_sequences, pcm_chunk_size))
+ sizeof = self.data.strides[-1]
+ self.data = np.lib.stride_tricks.as_strided(self.data, shape=(self.nb_sequences, pcm_chunk_size*2),
+ strides=(pcm_chunk_size*sizeof, sizeof))
+
+ self.features = np.reshape(np.memmap(feature_file, dtype='float32', mode='r'), (-1, nb_features))
+ sizeof = self.features.strides[-1]
+ self.features = np.lib.stride_tricks.as_strided(self.features, shape=(self.nb_sequences, self.sequence_length*2+4, nb_features),
+ strides=(self.sequence_length*self.nb_features*sizeof, self.nb_features*sizeof, sizeof))
+ #self.periods = np.round(50*self.features[:,:,self.nb_used_features-2]+100).astype('int')
+ self.periods = np.round(np.clip(256./2**(self.features[:,:,self.nb_used_features-2]+1.5), 32, 255)).astype('int')
+
+ self.lpc = self.features[:, :, self.nb_used_features:]
+ self.features = self.features[:, :, :self.nb_used_features]
+ print("lpc_size:", self.lpc.shape)
+
+ def __len__(self):
+ return self.nb_sequences
+
+ def __getitem__(self, index):
+ features = self.features[index, :, :].copy()
+ if self.lookahead != 0:
+ lpc = self.lpc[index, 4-self.lookahead:-self.lookahead, :].copy()
+ else:
+ lpc = self.lpc[index, 4:, :].copy()
+ data = self.data[index, :].copy().astype(np.float32) / 2**15
+ periods = self.periods[index, :].copy()
+ #lpc = lpc*(self.gamma**np.arange(1,17))
+ #lpc=lpc[None,:,:]
+ #lpc = fargan.interp_lpc(lpc, 4)
+ #lpc=lpc[0,:,:]
+
+ return features, periods, data, lpc
diff --git a/dnn/torch/fargan/dump_fargan_weights.py b/dnn/torch/fargan/dump_fargan_weights.py
new file mode 100644
index 00000000..ec1eb9ae
--- /dev/null
+++ b/dnn/torch/fargan/dump_fargan_weights.py
@@ -0,0 +1,112 @@
+import os
+import sys
+import argparse
+
+import torch
+from torch import nn
+
+
+sys.path.append(os.path.join(os.path.split(__file__)[0], '../weight-exchange'))
+import wexchange.torch
+
+import fargan
+#from models import model_dict
+
+unquantized = [ 'cond_net.pembed', 'cond_net.fdense1', 'sig_net.cond_gain_dense', 'sig_net.gain_dense_out' ]
+
+unquantized2 = [
+ 'cond_net.pembed',
+ 'cond_net.fdense1',
+ 'cond_net.fconv1',
+ 'cond_net.fconv2',
+ 'cont_net.0',
+ 'sig_net.cond_gain_dense',
+ 'sig_net.fwc0.conv',
+ 'sig_net.fwc0.glu.gate',
+ 'sig_net.dense1_glu.gate',
+ 'sig_net.gru1_glu.gate',
+ 'sig_net.gru2_glu.gate',
+ 'sig_net.gru3_glu.gate',
+ 'sig_net.skip_glu.gate',
+ 'sig_net.skip_dense',
+ 'sig_net.sig_dense_out',
+ 'sig_net.gain_dense_out'
+]
+
+description=f"""
+This is an unsafe dumping script for FARGAN models. It assumes that all weights are included in Linear, Conv1d or GRU layer
+and will fail to export any other weights.
+
+Furthermore, the quanitze option relies on the following explicit list of layers to be excluded:
+{unquantized}.
+
+Modify this script manually if adjustments are needed.
+"""
+
+parser = argparse.ArgumentParser(description=description)
+parser.add_argument('weightfile', type=str, help='weight file path')
+parser.add_argument('export_folder', type=str)
+parser.add_argument('--export-filename', type=str, default='fargan_data', help='filename for source and header file (.c and .h will be added), defaults to fargan_data')
+parser.add_argument('--struct-name', type=str, default='FARGAN', help='name for C struct, defaults to FARGAN')
+parser.add_argument('--quantize', action='store_true', help='apply quantization')
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+ print(f"loading weights from {args.weightfile}...")
+ saved_gen= torch.load(args.weightfile, map_location='cpu')
+ saved_gen['model_args'] = ()
+ saved_gen['model_kwargs'] = {'cond_size': 256, 'gamma': 0.9}
+
+ model = fargan.FARGAN(*saved_gen['model_args'], **saved_gen['model_kwargs'])
+ model.load_state_dict(saved_gen['state_dict'], strict=False)
+ def _remove_weight_norm(m):
+ try:
+ torch.nn.utils.remove_weight_norm(m)
+ except ValueError: # this module didn't have weight norm
+ return
+ model.apply(_remove_weight_norm)
+
+
+ print("dumping model...")
+ quantize_model=args.quantize
+
+ output_folder = args.export_folder
+ os.makedirs(output_folder, exist_ok=True)
+
+ writer = wexchange.c_export.c_writer.CWriter(os.path.join(output_folder, args.export_filename), model_struct_name=args.struct_name)
+
+ for name, module in model.named_modules():
+
+ if quantize_model:
+ quantize=name not in unquantized
+ scale = None if quantize else 1/128
+ else:
+ quantize=False
+ scale=1/128
+
+ if isinstance(module, nn.Linear):
+ print(f"dumping linear layer {name}...")
+ wexchange.torch.dump_torch_dense_weights(writer, module, name.replace('.', '_'), quantize=quantize, scale=scale)
+
+ elif isinstance(module, nn.Conv1d):
+ print(f"dumping conv1d layer {name}...")
+ wexchange.torch.dump_torch_conv1d_weights(writer, module, name.replace('.', '_'), quantize=quantize, scale=scale)
+
+ elif isinstance(module, nn.GRU):
+ print(f"dumping GRU layer {name}...")
+ wexchange.torch.dump_torch_gru_weights(writer, module, name.replace('.', '_'), quantize=quantize, scale=scale, recurrent_scale=scale)
+
+ elif isinstance(module, nn.GRUCell):
+ print(f"dumping GRUCell layer {name}...")
+ wexchange.torch.dump_torch_grucell_weights(writer, module, name.replace('.', '_'), quantize=quantize, scale=scale, recurrent_scale=scale)
+
+ elif isinstance(module, nn.Embedding):
+ print(f"dumping Embedding layer {name}...")
+ wexchange.torch.dump_torch_embedding_weights(writer, module, name.replace('.', '_'), quantize=quantize, scale=scale)
+ #wexchange.torch.dump_torch_embedding_weights(writer, module)
+
+ else:
+ print(f"Ignoring layer {name}...")
+
+ writer.close()
diff --git a/dnn/torch/fargan/fargan.py b/dnn/torch/fargan/fargan.py
new file mode 100644
index 00000000..8dbb694d
--- /dev/null
+++ b/dnn/torch/fargan/fargan.py
@@ -0,0 +1,322 @@
+import numpy as np
+import torch
+from torch import nn
+import torch.nn.functional as F
+import filters
+from torch.nn.utils import weight_norm
+#from convert_lsp import lpc_to_lsp, lsp_to_lpc
+from rc import lpc2rc, rc2lpc
+
+Fs = 16000
+
+fid_dict = {}
+def dump_signal(x, filename):
+ return
+ if filename in fid_dict:
+ fid = fid_dict[filename]
+ else:
+ fid = open(filename, "w")
+ fid_dict[filename] = fid
+ x = x.detach().numpy().astype('float32')
+ x.tofile(fid)
+
+
+def sig_l1(y_true, y_pred):
+ return torch.mean(abs(y_true-y_pred))/torch.mean(abs(y_true))
+
+def sig_loss(y_true, y_pred):
+ t = y_true/(1e-15+torch.norm(y_true, dim=-1, p=2, keepdim=True))
+ p = y_pred/(1e-15+torch.norm(y_pred, dim=-1, p=2, keepdim=True))
+ return torch.mean(1.-torch.sum(p*t, dim=-1))
+
+def interp_lpc(lpc, factor):
+ #print(lpc.shape)
+ #f = (np.arange(factor)+.5*((factor+1)%2))/factor
+ lsp = torch.atanh(lpc2rc(lpc))
+ #print("lsp0:")
+ #print(lsp)
+ shape = lsp.shape
+ #print("shape is", shape)
+ shape = (shape[0], shape[1]*factor, shape[2])
+ interp_lsp = torch.zeros(shape, device=lpc.device)
+ for k in range(factor):
+ f = (k+.5*((factor+1)%2))/factor
+ interp = (1-f)*lsp[:,:-1,:] + f*lsp[:,1:,:]
+ interp_lsp[:,factor//2+k:-(factor//2):factor,:] = interp
+ for k in range(factor//2):
+ interp_lsp[:,k,:] = interp_lsp[:,factor//2,:]
+ for k in range((factor+1)//2):
+ interp_lsp[:,-k-1,:] = interp_lsp[:,-(factor+3)//2,:]
+ #print("lsp:")
+ #print(interp_lsp)
+ return rc2lpc(torch.tanh(interp_lsp))
+
+def analysis_filter(x, lpc, nb_subframes=4, subframe_size=40, gamma=.9):
+ device = x.device
+ batch_size = lpc.size(0)
+
+ nb_frames = lpc.shape[1]
+
+
+ sig = torch.zeros(batch_size, subframe_size+16, device=device)
+ x = torch.reshape(x, (batch_size, nb_frames*nb_subframes, subframe_size))
+ out = torch.zeros((batch_size, 0), device=device)
+
+ #if gamma is not None:
+ # bw = gamma**(torch.arange(1, 17, device=device))
+ # lpc = lpc*bw[None,None,:]
+ ones = torch.ones((*(lpc.shape[:-1]), 1), device=device)
+ zeros = torch.zeros((*(lpc.shape[:-1]), subframe_size-1), device=device)
+ a = torch.cat([ones, lpc], -1)
+ a_big = torch.cat([a, zeros], -1)
+ fir_mat_big = filters.toeplitz_from_filter(a_big)
+
+ #print(a_big[:,0,:])
+ for n in range(nb_frames):
+ for k in range(nb_subframes):
+
+ sig = torch.cat([sig[:,subframe_size:], x[:,n*nb_subframes + k, :]], 1)
+ exc = torch.bmm(fir_mat_big[:,n,:,:], sig[:,:,None])
+ out = torch.cat([out, exc[:,-subframe_size:,0]], 1)
+
+ return out
+
+
+# weight initialization and clipping
+def init_weights(module):
+ if isinstance(module, nn.GRU):
+ for p in module.named_parameters():
+ if p[0].startswith('weight_hh_'):
+ nn.init.orthogonal_(p[1])
+
+def gen_phase_embedding(periods, frame_size):
+ device = periods.device
+ batch_size = periods.size(0)
+ nb_frames = periods.size(1)
+ w0 = 2*torch.pi/periods
+ w0_shift = torch.cat([2*torch.pi*torch.rand((batch_size, 1), device=device)/frame_size, w0[:,:-1]], 1)
+ cum_phase = frame_size*torch.cumsum(w0_shift, 1)
+ fine_phase = w0[:,:,None]*torch.broadcast_to(torch.arange(frame_size, device=device), (batch_size, nb_frames, frame_size))
+ embed = torch.unsqueeze(cum_phase, 2) + fine_phase
+ embed = torch.reshape(embed, (batch_size, -1))
+ return torch.cos(embed), torch.sin(embed)
+
+class GLU(nn.Module):
+ def __init__(self, feat_size):
+ super(GLU, self).__init__()
+
+ torch.manual_seed(5)
+
+ self.gate = weight_norm(nn.Linear(feat_size, feat_size, bias=False))
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d)\
+ or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x):
+
+ out = x * torch.sigmoid(self.gate(x))
+
+ return out
+
+class FWConv(nn.Module):
+ def __init__(self, in_size, out_size, kernel_size=2):
+ super(FWConv, self).__init__()
+
+ torch.manual_seed(5)
+
+ self.in_size = in_size
+ self.kernel_size = kernel_size
+ self.conv = weight_norm(nn.Linear(in_size*self.kernel_size, out_size, bias=False))
+ self.glu = GLU(out_size)
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d)\
+ or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x, state):
+ xcat = torch.cat((state, x), -1)
+ #print(x.shape, state.shape, xcat.shape, self.in_size, self.kernel_size)
+ out = self.glu(torch.tanh(self.conv(xcat)))
+ return out, xcat[:,self.in_size:]
+
+def n(x):
+ return torch.clamp(x + (1./127.)*(torch.rand_like(x)-.5), min=-1., max=1.)
+
+class FARGANCond(nn.Module):
+ def __init__(self, feature_dim=20, cond_size=256, pembed_dims=12):
+ super(FARGANCond, self).__init__()
+
+ self.feature_dim = feature_dim
+ self.cond_size = cond_size
+
+ self.pembed = nn.Embedding(224, pembed_dims)
+ self.fdense1 = nn.Linear(self.feature_dim + pembed_dims, 64, bias=False)
+ self.fconv1 = nn.Conv1d(64, 128, kernel_size=3, padding='valid', bias=False)
+ self.fdense2 = nn.Linear(128, 80*4, bias=False)
+
+ self.apply(init_weights)
+ nb_params = sum(p.numel() for p in self.parameters())
+ print(f"cond model: {nb_params} weights")
+
+ def forward(self, features, period):
+ features = features[:,2:,:]
+ period = period[:,2:]
+ p = self.pembed(period-32)
+ features = torch.cat((features, p), -1)
+ tmp = torch.tanh(self.fdense1(features))
+ tmp = tmp.permute(0, 2, 1)
+ tmp = torch.tanh(self.fconv1(tmp))
+ tmp = tmp.permute(0, 2, 1)
+ tmp = torch.tanh(self.fdense2(tmp))
+ #tmp = torch.tanh(self.fdense2(tmp))
+ return tmp
+
+class FARGANSub(nn.Module):
+ def __init__(self, subframe_size=40, nb_subframes=4, cond_size=256):
+ super(FARGANSub, self).__init__()
+
+ self.subframe_size = subframe_size
+ self.nb_subframes = nb_subframes
+ self.cond_size = cond_size
+ self.cond_gain_dense = nn.Linear(80, 1)
+
+ #self.sig_dense1 = nn.Linear(4*self.subframe_size+self.passthrough_size+self.cond_size, self.cond_size, bias=False)
+ self.fwc0 = FWConv(2*self.subframe_size+80+4, 192)
+ self.gru1 = nn.GRUCell(192+2*self.subframe_size, 160, bias=False)
+ self.gru2 = nn.GRUCell(160+2*self.subframe_size, 128, bias=False)
+ self.gru3 = nn.GRUCell(128+2*self.subframe_size, 128, bias=False)
+
+ self.gru1_glu = GLU(160)
+ self.gru2_glu = GLU(128)
+ self.gru3_glu = GLU(128)
+ self.skip_glu = GLU(128)
+ #self.ptaps_dense = nn.Linear(4*self.cond_size, 5)
+
+ self.skip_dense = nn.Linear(192+160+2*128+2*self.subframe_size, 128, bias=False)
+ self.sig_dense_out = nn.Linear(128, self.subframe_size, bias=False)
+ self.gain_dense_out = nn.Linear(192, 4)
+
+
+ self.apply(init_weights)
+ nb_params = sum(p.numel() for p in self.parameters())
+ print(f"subframe model: {nb_params} weights")
+
+ def forward(self, cond, prev_pred, exc_mem, period, states, gain=None):
+ device = exc_mem.device
+ #print(cond.shape, prev.shape)
+
+ cond = n(cond)
+ dump_signal(gain, 'gain0.f32')
+ gain = torch.exp(self.cond_gain_dense(cond))
+ dump_signal(gain, 'gain1.f32')
+ idx = 256-period[:,None]
+ rng = torch.arange(self.subframe_size+4, device=device)
+ idx = idx + rng[None,:] - 2
+ mask = idx >= 256
+ idx = idx - mask*period[:,None]
+ pred = torch.gather(exc_mem, 1, idx)
+ pred = n(pred/(1e-5+gain))
+
+ prev = exc_mem[:,-self.subframe_size:]
+ dump_signal(prev, 'prev_in.f32')
+ prev = n(prev/(1e-5+gain))
+ dump_signal(prev, 'pitch_exc.f32')
+ dump_signal(exc_mem, 'exc_mem.f32')
+
+ tmp = torch.cat((cond, pred, prev), 1)
+ #fpitch = taps[:,0:1]*pred[:,:-4] + taps[:,1:2]*pred[:,1:-3] + taps[:,2:3]*pred[:,2:-2] + taps[:,3:4]*pred[:,3:-1] + taps[:,4:]*pred[:,4:]
+ fpitch = pred[:,2:-2]
+
+ #tmp = self.dense1_glu(torch.tanh(self.sig_dense1(tmp)))
+ fwc0_out, fwc0_state = self.fwc0(tmp, states[3])
+ fwc0_out = n(fwc0_out)
+ pitch_gain = torch.sigmoid(self.gain_dense_out(fwc0_out))
+
+ gru1_state = self.gru1(torch.cat([fwc0_out, pitch_gain[:,0:1]*fpitch, prev], 1), states[0])
+ gru1_out = self.gru1_glu(n(gru1_state))
+ gru1_out = n(gru1_out)
+ gru2_state = self.gru2(torch.cat([gru1_out, pitch_gain[:,1:2]*fpitch, prev], 1), states[1])
+ gru2_out = self.gru2_glu(n(gru2_state))
+ gru2_out = n(gru2_out)
+ gru3_state = self.gru3(torch.cat([gru2_out, pitch_gain[:,2:3]*fpitch, prev], 1), states[2])
+ gru3_out = self.gru3_glu(n(gru3_state))
+ gru3_out = n(gru3_out)
+ gru3_out = torch.cat([gru1_out, gru2_out, gru3_out, fwc0_out], 1)
+ skip_out = torch.tanh(self.skip_dense(torch.cat([gru3_out, pitch_gain[:,3:4]*fpitch, prev], 1)))
+ skip_out = self.skip_glu(n(skip_out))
+ sig_out = torch.tanh(self.sig_dense_out(skip_out))
+ dump_signal(sig_out, 'exc_out.f32')
+ #taps = self.ptaps_dense(gru3_out)
+ #taps = .2*taps + torch.exp(taps)
+ #taps = taps / (1e-2 + torch.sum(torch.abs(taps), dim=-1, keepdim=True))
+ #dump_signal(taps, 'taps.f32')
+
+ dump_signal(pitch_gain, 'pgain.f32')
+ #sig_out = (sig_out + pitch_gain*fpitch) * gain
+ sig_out = sig_out * gain
+ exc_mem = torch.cat([exc_mem[:,self.subframe_size:], sig_out], 1)
+ prev_pred = torch.cat([prev_pred[:,self.subframe_size:], fpitch], 1)
+ dump_signal(sig_out, 'sig_out.f32')
+ return sig_out, exc_mem, prev_pred, (gru1_state, gru2_state, gru3_state, fwc0_state)
+
+class FARGAN(nn.Module):
+ def __init__(self, subframe_size=40, nb_subframes=4, feature_dim=20, cond_size=256, passthrough_size=0, has_gain=False, gamma=None):
+ super(FARGAN, self).__init__()
+
+ self.subframe_size = subframe_size
+ self.nb_subframes = nb_subframes
+ self.frame_size = self.subframe_size*self.nb_subframes
+ self.feature_dim = feature_dim
+ self.cond_size = cond_size
+
+ self.cond_net = FARGANCond(feature_dim=feature_dim, cond_size=cond_size)
+ self.sig_net = FARGANSub(subframe_size=subframe_size, nb_subframes=nb_subframes, cond_size=cond_size)
+
+ def forward(self, features, period, nb_frames, pre=None, states=None):
+ device = features.device
+ batch_size = features.size(0)
+
+ prev = torch.zeros(batch_size, 256, device=device)
+ exc_mem = torch.zeros(batch_size, 256, device=device)
+ nb_pre_frames = pre.size(1)//self.frame_size if pre is not None else 0
+
+ states = (
+ torch.zeros(batch_size, 160, device=device),
+ torch.zeros(batch_size, 128, device=device),
+ torch.zeros(batch_size, 128, device=device),
+ torch.zeros(batch_size, (2*self.subframe_size+80+4)*1, device=device)
+ )
+
+ sig = torch.zeros((batch_size, 0), device=device)
+ cond = self.cond_net(features, period)
+ if pre is not None:
+ exc_mem[:,-self.frame_size:] = pre[:, :self.frame_size]
+ start = 1 if nb_pre_frames>0 else 0
+ for n in range(start, nb_frames+nb_pre_frames):
+ for k in range(self.nb_subframes):
+ pos = n*self.frame_size + k*self.subframe_size
+ #print("now: ", preal.shape, prev.shape, sig_in.shape)
+ pitch = period[:, 3+n]
+ gain = .03*10**(0.5*features[:, 3+n, 0:1]/np.sqrt(18.0))
+ #gain = gain[:,:,None]
+ out, exc_mem, prev, states = self.sig_net(cond[:, n, k*80:(k+1)*80], prev, exc_mem, pitch, states, gain=gain)
+
+ if n < nb_pre_frames:
+ out = pre[:, pos:pos+self.subframe_size]
+ exc_mem[:,-self.subframe_size:] = out
+ else:
+ sig = torch.cat([sig, out], 1)
+
+ states = [s.detach() for s in states]
+ return sig, states
diff --git a/dnn/torch/fargan/filters.py b/dnn/torch/fargan/filters.py
new file mode 100644
index 00000000..8ec97ea6
--- /dev/null
+++ b/dnn/torch/fargan/filters.py
@@ -0,0 +1,46 @@
+import torch
+from torch import nn
+import torch.nn.functional as F
+import math
+
+def toeplitz_from_filter(a):
+ device = a.device
+ L = a.size(-1)
+ size0 = (*(a.shape[:-1]), L, L+1)
+ size = (*(a.shape[:-1]), L, L)
+ rnge = torch.arange(0, L, dtype=torch.int64, device=device)
+ z = torch.tensor(0, device=device)
+ idx = torch.maximum(rnge[:,None] - rnge[None,:] + 1, z)
+ a = torch.cat([a[...,:1]*0, a], -1)
+ #print(a)
+ a = a[...,None,:]
+ #print(idx)
+ a = torch.broadcast_to(a, size0)
+ idx = torch.broadcast_to(idx, size)
+ #print(idx)
+ return torch.gather(a, -1, idx)
+
+def filter_iir_response(a, N):
+ device = a.device
+ L = a.size(-1)
+ ar = a.flip(dims=(2,))
+ size = (*(a.shape[:-1]), N)
+ R = torch.zeros(size, device=device)
+ R[:,:,0] = torch.ones((a.shape[:-1]), device=device)
+ for i in range(1, L):
+ R[:,:,i] = - torch.sum(ar[:,:,L-i-1:-1] * R[:,:,:i], axis=-1)
+ #R[:,:,i] = - torch.einsum('ijk,ijk->ij', ar[:,:,L-i-1:-1], R[:,:,:i])
+ for i in range(L, N):
+ R[:,:,i] = - torch.sum(ar[:,:,:-1] * R[:,:,i-L+1:i], axis=-1)
+ #R[:,:,i] = - torch.einsum('ijk,ijk->ij', ar[:,:,:-1], R[:,:,i-L+1:i])
+ return R
+
+if __name__ == '__main__':
+ #a = torch.tensor([ [[1, -.9, 0.02], [1, -.8, .01]], [[1, .9, 0], [1, .8, 0]]])
+ a = torch.tensor([ [[1, -.9, 0.02], [1, -.8, .01]]])
+ A = toeplitz_from_filter(a)
+ #print(A)
+ R = filter_iir_response(a, 5)
+
+ RA = toeplitz_from_filter(R)
+ print(RA)
diff --git a/dnn/torch/fargan/rc.py b/dnn/torch/fargan/rc.py
new file mode 100644
index 00000000..7f67016a
--- /dev/null
+++ b/dnn/torch/fargan/rc.py
@@ -0,0 +1,29 @@
+import torch
+
+
+
+def rc2lpc(rc):
+ order = rc.shape[-1]
+ lpc=rc[...,0:1]
+ for i in range(1, order):
+ lpc = torch.cat([lpc + rc[...,i:i+1]*torch.flip(lpc,dims=(-1,)), rc[...,i:i+1]], -1)
+ #print("to:", lpc)
+ return lpc
+
+def lpc2rc(lpc):
+ order = lpc.shape[-1]
+ rc = lpc[...,-1:]
+ for i in range(order-1, 0, -1):
+ ki = lpc[...,-1:]
+ lpc = lpc[...,:-1]
+ lpc = (lpc - ki*torch.flip(lpc,dims=(-1,)))/(1 - ki*ki)
+ rc = torch.cat([lpc[...,-1:] , rc], -1)
+ return rc
+
+if __name__ == "__main__":
+ rc = torch.tensor([[.5, -.5, .6, -.6]])
+ print(rc)
+ lpc = rc2lpc(rc)
+ print(lpc)
+ rc2 = lpc2rc(lpc)
+ print(rc2)
diff --git a/dnn/torch/fargan/stft_loss.py b/dnn/torch/fargan/stft_loss.py
new file mode 100644
index 00000000..8c904054
--- /dev/null
+++ b/dnn/torch/fargan/stft_loss.py
@@ -0,0 +1,186 @@
+"""STFT-based Loss modules."""
+
+import torch
+import torch.nn.functional as F
+import numpy as np
+import torchaudio
+
+
+def stft(x, fft_size, hop_size, win_length, window):
+ """Perform STFT and convert to magnitude spectrogram.
+ Args:
+ x (Tensor): Input signal tensor (B, T).
+ fft_size (int): FFT size.
+ hop_size (int): Hop size.
+ win_length (int): Window length.
+ window (str): Window function type.
+ Returns:
+ Tensor: Magnitude spectrogram (B, #frames, fft_size // 2 + 1).
+ """
+
+ #x_stft = torch.stft(x, fft_size, hop_size, win_length, window, return_complex=False)
+ #real = x_stft[..., 0]
+ #imag = x_stft[..., 1]
+
+ # (kan-bayashi): clamp is needed to avoid nan or inf
+ #return torchaudio.functional.amplitude_to_DB(torch.abs(x_stft),db_multiplier=0.0, multiplier=20,amin=1e-05,top_db=80)
+ #return torch.clamp(torch.abs(x_stft), min=1e-7)
+
+ x_stft = torch.stft(x, fft_size, hop_size, win_length, window, return_complex=True)
+ return torch.clamp(torch.abs(x_stft), min=1e-7)
+
+class SpectralConvergenceLoss(torch.nn.Module):
+ """Spectral convergence loss module."""
+
+ def __init__(self):
+ """Initilize spectral convergence loss module."""
+ super(SpectralConvergenceLoss, self).__init__()
+
+ def forward(self, x_mag, y_mag):
+ """Calculate forward propagation.
+ Args:
+ x_mag (Tensor): Magnitude spectrogram of predicted signal (B, #frames, #freq_bins).
+ y_mag (Tensor): Magnitude spectrogram of groundtruth signal (B, #frames, #freq_bins).
+ Returns:
+ Tensor: Spectral convergence loss value.
+ """
+ x_mag = torch.sqrt(x_mag)
+ y_mag = torch.sqrt(y_mag)
+ return torch.norm(y_mag - x_mag, p=1) / torch.norm(y_mag, p=1)
+
+class LogSTFTMagnitudeLoss(torch.nn.Module):
+ """Log STFT magnitude loss module."""
+
+ def __init__(self):
+ """Initilize los STFT magnitude loss module."""
+ super(LogSTFTMagnitudeLoss, self).__init__()
+
+ def forward(self, x, y):
+ """Calculate forward propagation.
+ Args:
+ x_mag (Tensor): Magnitude spectrogram of predicted signal (B, #frames, #freq_bins).
+ y_mag (Tensor): Magnitude spectrogram of groundtruth signal (B, #frames, #freq_bins).
+ Returns:
+ Tensor: Log STFT magnitude loss value.
+ """
+ #F.l1_loss(torch.sqrt(y_mag), torch.sqrt(x_mag)) +
+ #F.l1_loss(torchaudio.functional.amplitude_to_DB(y_mag,db_multiplier=0.0, multiplier=20,amin=1e-05,top_db=80),\
+ #torchaudio.functional.amplitude_to_DB(x_mag,db_multiplier=0.0, multiplier=20,amin=1e-05,top_db=80))
+
+ #y_mag[:,:y_mag.size(1)//2,:] = y_mag[:,:y_mag.size(1)//2,:] *0.0
+
+ #return F.l1_loss(torch.log(y_mag) + torch.sqrt(y_mag), torch.log(x_mag) + torch.sqrt(x_mag))
+
+ #return F.l1_loss(y_mag, x_mag)
+
+ error_loss = F.l1_loss(y, x) #+ F.l1_loss(torch.sqrt(y), torch.sqrt(x))#F.l1_loss(torch.log(y), torch.log(x))#
+
+ #x = torch.log(x)
+ #y = torch.log(y)
+ #x = x.permute(0,2,1).contiguous()
+ #y = y.permute(0,2,1).contiguous()
+
+ '''mean_x = torch.mean(x, dim=1, keepdim=True)
+ mean_y = torch.mean(y, dim=1, keepdim=True)
+
+ var_x = torch.var(x, dim=1, keepdim=True)
+ var_y = torch.var(y, dim=1, keepdim=True)
+
+ std_x = torch.std(x, dim=1, keepdim=True)
+ std_y = torch.std(y, dim=1, keepdim=True)
+
+ x_minus_mean = x - mean_x
+ y_minus_mean = y - mean_y
+
+ pearson_corr = torch.sum(x_minus_mean * y_minus_mean, dim=1, keepdim=True) / \
+ (torch.sqrt(torch.sum(x_minus_mean ** 2, dim=1, keepdim=True) + 1e-7) * \
+ torch.sqrt(torch.sum(y_minus_mean ** 2, dim=1, keepdim=True) + 1e-7))
+
+ numerator = 2.0 * pearson_corr * std_x * std_y
+ denominator = var_x + var_y + (mean_y - mean_x)**2
+
+ ccc = numerator/denominator
+
+ ccc_loss = F.l1_loss(1.0 - ccc, torch.zeros_like(ccc))'''
+
+ return error_loss #+ ccc_loss#+ ccc_loss
+
+
+class STFTLoss(torch.nn.Module):
+ """STFT loss module."""
+
+ def __init__(self, device, fft_size=1024, shift_size=120, win_length=600, window="hann_window"):
+ """Initialize STFT loss module."""
+ super(STFTLoss, self).__init__()
+ self.fft_size = fft_size
+ self.shift_size = shift_size
+ self.win_length = win_length
+ self.window = getattr(torch, window)(win_length).to(device)
+ self.spectral_convergenge_loss = SpectralConvergenceLoss()
+ self.log_stft_magnitude_loss = LogSTFTMagnitudeLoss()
+
+ def forward(self, x, y):
+ """Calculate forward propagation.
+ Args:
+ x (Tensor): Predicted signal (B, T).
+ y (Tensor): Groundtruth signal (B, T).
+ Returns:
+ Tensor: Spectral convergence loss value.
+ Tensor: Log STFT magnitude loss value.
+ """
+ x_mag = stft(x, self.fft_size, self.shift_size, self.win_length, self.window)
+ y_mag = stft(y, self.fft_size, self.shift_size, self.win_length, self.window)
+ sc_loss = self.spectral_convergenge_loss(x_mag, y_mag)
+ mag_loss = self.log_stft_magnitude_loss(x_mag, y_mag)
+
+ return sc_loss, mag_loss
+
+
+class MultiResolutionSTFTLoss(torch.nn.Module):
+
+ '''def __init__(self,
+ device,
+ fft_sizes=[2048, 1024, 512, 256, 128, 64],
+ hop_sizes=[512, 256, 128, 64, 32, 16],
+ win_lengths=[2048, 1024, 512, 256, 128, 64],
+ window="hann_window"):'''
+
+ '''def __init__(self,
+ device,
+ fft_sizes=[2048, 1024, 512, 256, 128, 64],
+ hop_sizes=[256, 128, 64, 32, 16, 8],
+ win_lengths=[1024, 512, 256, 128, 64, 32],
+ window="hann_window"):'''
+
+ def __init__(self,
+ device,
+ fft_sizes=[2560, 1280, 640, 320, 160, 80],
+ hop_sizes=[640, 320, 160, 80, 40, 20],
+ win_lengths=[2560, 1280, 640, 320, 160, 80],
+ window="hann_window"):
+
+ super(MultiResolutionSTFTLoss, self).__init__()
+ assert len(fft_sizes) == len(hop_sizes) == len(win_lengths)
+ self.stft_losses = torch.nn.ModuleList()
+ for fs, ss, wl in zip(fft_sizes, hop_sizes, win_lengths):
+ self.stft_losses += [STFTLoss(device, fs, ss, wl, window)]
+
+ def forward(self, x, y):
+ """Calculate forward propagation.
+ Args:
+ x (Tensor): Predicted signal (B, T).
+ y (Tensor): Groundtruth signal (B, T).
+ Returns:
+ Tensor: Multi resolution spectral convergence loss value.
+ Tensor: Multi resolution log STFT magnitude loss value.
+ """
+ sc_loss = 0.0
+ mag_loss = 0.0
+ for f in self.stft_losses:
+ sc_l, mag_l = f(x, y)
+ sc_loss += sc_l
+ #mag_loss += mag_l
+ sc_loss /= len(self.stft_losses)
+ mag_loss /= len(self.stft_losses)
+
+ return sc_loss #mag_loss #+
diff --git a/dnn/torch/fargan/test_fargan.py b/dnn/torch/fargan/test_fargan.py
new file mode 100644
index 00000000..d3aeb613
--- /dev/null
+++ b/dnn/torch/fargan/test_fargan.py
@@ -0,0 +1,128 @@
+import os
+import argparse
+import numpy as np
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+import tqdm
+
+import fargan
+from dataset import FARGANDataset
+
+nb_features = 36
+nb_used_features = 20
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('model', type=str, help='CELPNet model')
+parser.add_argument('features', type=str, help='path to feature file in .f32 format')
+parser.add_argument('output', type=str, help='path to output file (16-bit PCM)')
+
+parser.add_argument('--cuda-visible-devices', type=str, help="comma separates list of cuda visible device indices, default: CUDA_VISIBLE_DEVICES", default=None)
+
+
+model_group = parser.add_argument_group(title="model parameters")
+model_group.add_argument('--cond-size', type=int, help="first conditioning size, default: 256", default=256)
+
+args = parser.parse_args()
+
+if args.cuda_visible_devices != None:
+ os.environ['CUDA_VISIBLE_DEVICES'] = args.cuda_visible_devices
+
+
+features_file = args.features
+signal_file = args.output
+
+
+
+device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
+
+checkpoint = torch.load(args.model, map_location='cpu')
+
+model = fargan.FARGAN(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+
+
+model.load_state_dict(checkpoint['state_dict'], strict=False)
+
+features = np.reshape(np.memmap(features_file, dtype='float32', mode='r'), (1, -1, nb_features))
+lpc = features[:,4-1:-1,nb_used_features:]
+features = features[:, :, :nb_used_features]
+#periods = np.round(50*features[:,:,nb_used_features-2]+100).astype('int')
+periods = np.round(np.clip(256./2**(features[:,:,nb_used_features-2]+1.5), 32, 255)).astype('int')
+
+
+nb_frames = features.shape[1]
+#nb_frames = 1000
+gamma = checkpoint['model_kwargs']['gamma']
+
+def lpc_synthesis_one_frame(frame, filt, buffer, weighting_vector=np.ones(16)):
+
+ out = np.zeros_like(frame)
+ filt = np.flip(filt)
+
+ inp = frame[:]
+
+
+ for i in range(0, inp.shape[0]):
+
+ s = inp[i] - np.dot(buffer*weighting_vector, filt)
+
+ buffer[0] = s
+
+ buffer = np.roll(buffer, -1)
+
+ out[i] = s
+
+ return out
+
+def inverse_perceptual_weighting (pw_signal, filters, weighting_vector):
+
+ #inverse perceptual weighting= H_preemph / W(z/gamma)
+
+ signal = np.zeros_like(pw_signal)
+ buffer = np.zeros(16)
+ num_frames = pw_signal.shape[0] //160
+ assert num_frames == filters.shape[0]
+ for frame_idx in range(0, num_frames):
+
+ in_frame = pw_signal[frame_idx*160: (frame_idx+1)*160][:]
+ out_sig_frame = lpc_synthesis_one_frame(in_frame, filters[frame_idx, :], buffer, weighting_vector)
+ signal[frame_idx*160: (frame_idx+1)*160] = out_sig_frame[:]
+ buffer[:] = out_sig_frame[-16:]
+ return signal
+
+def inverse_perceptual_weighting40 (pw_signal, filters):
+
+ #inverse perceptual weighting= H_preemph / W(z/gamma)
+
+ signal = np.zeros_like(pw_signal)
+ buffer = np.zeros(16)
+ num_frames = pw_signal.shape[0] //40
+ assert num_frames == filters.shape[0]
+ for frame_idx in range(0, num_frames):
+ in_frame = pw_signal[frame_idx*40: (frame_idx+1)*40][:]
+ out_sig_frame = lpc_synthesis_one_frame(in_frame, filters[frame_idx, :], buffer)
+ signal[frame_idx*40: (frame_idx+1)*40] = out_sig_frame[:]
+ buffer[:] = out_sig_frame[-16:]
+ return signal
+
+from scipy.signal import lfilter
+
+if __name__ == '__main__':
+ model.to(device)
+ features = torch.tensor(features).to(device)
+ #lpc = torch.tensor(lpc).to(device)
+ periods = torch.tensor(periods).to(device)
+ weighting = gamma**np.arange(1, 17)
+ lpc = lpc*weighting
+ lpc = fargan.interp_lpc(torch.tensor(lpc), 4).numpy()
+
+ sig, _ = model(features, periods, nb_frames - 4)
+ #weighting_vector = np.array([gamma**i for i in range(16,0,-1)])
+ sig = sig.detach().numpy().flatten()
+ sig = lfilter(np.array([1.]), np.array([1., -.85]), sig)
+ #sig = inverse_perceptual_weighting40(sig, lpc[0,:,:])
+
+ pcm = np.round(32768*np.clip(sig, a_max=.99, a_min=-.99)).astype('int16')
+ pcm.tofile(signal_file)
diff --git a/dnn/torch/fargan/train_fargan.py b/dnn/torch/fargan/train_fargan.py
new file mode 100644
index 00000000..1b2e2009
--- /dev/null
+++ b/dnn/torch/fargan/train_fargan.py
@@ -0,0 +1,168 @@
+import os
+import argparse
+import random
+import numpy as np
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+import tqdm
+
+import fargan
+from dataset import FARGANDataset
+from stft_loss import *
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('features', type=str, help='path to feature file in .f32 format')
+parser.add_argument('signal', type=str, help='path to signal file in .s16 format')
+parser.add_argument('output', type=str, help='path to output folder')
+
+parser.add_argument('--suffix', type=str, help="model name suffix", default="")
+parser.add_argument('--cuda-visible-devices', type=str, help="comma separates list of cuda visible device indices, default: CUDA_VISIBLE_DEVICES", default=None)
+
+
+model_group = parser.add_argument_group(title="model parameters")
+model_group.add_argument('--cond-size', type=int, help="first conditioning size, default: 256", default=256)
+model_group.add_argument('--gamma', type=float, help="Use A(z/gamma), default: 0.9", default=0.9)
+
+training_group = parser.add_argument_group(title="training parameters")
+training_group.add_argument('--batch-size', type=int, help="batch size, default: 512", default=512)
+training_group.add_argument('--lr', type=float, help='learning rate, default: 1e-3', default=1e-3)
+training_group.add_argument('--epochs', type=int, help='number of training epochs, default: 20', default=20)
+training_group.add_argument('--sequence-length', type=int, help='sequence length, default: 15', default=15)
+training_group.add_argument('--lr-decay', type=float, help='learning rate decay factor, default: 1e-4', default=1e-4)
+training_group.add_argument('--initial-checkpoint', type=str, help='initial checkpoint to start training from, default: None', default=None)
+
+args = parser.parse_args()
+
+if args.cuda_visible_devices != None:
+ os.environ['CUDA_VISIBLE_DEVICES'] = args.cuda_visible_devices
+
+# checkpoints
+checkpoint_dir = os.path.join(args.output, 'checkpoints')
+checkpoint = dict()
+os.makedirs(checkpoint_dir, exist_ok=True)
+
+
+# training parameters
+batch_size = args.batch_size
+lr = args.lr
+epochs = args.epochs
+sequence_length = args.sequence_length
+lr_decay = args.lr_decay
+
+adam_betas = [0.8, 0.95]
+adam_eps = 1e-8
+features_file = args.features
+signal_file = args.signal
+
+# model parameters
+cond_size = args.cond_size
+
+
+checkpoint['batch_size'] = batch_size
+checkpoint['lr'] = lr
+checkpoint['lr_decay'] = lr_decay
+checkpoint['epochs'] = epochs
+checkpoint['sequence_length'] = sequence_length
+checkpoint['adam_betas'] = adam_betas
+
+
+device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
+
+checkpoint['model_args'] = ()
+checkpoint['model_kwargs'] = {'cond_size': cond_size, 'gamma': args.gamma}
+print(checkpoint['model_kwargs'])
+model = fargan.FARGAN(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+
+#model = fargan.FARGAN()
+#model = nn.DataParallel(model)
+
+if type(args.initial_checkpoint) != type(None):
+ checkpoint = torch.load(args.initial_checkpoint, map_location='cpu')
+ model.load_state_dict(checkpoint['state_dict'], strict=False)
+
+checkpoint['state_dict'] = model.state_dict()
+
+
+dataset = FARGANDataset(features_file, signal_file, sequence_length=sequence_length)
+dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, drop_last=True, num_workers=4)
+
+
+optimizer = torch.optim.AdamW(model.parameters(), lr=lr, betas=adam_betas, eps=adam_eps)
+
+
+# learning rate scheduler
+scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer=optimizer, lr_lambda=lambda x : 1 / (1 + lr_decay * x))
+
+states = None
+
+spect_loss = MultiResolutionSTFTLoss(device).to(device)
+
+if __name__ == '__main__':
+ model.to(device)
+
+ for epoch in range(1, epochs + 1):
+
+ running_specc = 0
+ running_cont_loss = 0
+ running_loss = 0
+
+ print(f"training epoch {epoch}...")
+ with tqdm.tqdm(dataloader, unit='batch') as tepoch:
+ for i, (features, periods, target, lpc) in enumerate(tepoch):
+ optimizer.zero_grad()
+ features = features.to(device)
+ #lpc = torch.tensor(fargan.interp_lpc(lpc.numpy(), 4))
+ #print("interp size", lpc.shape)
+ #lpc = lpc.to(device)
+ #lpc = lpc*(args.gamma**torch.arange(1,17, device=device))
+ #lpc = fargan.interp_lpc(lpc, 4)
+ periods = periods.to(device)
+ if (np.random.rand() > 0.1):
+ target = target[:, :sequence_length*160]
+ #lpc = lpc[:,:sequence_length*4,:]
+ features = features[:,:sequence_length+4,:]
+ periods = periods[:,:sequence_length+4]
+ else:
+ target=target[::2, :]
+ #lpc=lpc[::2,:]
+ features=features[::2,:]
+ periods=periods[::2,:]
+ target = target.to(device)
+ #print(target.shape, lpc.shape)
+ #target = fargan.analysis_filter(target, lpc[:,:,:], nb_subframes=1, gamma=args.gamma)
+
+ #nb_pre = random.randrange(1, 6)
+ nb_pre = 2
+ pre = target[:, :nb_pre*160]
+ sig, states = model(features, periods, target.size(1)//160 - nb_pre, pre=pre, states=None)
+ sig = torch.cat([pre, sig], -1)
+
+ cont_loss = fargan.sig_loss(target[:, nb_pre*160:nb_pre*160+160], sig[:, nb_pre*160:nb_pre*160+160])
+ specc_loss = spect_loss(sig, target.detach())
+ loss = .03*cont_loss + specc_loss
+
+ loss.backward()
+ optimizer.step()
+
+ #model.clip_weights()
+
+ scheduler.step()
+
+ running_specc += specc_loss.detach().cpu().item()
+ running_cont_loss += cont_loss.detach().cpu().item()
+
+ running_loss += loss.detach().cpu().item()
+ tepoch.set_postfix(loss=f"{running_loss/(i+1):8.5f}",
+ cont_loss=f"{running_cont_loss/(i+1):8.5f}",
+ specc=f"{running_specc/(i+1):8.5f}",
+ )
+
+ # save checkpoint
+ checkpoint_path = os.path.join(checkpoint_dir, f'fargan{args.suffix}_{epoch}.pth')
+ checkpoint['state_dict'] = model.state_dict()
+ checkpoint['loss'] = running_loss / len(dataloader)
+ checkpoint['epoch'] = epoch
+ torch.save(checkpoint, checkpoint_path)
diff --git a/dnn/torch/fwgan/dump_model_weights.py b/dnn/torch/fwgan/dump_model_weights.py
new file mode 100644
index 00000000..64f955ac
--- /dev/null
+++ b/dnn/torch/fwgan/dump_model_weights.py
@@ -0,0 +1,88 @@
+import os
+import sys
+import argparse
+
+import torch
+from torch import nn
+
+
+sys.path.append(os.path.join(os.path.split(__file__)[0], '../weight-exchange'))
+import wexchange.torch
+
+from models import model_dict
+
+unquantized = [
+ 'bfcc_with_corr_upsampler.fc',
+ 'cont_net.0',
+ 'fwc6.cont_fc.0',
+ 'fwc6.fc.0',
+ 'fwc6.fc.1.gate',
+ 'fwc7.cont_fc.0',
+ 'fwc7.fc.0',
+ 'fwc7.fc.1.gate'
+]
+
+description=f"""
+This is an unsafe dumping script for FWGAN models. It assumes that all weights are included in Linear, Conv1d or GRU layer
+and will fail to export any other weights.
+
+Furthermore, the quanitze option relies on the following explicit list of layers to be excluded:
+{unquantized}.
+
+Modify this script manually if adjustments are needed.
+"""
+
+parser = argparse.ArgumentParser(description=description)
+parser.add_argument('model', choices=['fwgan400', 'fwgan500'], help='model name')
+parser.add_argument('weightfile', type=str, help='weight file path')
+parser.add_argument('export_folder', type=str)
+parser.add_argument('--export-filename', type=str, default='fwgan_data', help='filename for source and header file (.c and .h will be added), defaults to fwgan_data')
+parser.add_argument('--struct-name', type=str, default='FWGAN', help='name for C struct, defaults to FWGAN')
+parser.add_argument('--quantize', action='store_true', help='apply quantization')
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+ model = model_dict[args.model]()
+
+ print(f"loading weights from {args.weightfile}...")
+ saved_gen= torch.load(args.weightfile, map_location='cpu')
+ model.load_state_dict(saved_gen)
+ def _remove_weight_norm(m):
+ try:
+ torch.nn.utils.remove_weight_norm(m)
+ except ValueError: # this module didn't have weight norm
+ return
+ model.apply(_remove_weight_norm)
+
+
+ print("dumping model...")
+ quantize_model=args.quantize
+
+ output_folder = args.export_folder
+ os.makedirs(output_folder, exist_ok=True)
+
+ writer = wexchange.c_export.c_writer.CWriter(os.path.join(output_folder, args.export_filename), model_struct_name=args.struct_name)
+
+ for name, module in model.named_modules():
+
+ if quantize_model:
+ quantize=name not in unquantized
+ scale = None if quantize else 1/128
+ else:
+ quantize=False
+ scale=1/128
+
+ if isinstance(module, nn.Linear):
+ print(f"dumping linear layer {name}...")
+ wexchange.torch.dump_torch_dense_weights(writer, module, name.replace('.', '_'), quantize=quantize, scale=scale)
+
+ if isinstance(module, nn.Conv1d):
+ print(f"dumping conv1d layer {name}...")
+ wexchange.torch.dump_torch_conv1d_weights(writer, module, name.replace('.', '_'), quantize=quantize, scale=scale)
+
+ if isinstance(module, nn.GRU):
+ print(f"dumping GRU layer {name}...")
+ wexchange.torch.dump_torch_gru_weights(writer, module, name.replace('.', '_'), quantize=quantize, scale=scale, recurrent_scale=scale)
+
+ writer.close()
diff --git a/dnn/torch/fwgan/inference.py b/dnn/torch/fwgan/inference.py
new file mode 100644
index 00000000..c06b68b1
--- /dev/null
+++ b/dnn/torch/fwgan/inference.py
@@ -0,0 +1,141 @@
+import os
+import time
+import torch
+import numpy as np
+from scipy import signal as si
+from scipy.io import wavfile
+import argparse
+
+from models import model_dict
+
+parser = argparse.ArgumentParser()
+parser.add_argument('model', choices=['fwgan400', 'fwgan500'], help='model name')
+parser.add_argument('weightfile', type=str, help='weight file')
+parser.add_argument('input', type=str, help='input: feature file or folder with feature files')
+parser.add_argument('output', type=str, help='output: wav file name or folder name, depending on input')
+
+
+########################### Signal Processing Layers ###########################
+
+def preemphasis(x, coef= -0.85):
+
+ return si.lfilter(np.array([1.0, coef]), np.array([1.0]), x).astype('float32')
+
+def deemphasis(x, coef= -0.85):
+
+ return si.lfilter(np.array([1.0]), np.array([1.0, coef]), x).astype('float32')
+
+gamma = 0.92
+weighting_vector = np.array([gamma**i for i in range(16,0,-1)])
+
+
+def lpc_synthesis_one_frame(frame, filt, buffer, weighting_vector=np.ones(16)):
+
+ out = np.zeros_like(frame)
+
+ filt = np.flip(filt)
+
+ inp = frame[:]
+
+
+ for i in range(0, inp.shape[0]):
+
+ s = inp[i] - np.dot(buffer*weighting_vector, filt)
+
+ buffer[0] = s
+
+ buffer = np.roll(buffer, -1)
+
+ out[i] = s
+
+ return out
+
+def inverse_perceptual_weighting (pw_signal, filters, weighting_vector):
+
+ #inverse perceptual weighting= H_preemph / W(z/gamma)
+
+ pw_signal = preemphasis(pw_signal)
+
+ signal = np.zeros_like(pw_signal)
+ buffer = np.zeros(16)
+ num_frames = pw_signal.shape[0] //160
+ assert num_frames == filters.shape[0]
+
+ for frame_idx in range(0, num_frames):
+
+ in_frame = pw_signal[frame_idx*160: (frame_idx+1)*160][:]
+ out_sig_frame = lpc_synthesis_one_frame(in_frame, filters[frame_idx, :], buffer, weighting_vector)
+ signal[frame_idx*160: (frame_idx+1)*160] = out_sig_frame[:]
+ buffer[:] = out_sig_frame[-16:]
+
+ return signal
+
+
+def process_item(generator, feature_filename, output_filename, verbose=False):
+
+ feat = np.memmap(feature_filename, dtype='float32', mode='r')
+
+ num_feat_frames = len(feat) // 36
+ feat = np.reshape(feat, (num_feat_frames, 36))
+
+ bfcc = np.copy(feat[:, :18])
+ corr = np.copy(feat[:, 19:20]) + 0.5
+ bfcc_with_corr = torch.from_numpy(np.hstack((bfcc, corr))).type(torch.FloatTensor).unsqueeze(0)#.to(device)
+
+ period = torch.from_numpy((0.1 + 50 * np.copy(feat[:, 18:19]) + 100)\
+ .astype('int32')).type(torch.long).view(1,-1)#.to(device)
+
+ lpc_filters = np.copy(feat[:, -16:])
+
+ start_time = time.time()
+ x1 = generator(period, bfcc_with_corr, torch.zeros(1,320)) #this means the vocoder runs in complete synthesis mode with zero history audio frames
+ end_time = time.time()
+ total_time = end_time - start_time
+ x1 = x1.squeeze(1).squeeze(0).detach().cpu().numpy()
+ gen_seconds = len(x1)/16000
+ out = deemphasis(inverse_perceptual_weighting(x1, lpc_filters, weighting_vector))
+ if verbose:
+ print(f"Took {total_time:.3f}s to generate {len(x1)} samples ({gen_seconds}s) -> {gen_seconds/total_time:.2f}x real time")
+
+ out = np.clip(np.round(2**15 * out), -2**15, 2**15 -1).astype(np.int16)
+ wavfile.write(output_filename, 16000, out)
+
+
+########################### The inference loop over folder containing lpcnet feature files #################################
+if __name__ == "__main__":
+
+ args = parser.parse_args()
+
+ generator = model_dict[args.model]()
+
+
+ #Load the FWGAN500Hz Checkpoint
+ saved_gen= torch.load(args.weightfile, map_location='cpu')
+ generator.load_state_dict(saved_gen)
+
+ #this is just to remove the weight_norm from the model layers as it's no longer needed
+ def _remove_weight_norm(m):
+ try:
+ torch.nn.utils.remove_weight_norm(m)
+ except ValueError: # this module didn't have weight norm
+ return
+ generator.apply(_remove_weight_norm)
+
+ #enable inference mode
+ generator = generator.eval()
+
+ print('Successfully loaded the generator model ... start generation:')
+
+ if os.path.isdir(args.input):
+
+ os.makedirs(args.output, exist_ok=True)
+
+ for fn in os.listdir(args.input):
+ print(f"processing input {fn}...")
+ feature_filename = os.path.join(args.input, fn)
+ output_filename = os.path.join(args.output, os.path.splitext(fn)[0] + f"_{args.model}.wav")
+ process_item(generator, feature_filename, output_filename)
+ else:
+ process_item(generator, args.input, args.output)
+
+ print("Finished!") \ No newline at end of file
diff --git a/dnn/torch/fwgan/models/__init__.py b/dnn/torch/fwgan/models/__init__.py
new file mode 100644
index 00000000..d52a6eb0
--- /dev/null
+++ b/dnn/torch/fwgan/models/__init__.py
@@ -0,0 +1,7 @@
+from .fwgan400 import FWGAN400ContLarge
+from .fwgan500 import FWGAN500Cont
+
+model_dict = {
+ 'fwgan400': FWGAN400ContLarge,
+ 'fwgan500': FWGAN500Cont
+} \ No newline at end of file
diff --git a/dnn/torch/fwgan/models/fwgan400.py b/dnn/torch/fwgan/models/fwgan400.py
new file mode 100644
index 00000000..84d9849e
--- /dev/null
+++ b/dnn/torch/fwgan/models/fwgan400.py
@@ -0,0 +1,308 @@
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from torch.nn.utils import weight_norm
+import numpy as np
+
+which_norm = weight_norm
+
+#################### Definition of basic model components ####################
+
+#Convolutional layer with 1 frame look-ahead (used for feature PreCondNet)
+class ConvLookahead(nn.Module):
+ def __init__(self, in_ch, out_ch, kernel_size, dilation=1, groups=1, bias= False):
+ super(ConvLookahead, self).__init__()
+ torch.manual_seed(5)
+
+ self.padding_left = (kernel_size - 2) * dilation
+ self.padding_right = 1 * dilation
+
+ self.conv = which_norm(nn.Conv1d(in_ch,out_ch,kernel_size,dilation=dilation, groups=groups, bias= bias))
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d) or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x):
+
+ x = F.pad(x,(self.padding_left, self.padding_right))
+ conv_out = self.conv(x)
+ return conv_out
+
+#(modified) GLU Activation layer definition
+class GLU(nn.Module):
+ def __init__(self, feat_size):
+ super(GLU, self).__init__()
+
+ torch.manual_seed(5)
+
+ self.gate = which_norm(nn.Linear(feat_size, feat_size, bias=False))
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d)\
+ or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x):
+
+ out = torch.tanh(x) * torch.sigmoid(self.gate(x))
+
+ return out
+
+#GRU layer definition
+class ContForwardGRU(nn.Module):
+ def __init__(self, input_size, hidden_size, num_layers=1):
+ super(ContForwardGRU, self).__init__()
+
+ torch.manual_seed(5)
+
+ self.hidden_size = hidden_size
+
+ self.cont_fc = nn.Sequential(which_norm(nn.Linear(64, self.hidden_size, bias=False)),
+ nn.Tanh())
+
+ self.gru = nn.GRU(input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True,\
+ bias=False)
+
+ self.nl = GLU(self.hidden_size)
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d) or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x, x0):
+
+ self.gru.flatten_parameters()
+
+ h0 = self.cont_fc(x0).unsqueeze(0)
+
+ output, h0 = self.gru(x, h0)
+
+ return self.nl(output)
+
+# Framewise convolution layer definition
+class ContFramewiseConv(torch.nn.Module):
+
+ def __init__(self, frame_len, out_dim, frame_kernel_size=3, act='glu', causal=True):
+
+ super(ContFramewiseConv, self).__init__()
+ torch.manual_seed(5)
+
+ self.frame_kernel_size = frame_kernel_size
+ self.frame_len = frame_len
+
+ if (causal == True) or (self.frame_kernel_size == 2):
+
+ self.required_pad_left = (self.frame_kernel_size - 1) * self.frame_len
+ self.required_pad_right = 0
+
+ self.cont_fc = nn.Sequential(which_norm(nn.Linear(64, self.required_pad_left, bias=False)),
+ nn.Tanh()
+ )
+
+ else:
+
+ self.required_pad_left = (self.frame_kernel_size - 1)//2 * self.frame_len
+ self.required_pad_right = (self.frame_kernel_size - 1)//2 * self.frame_len
+
+ self.fc_input_dim = self.frame_kernel_size * self.frame_len
+ self.fc_out_dim = out_dim
+
+ if act=='glu':
+ self.fc = nn.Sequential(which_norm(nn.Linear(self.fc_input_dim, self.fc_out_dim, bias=False)),
+ GLU(self.fc_out_dim)
+ )
+ if act=='tanh':
+ self.fc = nn.Sequential(which_norm(nn.Linear(self.fc_input_dim, self.fc_out_dim, bias=False)),
+ nn.Tanh()
+ )
+
+ self.init_weights()
+
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d) or isinstance(m, nn.Linear) or\
+ isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x, x0):
+
+ if self.frame_kernel_size == 1:
+ return self.fc(x)
+
+ x_flat = x.reshape(x.size(0),1,-1)
+ pad = self.cont_fc(x0).view(x0.size(0),1,-1)
+ x_flat_padded = torch.cat((pad, x_flat), dim=-1).unsqueeze(2)
+
+ x_flat_padded_unfolded = F.unfold(x_flat_padded,\
+ kernel_size= (1,self.fc_input_dim), stride=self.frame_len).permute(0,2,1).contiguous()
+
+ out = self.fc(x_flat_padded_unfolded)
+ return out
+
+# A fully-connected based upsampling layer definition
+class UpsampleFC(nn.Module):
+ def __init__(self, in_ch, out_ch, upsample_factor):
+ super(UpsampleFC, self).__init__()
+ torch.manual_seed(5)
+
+ self.in_ch = in_ch
+ self.out_ch = out_ch
+ self.upsample_factor = upsample_factor
+ self.fc = nn.Linear(in_ch, out_ch * upsample_factor, bias=False)
+ self.nl = nn.Tanh()
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d) or\
+ isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x):
+
+ batch_size = x.size(0)
+ x = x.permute(0, 2, 1)
+ x = self.nl(self.fc(x))
+ x = x.reshape((batch_size, -1, self.out_ch))
+ x = x.permute(0, 2, 1)
+ return x
+
+########################### The complete model definition #################################
+
+class FWGAN400ContLarge(nn.Module):
+ def __init__(self):
+ super().__init__()
+ torch.manual_seed(5)
+
+ self.bfcc_with_corr_upsampler = UpsampleFC(19,80,4)
+
+ self.feat_in_conv1 = ConvLookahead(160,256,kernel_size=5)
+ self.feat_in_nl1 = GLU(256)
+
+ self.cont_net = nn.Sequential(which_norm(nn.Linear(321, 160, bias=False)),
+ nn.Tanh(),
+ which_norm(nn.Linear(160, 160, bias=False)),
+ nn.Tanh(),
+ which_norm(nn.Linear(160, 80, bias=False)),
+ nn.Tanh(),
+ which_norm(nn.Linear(80, 80, bias=False)),
+ nn.Tanh(),
+ which_norm(nn.Linear(80, 64, bias=False)),
+ nn.Tanh(),
+ which_norm(nn.Linear(64, 64, bias=False)),
+ nn.Tanh())
+
+ self.rnn = ContForwardGRU(256,256)
+
+ self.fwc1 = ContFramewiseConv(256, 256)
+ self.fwc2 = ContFramewiseConv(256, 128)
+ self.fwc3 = ContFramewiseConv(128, 128)
+ self.fwc4 = ContFramewiseConv(128, 64)
+ self.fwc5 = ContFramewiseConv(64, 64)
+ self.fwc6 = ContFramewiseConv(64, 40)
+ self.fwc7 = ContFramewiseConv(40, 40)
+
+ self.init_weights()
+ self.count_parameters()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d) or isinstance(m, nn.Linear) or\
+ isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def count_parameters(self):
+ num_params = sum(p.numel() for p in self.parameters() if p.requires_grad)
+ print(f"Total number of {self.__class__.__name__} network parameters = {num_params}\n")
+
+ def create_phase_signals(self, periods):
+
+ batch_size = periods.size(0)
+ progression = torch.arange(1, 160 + 1, dtype=periods.dtype, device=periods.device).view((1, -1))
+ progression = torch.repeat_interleave(progression, batch_size, 0)
+
+ phase0 = torch.zeros(batch_size, dtype=periods.dtype, device=periods.device).unsqueeze(-1)
+ chunks = []
+ for sframe in range(periods.size(1)):
+ f = (2.0 * torch.pi / periods[:, sframe]).unsqueeze(-1)
+
+ chunk_sin = torch.sin(f * progression + phase0)
+ chunk_sin = chunk_sin.reshape(chunk_sin.size(0),-1,40)
+
+ chunk_cos = torch.cos(f * progression + phase0)
+ chunk_cos = chunk_cos.reshape(chunk_cos.size(0),-1,40)
+
+ chunk = torch.cat((chunk_sin, chunk_cos), dim = -1)
+
+ phase0 = phase0 + 160 * f
+
+ chunks.append(chunk)
+
+ phase_signals = torch.cat(chunks, dim=1)
+
+ return phase_signals
+
+
+ def gain_multiply(self, x, c0):
+
+ gain = 10**(0.5*c0/np.sqrt(18.0))
+ gain = torch.repeat_interleave(gain, 160, dim=-1)
+ gain = gain.reshape(gain.size(0),1,-1).squeeze(1)
+
+ return x * gain
+
+ def forward(self, pitch_period, bfcc_with_corr, x0):
+
+ norm_x0 = torch.norm(x0,2, dim=-1, keepdim=True)
+ x0 = x0 / torch.sqrt((1e-8) + norm_x0**2)
+ x0 = torch.cat((torch.log(norm_x0 + 1e-7), x0), dim=-1)
+
+ p_embed = self.create_phase_signals(pitch_period).permute(0, 2, 1).contiguous()
+
+ envelope = self.bfcc_with_corr_upsampler(bfcc_with_corr.permute(0,2,1).contiguous())
+
+ feat_in = torch.cat((p_embed , envelope), dim=1)
+
+ wav_latent1 = self.feat_in_nl1(self.feat_in_conv1(feat_in).permute(0,2,1).contiguous())
+
+ cont_latent = self.cont_net(x0)
+
+ rnn_out = self.rnn(wav_latent1, cont_latent)
+
+ fwc1_out = self.fwc1(rnn_out, cont_latent)
+
+ fwc2_out = self.fwc2(fwc1_out, cont_latent)
+
+ fwc3_out = self.fwc3(fwc2_out, cont_latent)
+
+ fwc4_out = self.fwc4(fwc3_out, cont_latent)
+
+ fwc5_out = self.fwc5(fwc4_out, cont_latent)
+
+ fwc6_out = self.fwc6(fwc5_out, cont_latent)
+
+ fwc7_out = self.fwc7(fwc6_out, cont_latent)
+
+ waveform = fwc7_out.reshape(fwc7_out.size(0),1,-1).squeeze(1)
+
+ waveform = self.gain_multiply(waveform,bfcc_with_corr[:,:,:1])
+
+ return waveform \ No newline at end of file
diff --git a/dnn/torch/fwgan/models/fwgan500.py b/dnn/torch/fwgan/models/fwgan500.py
new file mode 100644
index 00000000..2c6dea5f
--- /dev/null
+++ b/dnn/torch/fwgan/models/fwgan500.py
@@ -0,0 +1,260 @@
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from torch.nn.utils import weight_norm
+import numpy as np
+
+
+which_norm = weight_norm
+
+#################### Definition of basic model components ####################
+
+#Convolutional layer with 1 frame look-ahead (used for feature PreCondNet)
+class ConvLookahead(nn.Module):
+ def __init__(self, in_ch, out_ch, kernel_size, dilation=1, groups=1, bias= False):
+ super(ConvLookahead, self).__init__()
+ torch.manual_seed(5)
+
+ self.padding_left = (kernel_size - 2) * dilation
+ self.padding_right = 1 * dilation
+
+ self.conv = which_norm(nn.Conv1d(in_ch,out_ch,kernel_size,dilation=dilation, groups=groups, bias= bias))
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d) or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x):
+
+ x = F.pad(x,(self.padding_left, self.padding_right))
+ conv_out = self.conv(x)
+ return conv_out
+
+#(modified) GLU Activation layer definition
+class GLU(nn.Module):
+ def __init__(self, feat_size):
+ super(GLU, self).__init__()
+
+ torch.manual_seed(5)
+
+ self.gate = which_norm(nn.Linear(feat_size, feat_size, bias=False))
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d)\
+ or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x):
+
+ out = torch.tanh(x) * torch.sigmoid(self.gate(x))
+
+ return out
+
+#GRU layer definition
+class ContForwardGRU(nn.Module):
+ def __init__(self, input_size, hidden_size, num_layers=1):
+ super(ContForwardGRU, self).__init__()
+
+ torch.manual_seed(5)
+
+ self.hidden_size = hidden_size
+
+ #This is to initialize the layer with history audio samples for continuation.
+ self.cont_fc = nn.Sequential(which_norm(nn.Linear(320, self.hidden_size, bias=False)),
+ nn.Tanh())
+
+ self.gru = nn.GRU(input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True,\
+ bias=False)
+
+ self.nl = GLU(self.hidden_size)
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d) or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x, x0):
+
+ self.gru.flatten_parameters()
+
+ h0 = self.cont_fc(x0).unsqueeze(0)
+
+ output, h0 = self.gru(x, h0)
+
+ return self.nl(output)
+
+# Framewise convolution layer definition
+class ContFramewiseConv(torch.nn.Module):
+
+ def __init__(self, frame_len, out_dim, frame_kernel_size=3, act='glu', causal=True):
+
+ super(ContFramewiseConv, self).__init__()
+ torch.manual_seed(5)
+
+ self.frame_kernel_size = frame_kernel_size
+ self.frame_len = frame_len
+
+ if (causal == True) or (self.frame_kernel_size == 2):
+
+ self.required_pad_left = (self.frame_kernel_size - 1) * self.frame_len
+ self.required_pad_right = 0
+
+ #This is to initialize the layer with history audio samples for continuation.
+ self.cont_fc = nn.Sequential(which_norm(nn.Linear(320, self.required_pad_left, bias=False)),
+ nn.Tanh()
+ )
+
+ else:
+ #This means non-causal frame-wise convolution. We don't use it at the moment
+ self.required_pad_left = (self.frame_kernel_size - 1)//2 * self.frame_len
+ self.required_pad_right = (self.frame_kernel_size - 1)//2 * self.frame_len
+
+ self.fc_input_dim = self.frame_kernel_size * self.frame_len
+ self.fc_out_dim = out_dim
+
+ if act=='glu':
+ self.fc = nn.Sequential(which_norm(nn.Linear(self.fc_input_dim, self.fc_out_dim, bias=False)),
+ GLU(self.fc_out_dim)
+ )
+ if act=='tanh':
+ self.fc = nn.Sequential(which_norm(nn.Linear(self.fc_input_dim, self.fc_out_dim, bias=False)),
+ nn.Tanh()
+ )
+
+ self.init_weights()
+
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d) or isinstance(m, nn.Linear) or\
+ isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x, x0):
+
+ if self.frame_kernel_size == 1:
+ return self.fc(x)
+
+ x_flat = x.reshape(x.size(0),1,-1)
+ pad = self.cont_fc(x0).view(x0.size(0),1,-1)
+ x_flat_padded = torch.cat((pad, x_flat), dim=-1).unsqueeze(2)
+
+ x_flat_padded_unfolded = F.unfold(x_flat_padded,\
+ kernel_size= (1,self.fc_input_dim), stride=self.frame_len).permute(0,2,1).contiguous()
+
+ out = self.fc(x_flat_padded_unfolded)
+ return out
+
+########################### The complete model definition #################################
+
+class FWGAN500Cont(nn.Module):
+ def __init__(self):
+ super().__init__()
+ torch.manual_seed(5)
+
+ #PrecondNet:
+ self.bfcc_with_corr_upsampler = nn.Sequential(nn.ConvTranspose1d(19,64,kernel_size=5,stride=5,padding=0,\
+ bias=False),
+ nn.Tanh())
+
+ self.feat_in_conv = ConvLookahead(128,256,kernel_size=5)
+ self.feat_in_nl = GLU(256)
+
+ #GRU:
+ self.rnn = ContForwardGRU(256,256)
+
+ #Frame-wise convolution stack:
+ self.fwc1 = ContFramewiseConv(256, 256)
+ self.fwc2 = ContFramewiseConv(256, 128)
+ self.fwc3 = ContFramewiseConv(128, 128)
+ self.fwc4 = ContFramewiseConv(128, 64)
+ self.fwc5 = ContFramewiseConv(64, 64)
+ self.fwc6 = ContFramewiseConv(64, 32)
+ self.fwc7 = ContFramewiseConv(32, 32, act='tanh')
+
+ self.init_weights()
+ self.count_parameters()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d) or isinstance(m, nn.Linear) or\
+ isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def count_parameters(self):
+ num_params = sum(p.numel() for p in self.parameters() if p.requires_grad)
+ print(f"Total number of {self.__class__.__name__} network parameters = {num_params}\n")
+
+ def create_phase_signals(self, periods):
+
+ batch_size = periods.size(0)
+ progression = torch.arange(1, 160 + 1, dtype=periods.dtype, device=periods.device).view((1, -1))
+ progression = torch.repeat_interleave(progression, batch_size, 0)
+
+ phase0 = torch.zeros(batch_size, dtype=periods.dtype, device=periods.device).unsqueeze(-1)
+ chunks = []
+ for sframe in range(periods.size(1)):
+ f = (2.0 * torch.pi / periods[:, sframe]).unsqueeze(-1)
+
+ chunk_sin = torch.sin(f * progression + phase0)
+ chunk_sin = chunk_sin.reshape(chunk_sin.size(0),-1,32)
+
+ chunk_cos = torch.cos(f * progression + phase0)
+ chunk_cos = chunk_cos.reshape(chunk_cos.size(0),-1,32)
+
+ chunk = torch.cat((chunk_sin, chunk_cos), dim = -1)
+
+ phase0 = phase0 + 160 * f
+
+ chunks.append(chunk)
+
+ phase_signals = torch.cat(chunks, dim=1)
+
+ return phase_signals
+
+
+ def gain_multiply(self, x, c0):
+
+ gain = 10**(0.5*c0/np.sqrt(18.0))
+ gain = torch.repeat_interleave(gain, 160, dim=-1)
+ gain = gain.reshape(gain.size(0),1,-1).squeeze(1)
+
+ return x * gain
+
+ def forward(self, pitch_period, bfcc_with_corr, x0):
+
+ #This should create a latent representation of shape [Batch_dim, 500 frames, 256 elemets per frame]
+ p_embed = self.create_phase_signals(pitch_period).permute(0, 2, 1).contiguous()
+ envelope = self.bfcc_with_corr_upsampler(bfcc_with_corr.permute(0,2,1).contiguous())
+ feat_in = torch.cat((p_embed , envelope), dim=1)
+ wav_latent = self.feat_in_nl(self.feat_in_conv(feat_in).permute(0,2,1).contiguous())
+
+ #Generation with continuation using history samples x0 starts from here:
+
+ rnn_out = self.rnn(wav_latent, x0)
+
+ fwc1_out = self.fwc1(rnn_out, x0)
+ fwc2_out = self.fwc2(fwc1_out, x0)
+ fwc3_out = self.fwc3(fwc2_out, x0)
+ fwc4_out = self.fwc4(fwc3_out, x0)
+ fwc5_out = self.fwc5(fwc4_out, x0)
+ fwc6_out = self.fwc6(fwc5_out, x0)
+ fwc7_out = self.fwc7(fwc6_out, x0)
+
+ waveform_unscaled = fwc7_out.reshape(fwc7_out.size(0),1,-1).squeeze(1)
+ waveform = self.gain_multiply(waveform_unscaled,bfcc_with_corr[:,:,:1])
+
+ return waveform
diff --git a/dnn/torch/lossgen/README.md b/dnn/torch/lossgen/README.md
new file mode 100644
index 00000000..55c1b442
--- /dev/null
+++ b/dnn/torch/lossgen/README.md
@@ -0,0 +1,27 @@
+#Packet loss simulator
+
+This code is an attempt at simulating better packet loss scenarios. The most common way of simulating
+packet loss is to use a random sequence where each packet loss event is uncorrelated with previous events.
+That is a simplistic model since we know that losses often occur in bursts. This model uses real data
+to build a generative model for packet loss.
+
+We use the training data provided for the Audio Deep Packet Loss Concealment Challenge, which is available at:
+
+http://plcchallenge2022pub.blob.core.windows.net/plcchallengearchive/test_train.tar.gz
+
+To create the training data, run:
+
+`./process_data.sh /<path>/test_train/train/lossy_signals/`
+
+That will create an ascii loss\_sorted.txt file with all loss data sorted in increasing packet loss
+percentage. Then just run:
+
+`python ./train_lossgen.py`
+
+to train a model
+
+To generate a sequence, run
+
+`python3 ./test_lossgen.py <checkpoint> <percentage> output.txt --length 10000`
+
+where <checkpoint> is the .pth model file and <percentage> is the amount of loss (e.g. 0.2 for 20% loss).
diff --git a/dnn/torch/lossgen/export_lossgen.py b/dnn/torch/lossgen/export_lossgen.py
new file mode 100644
index 00000000..1f7df957
--- /dev/null
+++ b/dnn/torch/lossgen/export_lossgen.py
@@ -0,0 +1,101 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../weight-exchange'))
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('checkpoint', type=str, help='model checkpoint')
+parser.add_argument('output_dir', type=str, help='output folder')
+
+args = parser.parse_args()
+
+import torch
+import numpy as np
+
+import lossgen
+from wexchange.torch import dump_torch_weights
+from wexchange.c_export import CWriter, print_vector
+
+def c_export(args, model):
+
+ message = f"Auto generated from checkpoint {os.path.basename(args.checkpoint)}"
+
+ writer = CWriter(os.path.join(args.output_dir, "lossgen_data"), message=message, model_struct_name='LossGen')
+ writer.header.write(
+f"""
+#include "opus_types.h"
+"""
+ )
+
+ dense_layers = [
+ ('dense_in', "lossgen_dense_in"),
+ ('dense_out', "lossgen_dense_out")
+ ]
+
+
+ for name, export_name in dense_layers:
+ layer = model.get_submodule(name)
+ dump_torch_weights(writer, layer, name=export_name, verbose=True, quantize=False, scale=None)
+
+
+ gru_layers = [
+ ("gru1", "lossgen_gru1"),
+ ("gru2", "lossgen_gru2"),
+ ]
+
+ max_rnn_units = max([dump_torch_weights(writer, model.get_submodule(name), export_name, verbose=True, input_sparse=False, quantize=True, scale=None, recurrent_scale=None)
+ for name, export_name in gru_layers])
+
+ writer.header.write(
+f"""
+
+#define LOSSGEN_MAX_RNN_UNITS {max_rnn_units}
+
+"""
+ )
+
+ writer.close()
+
+
+if __name__ == "__main__":
+
+ os.makedirs(args.output_dir, exist_ok=True)
+ checkpoint = torch.load(args.checkpoint, map_location='cpu')
+ model = lossgen.LossGen(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+ model.load_state_dict(checkpoint['state_dict'], strict=False)
+ #model = LossGen()
+ #checkpoint = torch.load(args.checkpoint, map_location='cpu')
+ #model.load_state_dict(checkpoint['state_dict'])
+ c_export(args, model)
diff --git a/dnn/torch/lossgen/lossgen.py b/dnn/torch/lossgen/lossgen.py
new file mode 100644
index 00000000..9025165c
--- /dev/null
+++ b/dnn/torch/lossgen/lossgen.py
@@ -0,0 +1,29 @@
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+class LossGen(nn.Module):
+ def __init__(self, gru1_size=16, gru2_size=16):
+ super(LossGen, self).__init__()
+
+ self.gru1_size = gru1_size
+ self.gru2_size = gru2_size
+ self.dense_in = nn.Linear(2, 8)
+ self.gru1 = nn.GRU(8, self.gru1_size, batch_first=True)
+ self.gru2 = nn.GRU(self.gru1_size, self.gru2_size, batch_first=True)
+ self.dense_out = nn.Linear(self.gru2_size, 1)
+
+ def forward(self, loss, perc, states=None):
+ #print(states)
+ device = loss.device
+ batch_size = loss.size(0)
+ if states is None:
+ gru1_state = torch.zeros((1, batch_size, self.gru1_size), device=device)
+ gru2_state = torch.zeros((1, batch_size, self.gru2_size), device=device)
+ else:
+ gru1_state = states[0]
+ gru2_state = states[1]
+ x = torch.tanh(self.dense_in(torch.cat([loss, perc], dim=-1)))
+ gru1_out, gru1_state = self.gru1(x, gru1_state)
+ gru2_out, gru2_state = self.gru2(gru1_out, gru2_state)
+ return self.dense_out(gru2_out), [gru1_state, gru2_state]
diff --git a/dnn/torch/lossgen/process_data.sh b/dnn/torch/lossgen/process_data.sh
new file mode 100755
index 00000000..308fd0aa
--- /dev/null
+++ b/dnn/torch/lossgen/process_data.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+#directory containing the loss files
+datadir=$1
+
+for i in $datadir/*_is_lost.txt
+do
+ perc=`cat $i | awk '{a+=$1}END{print a/NR}'`
+ echo $perc $i
+done > percentage_list.txt
+
+sort -n percentage_list.txt | awk '{print $2}' > percentage_sorted.txt
+
+for i in `cat percentage_sorted.txt`
+do
+ cat $i
+done > loss_sorted.txt
diff --git a/dnn/torch/lossgen/test_lossgen.py b/dnn/torch/lossgen/test_lossgen.py
new file mode 100644
index 00000000..95659b1f
--- /dev/null
+++ b/dnn/torch/lossgen/test_lossgen.py
@@ -0,0 +1,42 @@
+import lossgen
+import os
+import argparse
+import torch
+import numpy as np
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('model', type=str, help='CELPNet model')
+parser.add_argument('percentage', type=float, help='percentage loss')
+parser.add_argument('output', type=str, help='path to output file (ascii)')
+
+parser.add_argument('--length', type=int, help="length of sequence to generate", default=500)
+
+args = parser.parse_args()
+
+
+
+checkpoint = torch.load(args.model, map_location='cpu')
+model = lossgen.LossGen(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+model.load_state_dict(checkpoint['state_dict'], strict=False)
+
+states=None
+last = torch.zeros((1,1,1))
+perc = torch.tensor((args.percentage,))[None,None,:]
+seq = torch.zeros((0,1,1))
+
+one = torch.ones((1,1,1))
+zero = torch.zeros((1,1,1))
+
+if __name__ == '__main__':
+ for i in range(args.length):
+ prob, states = model(last, perc, states=states)
+ prob = torch.sigmoid(prob)
+ states[0] = states[0].detach()
+ states[1] = states[1].detach()
+ loss = one if np.random.rand() < prob else zero
+ last = loss
+ seq = torch.cat([seq, loss])
+
+np.savetxt(args.output, seq[:,:,0].numpy().astype('int'), fmt='%d')
diff --git a/dnn/torch/lossgen/train_lossgen.py b/dnn/torch/lossgen/train_lossgen.py
new file mode 100644
index 00000000..0bbe2798
--- /dev/null
+++ b/dnn/torch/lossgen/train_lossgen.py
@@ -0,0 +1,99 @@
+import numpy as np
+import torch
+from torch import nn
+import torch.nn.functional as F
+import tqdm
+from scipy.signal import lfilter
+import os
+import lossgen
+
+class LossDataset(torch.utils.data.Dataset):
+ def __init__(self,
+ loss_file,
+ sequence_length=997):
+
+ self.sequence_length = sequence_length
+
+ self.loss = np.loadtxt(loss_file, dtype='float32')
+
+ self.nb_sequences = self.loss.shape[0]//self.sequence_length
+ self.loss = self.loss[:self.nb_sequences*self.sequence_length]
+ self.perc = lfilter(np.array([.001], dtype='float32'), np.array([1., -.999], dtype='float32'), self.loss)
+
+ self.loss = np.reshape(self.loss, (self.nb_sequences, self.sequence_length, 1))
+ self.perc = np.reshape(self.perc, (self.nb_sequences, self.sequence_length, 1))
+
+ def __len__(self):
+ return self.nb_sequences
+
+ def __getitem__(self, index):
+ r0 = np.random.normal(scale=.1, size=(1,1)).astype('float32')
+ r1 = np.random.normal(scale=.1, size=(self.sequence_length,1)).astype('float32')
+ perc = self.perc[index, :, :]
+ perc = perc + (r0+r1)*perc*(1-perc)
+ return [self.loss[index, :, :], perc]
+
+
+adam_betas = [0.8, 0.98]
+adam_eps = 1e-8
+batch_size=256
+lr_decay = 0.001
+lr = 0.003
+epsilon = 1e-5
+epochs = 2000
+checkpoint_dir='checkpoint'
+os.makedirs(checkpoint_dir, exist_ok=True)
+checkpoint = dict()
+
+device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
+
+checkpoint['model_args'] = ()
+checkpoint['model_kwargs'] = {'gru1_size': 16, 'gru2_size': 32}
+model = lossgen.LossGen(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+dataset = LossDataset('loss_sorted.txt')
+dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, drop_last=True, num_workers=4)
+
+
+optimizer = torch.optim.AdamW(model.parameters(), lr=lr, betas=adam_betas, eps=adam_eps)
+
+
+# learning rate scheduler
+scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer=optimizer, lr_lambda=lambda x : 1 / (1 + lr_decay * x))
+
+
+if __name__ == '__main__':
+ model.to(device)
+ states = None
+ for epoch in range(1, epochs + 1):
+
+ running_loss = 0
+
+ print(f"training epoch {epoch}...")
+ with tqdm.tqdm(dataloader, unit='batch') as tepoch:
+ for i, (loss, perc) in enumerate(tepoch):
+ optimizer.zero_grad()
+ loss = loss.to(device)
+ perc = perc.to(device)
+
+ out, states = model(loss, perc, states=states)
+ states = [state.detach() for state in states]
+ out = torch.sigmoid(out[:,:-1,:])
+ target = loss[:,1:,:]
+
+ loss = torch.mean(-target*torch.log(out+epsilon) - (1-target)*torch.log(1-out+epsilon))
+
+ loss.backward()
+ optimizer.step()
+
+ scheduler.step()
+
+ running_loss += loss.detach().cpu().item()
+ tepoch.set_postfix(loss=f"{running_loss/(i+1):8.5f}",
+ )
+
+ # save checkpoint
+ checkpoint_path = os.path.join(checkpoint_dir, f'lossgen_{epoch}.pth')
+ checkpoint['state_dict'] = model.state_dict()
+ checkpoint['loss'] = running_loss / len(dataloader)
+ checkpoint['epoch'] = epoch
+ torch.save(checkpoint, checkpoint_path)
diff --git a/dnn/torch/lpcnet/README.md b/dnn/torch/lpcnet/README.md
new file mode 100644
index 00000000..26d9ea19
--- /dev/null
+++ b/dnn/torch/lpcnet/README.md
@@ -0,0 +1,27 @@
+# LPCNet
+
+Incomplete pytorch implementation of LPCNet
+
+## Data preparation
+For data preparation use dump_data in github.com/xiph/LPCNet. To turn this into
+a training dataset, copy data and feature file to a folder and run
+
+python add_dataset_config.py my_dataset_folder
+
+
+## Training
+To train a model, create and adjust a setup file, e.g. with
+
+python make_default_setup.py my_setup.yml --path2dataset my_dataset_folder
+
+Then simply run
+
+python train_lpcnet.py my_setup.yml my_output
+
+## Inference
+Create feature file with dump_data from github.com/xiph/LPCNet. Then run e.g.
+
+python test_lpcnet.py features.f32 my_output/checkpoints/checkpoint_ep_10.pth out.wav
+
+Inference runs on CPU and takes usually between 3 and 20 seconds per generated second of audio,
+depending on the CPU.
diff --git a/dnn/torch/lpcnet/add_dataset_config.py b/dnn/torch/lpcnet/add_dataset_config.py
new file mode 100644
index 00000000..1b6b4e8c
--- /dev/null
+++ b/dnn/torch/lpcnet/add_dataset_config.py
@@ -0,0 +1,77 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import argparse
+import os
+
+import yaml
+
+
+from utils.templates import dataset_template_v1, dataset_template_v2
+
+
+
+
+parser = argparse.ArgumentParser("add_dataset_config.py")
+
+parser.add_argument('path', type=str, help='path to folder containing feature and data file')
+parser.add_argument('--version', type=int, help="dataset version, 1 for classic LPCNet with 55 feature slots, 2 for new format with 36 feature slots.", default=2)
+parser.add_argument('--description', type=str, help='brief dataset description', default="I will add a description later")
+args = parser.parse_args()
+
+
+if args.version == 1:
+ template = dataset_template_v1
+ data_extension = '.u8'
+elif args.version == 2:
+ template = dataset_template_v2
+ data_extension = '.s16'
+else:
+ raise ValueError(f"unknown dataset version {args.version}")
+
+# get folder content
+content = os.listdir(args.path)
+
+features = [c for c in content if c.endswith('.f32')]
+
+if len(features) != 1:
+ print("could not determine feature file")
+else:
+ template['feature_file'] = features[0]
+
+data = [c for c in content if c.endswith(data_extension)]
+if len(data) != 1:
+ print("could not determine data file")
+else:
+ template['signal_file'] = data[0]
+
+template['description'] = args.description
+
+with open(os.path.join(args.path, 'info.yml'), 'w') as f:
+ yaml.dump(template, f)
diff --git a/dnn/torch/lpcnet/data/__init__.py b/dnn/torch/lpcnet/data/__init__.py
new file mode 100644
index 00000000..50bad871
--- /dev/null
+++ b/dnn/torch/lpcnet/data/__init__.py
@@ -0,0 +1 @@
+from .lpcnet_dataset import LPCNetDataset \ No newline at end of file
diff --git a/dnn/torch/lpcnet/data/lpcnet_dataset.py b/dnn/torch/lpcnet/data/lpcnet_dataset.py
new file mode 100644
index 00000000..dbefc57d
--- /dev/null
+++ b/dnn/torch/lpcnet/data/lpcnet_dataset.py
@@ -0,0 +1,227 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+""" Dataset for LPCNet training """
+import os
+
+import yaml
+import torch
+import numpy as np
+from torch.utils.data import Dataset
+
+
+scale = 255.0/32768.0
+scale_1 = 32768.0/255.0
+def ulaw2lin(u):
+ u = u - 128
+ s = np.sign(u)
+ u = np.abs(u)
+ return s*scale_1*(np.exp(u/128.*np.log(256))-1)
+
+
+def lin2ulaw(x):
+ s = np.sign(x)
+ x = np.abs(x)
+ u = (s*(128*np.log(1+scale*x)/np.log(256)))
+ u = np.clip(128 + np.round(u), 0, 255)
+ return u
+
+
+def run_lpc(signal, lpcs, frame_length=160):
+ num_frames, lpc_order = lpcs.shape
+
+ prediction = np.concatenate(
+ [- np.convolve(signal[i * frame_length : (i + 1) * frame_length + lpc_order - 1], lpcs[i], mode='valid') for i in range(num_frames)]
+ )
+ error = signal[lpc_order :] - prediction
+
+ return prediction, error
+
+class LPCNetDataset(Dataset):
+ def __init__(self,
+ path_to_dataset,
+ features=['cepstrum', 'periods', 'pitch_corr'],
+ input_signals=['last_signal', 'prediction', 'last_error'],
+ target='error',
+ frames_per_sample=15,
+ feature_history=2,
+ feature_lookahead=2,
+ lpc_gamma=1):
+
+ super(LPCNetDataset, self).__init__()
+
+ # load dataset info
+ self.path_to_dataset = path_to_dataset
+ with open(os.path.join(path_to_dataset, 'info.yml'), 'r') as f:
+ dataset = yaml.load(f, yaml.FullLoader)
+
+ # dataset version
+ self.version = dataset['version']
+ if self.version == 1:
+ self.getitem = self.getitem_v1
+ elif self.version == 2:
+ self.getitem = self.getitem_v2
+ else:
+ raise ValueError(f"dataset version {self.version} unknown")
+
+ # features
+ self.feature_history = feature_history
+ self.feature_lookahead = feature_lookahead
+ self.frame_offset = 1 + self.feature_history
+ self.frames_per_sample = frames_per_sample
+ self.input_features = features
+ self.feature_frame_layout = dataset['feature_frame_layout']
+ self.lpc_gamma = lpc_gamma
+
+ # load feature file
+ self.feature_file = os.path.join(path_to_dataset, dataset['feature_file'])
+ self.features = np.memmap(self.feature_file, dtype=dataset['feature_dtype'])
+ self.feature_frame_length = dataset['feature_frame_length']
+
+ assert len(self.features) % self.feature_frame_length == 0
+ self.features = self.features.reshape((-1, self.feature_frame_length))
+
+ # derive number of samples is dataset
+ self.dataset_length = (len(self.features) - self.frame_offset - self.feature_lookahead - 1) // self.frames_per_sample
+
+ # signals
+ self.frame_length = dataset['frame_length']
+ self.signal_frame_layout = dataset['signal_frame_layout']
+ self.input_signals = input_signals
+ self.target = target
+
+ # load signals
+ self.signal_file = os.path.join(path_to_dataset, dataset['signal_file'])
+ self.signals = np.memmap(self.signal_file, dtype=dataset['signal_dtype'])
+ self.signal_frame_length = dataset['signal_frame_length']
+ self.signals = self.signals.reshape((-1, self.signal_frame_length))
+ assert len(self.signals) == len(self.features) * self.frame_length
+
+ def __getitem__(self, index):
+ return self.getitem(index)
+
+ def getitem_v2(self, index):
+ sample = dict()
+
+ # extract features
+ frame_start = self.frame_offset + index * self.frames_per_sample - self.feature_history
+ frame_stop = self.frame_offset + (index + 1) * self.frames_per_sample + self.feature_lookahead
+
+ for feature in self.input_features:
+ feature_start, feature_stop = self.feature_frame_layout[feature]
+ sample[feature] = self.features[frame_start : frame_stop, feature_start : feature_stop]
+
+ # convert periods
+ if 'periods' in self.input_features:
+ sample['periods'] = (0.1 + 50 * sample['periods'] + 100).astype('int16')
+
+ signal_start = (self.frame_offset + index * self.frames_per_sample) * self.frame_length
+ signal_stop = (self.frame_offset + (index + 1) * self.frames_per_sample) * self.frame_length
+
+ # last_signal and signal are always expected to be there
+ sample['last_signal'] = self.signals[signal_start : signal_stop, self.signal_frame_layout['last_signal']]
+ sample['signal'] = self.signals[signal_start : signal_stop, self.signal_frame_layout['signal']]
+
+ # calculate prediction and error if lpc coefficients present and prediction not given
+ if 'lpc' in self.feature_frame_layout and 'prediction' not in self.signal_frame_layout:
+ # lpc coefficients with one frame lookahead
+ # frame positions (start one frame early for past excitation)
+ frame_start = self.frame_offset + self.frames_per_sample * index - 1
+ frame_stop = self.frame_offset + self.frames_per_sample * (index + 1)
+
+ # feature positions
+ lpc_start, lpc_stop = self.feature_frame_layout['lpc']
+ lpc_order = lpc_stop - lpc_start
+ lpcs = self.features[frame_start : frame_stop, lpc_start : lpc_stop]
+
+ # LPC weighting
+ lpc_order = lpc_stop - lpc_start
+ weights = np.array([self.lpc_gamma ** (i + 1) for i in range(lpc_order)])
+ lpcs = lpcs * weights
+
+ # signal position (lpc_order samples as history)
+ signal_start = frame_start * self.frame_length - lpc_order + 1
+ signal_stop = frame_stop * self.frame_length + 1
+ noisy_signal = self.signals[signal_start : signal_stop, self.signal_frame_layout['last_signal']]
+ clean_signal = self.signals[signal_start - 1 : signal_stop - 1, self.signal_frame_layout['signal']]
+
+ noisy_prediction, noisy_error = run_lpc(noisy_signal, lpcs, frame_length=self.frame_length)
+
+ # extract signals
+ offset = self.frame_length
+ sample['prediction'] = noisy_prediction[offset : offset + self.frame_length * self.frames_per_sample]
+ sample['last_error'] = noisy_error[offset - 1 : offset - 1 + self.frame_length * self.frames_per_sample]
+ # calculate error between real signal and noisy prediction
+
+
+ sample['error'] = sample['signal'] - sample['prediction']
+
+
+ # concatenate features
+ feature_keys = [key for key in self.input_features if not key.startswith("periods")]
+ features = torch.concat([torch.FloatTensor(sample[key]) for key in feature_keys], dim=-1)
+ signals = torch.cat([torch.LongTensor(lin2ulaw(sample[key])).unsqueeze(-1) for key in self.input_signals], dim=-1)
+ target = torch.LongTensor(lin2ulaw(sample[self.target]))
+ periods = torch.LongTensor(sample['periods'])
+
+ return {'features' : features, 'periods' : periods, 'signals' : signals, 'target' : target}
+
+ def getitem_v1(self, index):
+ sample = dict()
+
+ # extract features
+ frame_start = self.frame_offset + index * self.frames_per_sample - self.feature_history
+ frame_stop = self.frame_offset + (index + 1) * self.frames_per_sample + self.feature_lookahead
+
+ for feature in self.input_features:
+ feature_start, feature_stop = self.feature_frame_layout[feature]
+ sample[feature] = self.features[frame_start : frame_stop, feature_start : feature_stop]
+
+ # convert periods
+ if 'periods' in self.input_features:
+ sample['periods'] = (0.1 + 50 * sample['periods'] + 100).astype('int16')
+
+ signal_start = (self.frame_offset + index * self.frames_per_sample) * self.frame_length
+ signal_stop = (self.frame_offset + (index + 1) * self.frames_per_sample) * self.frame_length
+
+ # last_signal and signal are always expected to be there
+ for signal_name, index in self.signal_frame_layout.items():
+ sample[signal_name] = self.signals[signal_start : signal_stop, index]
+
+ # concatenate features
+ feature_keys = [key for key in self.input_features if not key.startswith("periods")]
+ features = torch.concat([torch.FloatTensor(sample[key]) for key in feature_keys], dim=-1)
+ signals = torch.cat([torch.LongTensor(sample[key]).unsqueeze(-1) for key in self.input_signals], dim=-1)
+ target = torch.LongTensor(sample[self.target])
+ periods = torch.LongTensor(sample['periods'])
+
+ return {'features' : features, 'periods' : periods, 'signals' : signals, 'target' : target}
+
+ def __len__(self):
+ return self.dataset_length
diff --git a/dnn/torch/lpcnet/engine/lpcnet_engine.py b/dnn/torch/lpcnet/engine/lpcnet_engine.py
new file mode 100644
index 00000000..c964bdd1
--- /dev/null
+++ b/dnn/torch/lpcnet/engine/lpcnet_engine.py
@@ -0,0 +1,141 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from tqdm import tqdm
+import sys
+
+def train_one_epoch(model, criterion, optimizer, dataloader, device, scheduler, log_interval=10):
+
+ model.to(device)
+ model.train()
+
+ running_loss = 0
+ previous_running_loss = 0
+
+ # gru states
+ gru_a_state = torch.zeros(1, dataloader.batch_size, model.gru_a_units, device=device).to(device)
+ gru_b_state = torch.zeros(1, dataloader.batch_size, model.gru_b_units, device=device).to(device)
+ gru_states = [gru_a_state, gru_b_state]
+
+ with tqdm(dataloader, unit='batch', file=sys.stdout) as tepoch:
+
+ for i, batch in enumerate(tepoch):
+
+ # set gradients to zero
+ optimizer.zero_grad()
+
+ # zero out initial gru states
+ gru_a_state.zero_()
+ gru_b_state.zero_()
+
+ # push batch to device
+ for key in batch:
+ batch[key] = batch[key].to(device)
+
+ target = batch['target']
+
+ # calculate model output
+ output = model(batch['features'], batch['periods'], batch['signals'], gru_states)
+
+ # calculate loss
+ loss = criterion(output.permute(0, 2, 1), target)
+
+ # calculate gradients
+ loss.backward()
+
+ # update weights
+ optimizer.step()
+
+ # update learning rate
+ scheduler.step()
+
+ # call sparsifier
+ model.sparsify()
+
+ # update running loss
+ running_loss += float(loss.cpu())
+
+ # update status bar
+ if i % log_interval == 0:
+ tepoch.set_postfix(running_loss=f"{running_loss/(i + 1):8.7f}", current_loss=f"{(running_loss - previous_running_loss)/log_interval:8.7f}")
+ previous_running_loss = running_loss
+
+
+ running_loss /= len(dataloader)
+
+ return running_loss
+
+def evaluate(model, criterion, dataloader, device, log_interval=10):
+
+ model.to(device)
+ model.eval()
+
+ running_loss = 0
+ previous_running_loss = 0
+
+ # gru states
+ gru_a_state = torch.zeros(1, dataloader.batch_size, model.gru_a_units, device=device).to(device)
+ gru_b_state = torch.zeros(1, dataloader.batch_size, model.gru_b_units, device=device).to(device)
+ gru_states = [gru_a_state, gru_b_state]
+
+ with torch.no_grad():
+ with tqdm(dataloader, unit='batch', file=sys.stdout) as tepoch:
+
+ for i, batch in enumerate(tepoch):
+
+
+ # zero out initial gru states
+ gru_a_state.zero_()
+ gru_b_state.zero_()
+
+ # push batch to device
+ for key in batch:
+ batch[key] = batch[key].to(device)
+
+ target = batch['target']
+
+ # calculate model output
+ output = model(batch['features'], batch['periods'], batch['signals'], gru_states)
+
+ # calculate loss
+ loss = criterion(output.permute(0, 2, 1), target)
+
+ # update running loss
+ running_loss += float(loss.cpu())
+
+ # update status bar
+ if i % log_interval == 0:
+ tepoch.set_postfix(running_loss=f"{running_loss/(i + 1):8.7f}", current_loss=f"{(running_loss - previous_running_loss)/log_interval:8.7f}")
+ previous_running_loss = running_loss
+
+
+ running_loss /= len(dataloader)
+
+ return running_loss \ No newline at end of file
diff --git a/dnn/torch/lpcnet/make_default_setup.py b/dnn/torch/lpcnet/make_default_setup.py
new file mode 100644
index 00000000..17031d26
--- /dev/null
+++ b/dnn/torch/lpcnet/make_default_setup.py
@@ -0,0 +1,56 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import argparse
+
+import yaml
+
+from utils.templates import setup_dict
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('name', type=str, help='name of default setup file')
+parser.add_argument('--model', choices=['lpcnet', 'multi_rate'], help='LPCNet model name', default='lpcnet')
+parser.add_argument('--path2dataset', type=str, help='dataset path', default=None)
+
+args = parser.parse_args()
+
+setup = setup_dict[args.model]
+
+# update dataset if given
+if type(args.path2dataset) != type(None):
+ setup['dataset'] = args.path2dataset
+
+name = args.name
+if not name.endswith('.yml'):
+ name += '.yml'
+
+if __name__ == '__main__':
+ with open(name, 'w') as f:
+ f.write(yaml.dump(setup))
diff --git a/dnn/torch/lpcnet/make_test_config.py b/dnn/torch/lpcnet/make_test_config.py
new file mode 100644
index 00000000..7c414e01
--- /dev/null
+++ b/dnn/torch/lpcnet/make_test_config.py
@@ -0,0 +1,78 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import argparse
+import os
+import sys
+
+parser = argparse.ArgumentParser()
+parser.add_argument("config_name", type=str, help="name of config file (.yml will be appended)")
+parser.add_argument("test_name", type=str, help="name for test result display")
+parser.add_argument("checkpoint", type=str, help="checkpoint to test")
+parser.add_argument("--lpcnet-demo", type=str, help="path to lpcnet_demo binary, default: /local/code/LPCNet/lpcnet_demo", default="/local/code/LPCNet/lpcnet_demo")
+parser.add_argument("--lpcnext-path", type=str, help="path to lpcnext folder, defalut: dirname(__file__)", default=os.path.dirname(__file__))
+parser.add_argument("--python-exe", type=str, help='python executable path, default: sys.executable', default=sys.executable)
+parser.add_argument("--pad", type=str, help="left pad of output in seconds, default: 0.015", default="0.015")
+parser.add_argument("--trim", type=str, help="left trim of output in seconds, default: 0", default="0")
+
+
+
+template='''
+test: "{NAME}"
+processing:
+ - "sox {{INPUT}} {{INPUT}}.raw"
+ - "{LPCNET_DEMO} -features {{INPUT}}.raw {{INPUT}}.features.f32"
+ - "{PYTHON} {WORKING}/test_lpcnet.py {{INPUT}}.features.f32 {CHECKPOINT} {{OUTPUT}}.ua.wav"
+ - "sox {{OUTPUT}}.ua.wav {{OUTPUT}}.uap.wav pad {PAD}"
+ - "sox {{OUTPUT}}.uap.wav {{OUTPUT}} trim {TRIM}"
+ - "rm {{INPUT}}.raw {{OUTPUT}}.uap.wav {{OUTPUT}}.ua.wav {{INPUT}}.features.f32"
+'''
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+
+ file_content = template.format(
+ NAME=args.test_name,
+ LPCNET_DEMO=os.path.abspath(args.lpcnet_demo),
+ PYTHON=os.path.abspath(args.python_exe),
+ PAD=args.pad,
+ TRIM=args.trim,
+ WORKING=os.path.abspath(args.lpcnext_path),
+ CHECKPOINT=os.path.abspath(args.checkpoint)
+ )
+
+ print(file_content)
+
+ filename = args.config_name
+ if not filename.endswith(".yml"):
+ filename += ".yml"
+
+ with open(filename, "w") as f:
+ f.write(file_content)
diff --git a/dnn/torch/lpcnet/models/__init__.py b/dnn/torch/lpcnet/models/__init__.py
new file mode 100644
index 00000000..a26bc1cd
--- /dev/null
+++ b/dnn/torch/lpcnet/models/__init__.py
@@ -0,0 +1,8 @@
+from .lpcnet import LPCNet
+from .multi_rate_lpcnet import MultiRateLPCNet
+
+
+model_dict = {
+ 'lpcnet' : LPCNet,
+ 'multi_rate' : MultiRateLPCNet
+} \ No newline at end of file
diff --git a/dnn/torch/lpcnet/models/lpcnet.py b/dnn/torch/lpcnet/models/lpcnet.py
new file mode 100644
index 00000000..e83ae901
--- /dev/null
+++ b/dnn/torch/lpcnet/models/lpcnet.py
@@ -0,0 +1,303 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+import numpy as np
+
+from utils.ulaw import lin2ulawq, ulaw2lin
+from utils.sample import sample_excitation
+from utils.pcm import clip_to_int16
+from utils.sparsification import GRUSparsifier, calculate_gru_flops_per_step
+from utils.layers import DualFC
+from utils.misc import get_pdf_from_tree
+
+
+class LPCNet(nn.Module):
+ def __init__(self, config):
+ super(LPCNet, self).__init__()
+
+ #
+ self.input_layout = config['input_layout']
+ self.feature_history = config['feature_history']
+ self.feature_lookahead = config['feature_lookahead']
+
+ # frame rate network parameters
+ self.feature_dimension = config['feature_dimension']
+ self.period_embedding_dim = config['period_embedding_dim']
+ self.period_levels = config['period_levels']
+ self.feature_channels = self.feature_dimension + self.period_embedding_dim
+ self.feature_conditioning_dim = config['feature_conditioning_dim']
+ self.feature_conv_kernel_size = config['feature_conv_kernel_size']
+
+
+ # frame rate network layers
+ self.period_embedding = nn.Embedding(self.period_levels, self.period_embedding_dim)
+ self.feature_conv1 = nn.Conv1d(self.feature_channels, self.feature_conditioning_dim, self.feature_conv_kernel_size, padding='valid')
+ self.feature_conv2 = nn.Conv1d(self.feature_conditioning_dim, self.feature_conditioning_dim, self.feature_conv_kernel_size, padding='valid')
+ self.feature_dense1 = nn.Linear(self.feature_conditioning_dim, self.feature_conditioning_dim)
+ self.feature_dense2 = nn.Linear(*(2*[self.feature_conditioning_dim]))
+
+ # sample rate network parameters
+ self.frame_size = config['frame_size']
+ self.signal_levels = config['signal_levels']
+ self.signal_embedding_dim = config['signal_embedding_dim']
+ self.gru_a_units = config['gru_a_units']
+ self.gru_b_units = config['gru_b_units']
+ self.output_levels = config['output_levels']
+ self.hsampling = config.get('hsampling', False)
+
+ self.gru_a_input_dim = len(self.input_layout['signals']) * self.signal_embedding_dim + self.feature_conditioning_dim
+ self.gru_b_input_dim = self.gru_a_units + self.feature_conditioning_dim
+
+ # sample rate network layers
+ self.signal_embedding = nn.Embedding(self.signal_levels, self.signal_embedding_dim)
+ self.gru_a = nn.GRU(self.gru_a_input_dim, self.gru_a_units, batch_first=True)
+ self.gru_b = nn.GRU(self.gru_b_input_dim, self.gru_b_units, batch_first=True)
+ self.dual_fc = DualFC(self.gru_b_units, self.output_levels)
+
+ # sparsification
+ self.sparsifier = []
+
+ # GRU A
+ if 'gru_a' in config['sparsification']:
+ gru_config = config['sparsification']['gru_a']
+ task_list = [(self.gru_a, gru_config['params'])]
+ self.sparsifier.append(GRUSparsifier(task_list,
+ gru_config['start'],
+ gru_config['stop'],
+ gru_config['interval'],
+ gru_config['exponent'])
+ )
+ self.gru_a_flops_per_step = calculate_gru_flops_per_step(self.gru_a,
+ gru_config['params'], drop_input=True)
+ else:
+ self.gru_a_flops_per_step = calculate_gru_flops_per_step(self.gru_a, drop_input=True)
+
+ # GRU B
+ if 'gru_b' in config['sparsification']:
+ gru_config = config['sparsification']['gru_b']
+ task_list = [(self.gru_b, gru_config['params'])]
+ self.sparsifier.append(GRUSparsifier(task_list,
+ gru_config['start'],
+ gru_config['stop'],
+ gru_config['interval'],
+ gru_config['exponent'])
+ )
+ self.gru_b_flops_per_step = calculate_gru_flops_per_step(self.gru_b,
+ gru_config['params'])
+ else:
+ self.gru_b_flops_per_step = calculate_gru_flops_per_step(self.gru_b)
+
+ # inference parameters
+ self.lpc_gamma = config.get('lpc_gamma', 1)
+
+ def sparsify(self):
+ for sparsifier in self.sparsifier:
+ sparsifier.step()
+
+ def get_gflops(self, fs, verbose=False):
+ gflops = 0
+
+ # frame rate network
+ conditioning_dim = self.feature_conditioning_dim
+ feature_channels = self.feature_channels
+ frame_rate = fs / self.frame_size
+ frame_rate_network_complexity = 1e-9 * 2 * (5 * conditioning_dim + 3 * feature_channels) * conditioning_dim * frame_rate
+ if verbose:
+ print(f"frame rate network: {frame_rate_network_complexity} GFLOPS")
+ gflops += frame_rate_network_complexity
+
+ # gru a
+ gru_a_rate = fs
+ gru_a_complexity = 1e-9 * gru_a_rate * self.gru_a_flops_per_step
+ if verbose:
+ print(f"gru A: {gru_a_complexity} GFLOPS")
+ gflops += gru_a_complexity
+
+ # gru b
+ gru_b_rate = fs
+ gru_b_complexity = 1e-9 * gru_b_rate * self.gru_b_flops_per_step
+ if verbose:
+ print(f"gru B: {gru_b_complexity} GFLOPS")
+ gflops += gru_b_complexity
+
+
+ # dual fcs
+ fc = self.dual_fc
+ rate = fs
+ input_size = fc.dense1.in_features
+ output_size = fc.dense1.out_features
+ dual_fc_complexity = 1e-9 * (4 * input_size * output_size + 22 * output_size) * rate
+ if self.hsampling:
+ dual_fc_complexity /= 8
+ if verbose:
+ print(f"dual_fc: {dual_fc_complexity} GFLOPS")
+ gflops += dual_fc_complexity
+
+ if verbose:
+ print(f'total: {gflops} GFLOPS')
+
+ return gflops
+
+ def frame_rate_network(self, features, periods):
+
+ embedded_periods = torch.flatten(self.period_embedding(periods), 2, 3)
+ features = torch.concat((features, embedded_periods), dim=-1)
+
+ # convert to channels first and calculate conditioning vector
+ c = torch.permute(features, [0, 2, 1])
+
+ c = torch.tanh(self.feature_conv1(c))
+ c = torch.tanh(self.feature_conv2(c))
+ # back to channels last
+ c = torch.permute(c, [0, 2, 1])
+ c = torch.tanh(self.feature_dense1(c))
+ c = torch.tanh(self.feature_dense2(c))
+
+ return c
+
+ def sample_rate_network(self, signals, c, gru_states):
+ embedded_signals = torch.flatten(self.signal_embedding(signals), 2, 3)
+ c_upsampled = torch.repeat_interleave(c, self.frame_size, dim=1)
+
+ y = torch.concat((embedded_signals, c_upsampled), dim=-1)
+ y, gru_a_state = self.gru_a(y, gru_states[0])
+ y = torch.concat((y, c_upsampled), dim=-1)
+ y, gru_b_state = self.gru_b(y, gru_states[1])
+
+ y = self.dual_fc(y)
+
+ if self.hsampling:
+ y = torch.sigmoid(y)
+ log_probs = torch.log(get_pdf_from_tree(y) + 1e-6)
+ else:
+ log_probs = torch.log_softmax(y, dim=-1)
+
+ return log_probs, (gru_a_state, gru_b_state)
+
+ def decoder(self, signals, c, gru_states):
+ embedded_signals = torch.flatten(self.signal_embedding(signals), 2, 3)
+
+ y = torch.concat((embedded_signals, c), dim=-1)
+ y, gru_a_state = self.gru_a(y, gru_states[0])
+ y = torch.concat((y, c), dim=-1)
+ y, gru_b_state = self.gru_b(y, gru_states[1])
+
+ y = self.dual_fc(y)
+
+ if self.hsampling:
+ y = torch.sigmoid(y)
+ probs = get_pdf_from_tree(y)
+ else:
+ probs = torch.softmax(y, dim=-1)
+
+ return probs, (gru_a_state, gru_b_state)
+
+ def forward(self, features, periods, signals, gru_states):
+
+ c = self.frame_rate_network(features, periods)
+ log_probs, _ = self.sample_rate_network(signals, c, gru_states)
+
+ return log_probs
+
+ def generate(self, features, periods, lpcs):
+
+ with torch.no_grad():
+ device = self.parameters().__next__().device
+
+ num_frames = features.shape[0] - self.feature_history - self.feature_lookahead
+ lpc_order = lpcs.shape[-1]
+ num_input_signals = len(self.input_layout['signals'])
+ pitch_corr_position = self.input_layout['features']['pitch_corr'][0]
+
+ # signal buffers
+ pcm = torch.zeros((num_frames * self.frame_size + lpc_order))
+ output = torch.zeros((num_frames * self.frame_size), dtype=torch.int16)
+ mem = 0
+
+ # state buffers
+ gru_a_state = torch.zeros((1, 1, self.gru_a_units))
+ gru_b_state = torch.zeros((1, 1, self.gru_b_units))
+ gru_states = [gru_a_state, gru_b_state]
+
+ input_signals = torch.zeros((1, 1, num_input_signals), dtype=torch.long) + 128
+
+ # push data to device
+ features = features.to(device)
+ periods = periods.to(device)
+ lpcs = lpcs.to(device)
+
+ # lpc weighting
+ weights = torch.FloatTensor([self.lpc_gamma ** (i + 1) for i in range(lpc_order)]).to(device)
+ lpcs = lpcs * weights
+
+ # run feature encoding
+ c = self.frame_rate_network(features.unsqueeze(0), periods.unsqueeze(0))
+
+ for frame_index in range(num_frames):
+ frame_start = frame_index * self.frame_size
+ pitch_corr = features[frame_index + self.feature_history, pitch_corr_position]
+ a = - torch.flip(lpcs[frame_index + self.feature_history], [0])
+ current_c = c[:, frame_index : frame_index + 1, :]
+
+ for i in range(self.frame_size):
+ pcm_position = frame_start + i + lpc_order
+ output_position = frame_start + i
+
+ # prepare input
+ pred = torch.sum(pcm[pcm_position - lpc_order : pcm_position] * a)
+ if 'prediction' in self.input_layout['signals']:
+ input_signals[0, 0, self.input_layout['signals']['prediction']] = lin2ulawq(pred)
+
+ # run single step of sample rate network
+ probs, gru_states = self.decoder(
+ input_signals,
+ current_c,
+ gru_states
+ )
+
+ # sample from output
+ exc_ulaw = sample_excitation(probs, pitch_corr)
+
+ # signal generation
+ exc = ulaw2lin(exc_ulaw)
+ sig = exc + pred
+ pcm[pcm_position] = sig
+ mem = 0.85 * mem + float(sig)
+ output[output_position] = clip_to_int16(round(mem))
+
+ # buffer update
+ if 'last_signal' in self.input_layout['signals']:
+ input_signals[0, 0, self.input_layout['signals']['last_signal']] = lin2ulawq(sig)
+
+ if 'last_error' in self.input_layout['signals']:
+ input_signals[0, 0, self.input_layout['signals']['last_error']] = lin2ulawq(exc)
+
+ return output
diff --git a/dnn/torch/lpcnet/models/multi_rate_lpcnet.py b/dnn/torch/lpcnet/models/multi_rate_lpcnet.py
new file mode 100644
index 00000000..e613e74c
--- /dev/null
+++ b/dnn/torch/lpcnet/models/multi_rate_lpcnet.py
@@ -0,0 +1,437 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+from utils.layers.subconditioner import get_subconditioner
+from utils.layers import DualFC
+
+from utils.ulaw import lin2ulawq, ulaw2lin
+from utils.sample import sample_excitation
+from utils.pcm import clip_to_int16
+from utils.sparsification import GRUSparsifier, calculate_gru_flops_per_step
+
+from utils.misc import interleave_tensors
+
+
+
+
+# MultiRateLPCNet
+class MultiRateLPCNet(nn.Module):
+ def __init__(self, config):
+ super(MultiRateLPCNet, self).__init__()
+
+ # general parameters
+ self.input_layout = config['input_layout']
+ self.feature_history = config['feature_history']
+ self.feature_lookahead = config['feature_lookahead']
+ self.signals = config['signals']
+
+ # frame rate network parameters
+ self.feature_dimension = config['feature_dimension']
+ self.period_embedding_dim = config['period_embedding_dim']
+ self.period_levels = config['period_levels']
+ self.feature_channels = self.feature_dimension + self.period_embedding_dim
+ self.feature_conditioning_dim = config['feature_conditioning_dim']
+ self.feature_conv_kernel_size = config['feature_conv_kernel_size']
+
+ # frame rate network layers
+ self.period_embedding = nn.Embedding(self.period_levels, self.period_embedding_dim)
+ self.feature_conv1 = nn.Conv1d(self.feature_channels, self.feature_conditioning_dim, self.feature_conv_kernel_size, padding='valid')
+ self.feature_conv2 = nn.Conv1d(self.feature_conditioning_dim, self.feature_conditioning_dim, self.feature_conv_kernel_size, padding='valid')
+ self.feature_dense1 = nn.Linear(self.feature_conditioning_dim, self.feature_conditioning_dim)
+ self.feature_dense2 = nn.Linear(*(2*[self.feature_conditioning_dim]))
+
+ # sample rate network parameters
+ self.frame_size = config['frame_size']
+ self.signal_levels = config['signal_levels']
+ self.signal_embedding_dim = config['signal_embedding_dim']
+ self.gru_a_units = config['gru_a_units']
+ self.gru_b_units = config['gru_b_units']
+ self.output_levels = config['output_levels']
+
+ # subconditioning B
+ sub_config = config['subconditioning']['subconditioning_b']
+ self.substeps_b = sub_config['number_of_subsamples']
+ self.subcondition_signals_b = sub_config['signals']
+ self.signals_idx_b = [self.input_layout['signals'][key] for key in sub_config['signals']]
+ method = sub_config['method']
+ kwargs = sub_config['kwargs']
+ if type(kwargs) == type(None):
+ kwargs = dict()
+
+ state_size = self.gru_b_units
+ self.subconditioner_b = get_subconditioner(method,
+ sub_config['number_of_subsamples'], sub_config['pcm_embedding_size'],
+ state_size, self.signal_levels, len(sub_config['signals']),
+ **sub_config['kwargs'])
+
+ # subconditioning A
+ sub_config = config['subconditioning']['subconditioning_a']
+ self.substeps_a = sub_config['number_of_subsamples']
+ self.subcondition_signals_a = sub_config['signals']
+ self.signals_idx_a = [self.input_layout['signals'][key] for key in sub_config['signals']]
+ method = sub_config['method']
+ kwargs = sub_config['kwargs']
+ if type(kwargs) == type(None):
+ kwargs = dict()
+
+ state_size = self.gru_a_units
+ self.subconditioner_a = get_subconditioner(method,
+ sub_config['number_of_subsamples'], sub_config['pcm_embedding_size'],
+ state_size, self.signal_levels, self.substeps_b * len(sub_config['signals']),
+ **sub_config['kwargs'])
+
+
+ # wrap up subconditioning, group_size_gru_a holds the number
+ # of timesteps that are grouped as sample input for GRU A
+ # input and group_size_subcondition_a holds the number of samples that are
+ # grouped as input to pre-GRU B subconditioning
+ self.group_size_gru_a = self.substeps_a * self.substeps_b
+ self.group_size_subcondition_a = self.substeps_b
+ self.gru_a_rate_divider = self.group_size_gru_a
+ self.gru_b_rate_divider = self.substeps_b
+
+ # gru sizes
+ self.gru_a_input_dim = self.group_size_gru_a * len(self.signals) * self.signal_embedding_dim + self.feature_conditioning_dim
+ self.gru_b_input_dim = self.subconditioner_a.get_output_dim(0) + self.feature_conditioning_dim
+ self.signals_idx = [self.input_layout['signals'][key] for key in self.signals]
+
+ # sample rate network layers
+ self.signal_embedding = nn.Embedding(self.signal_levels, self.signal_embedding_dim)
+ self.gru_a = nn.GRU(self.gru_a_input_dim, self.gru_a_units, batch_first=True)
+ self.gru_b = nn.GRU(self.gru_b_input_dim, self.gru_b_units, batch_first=True)
+
+ # sparsification
+ self.sparsifier = []
+
+ # GRU A
+ if 'gru_a' in config['sparsification']:
+ gru_config = config['sparsification']['gru_a']
+ task_list = [(self.gru_a, gru_config['params'])]
+ self.sparsifier.append(GRUSparsifier(task_list,
+ gru_config['start'],
+ gru_config['stop'],
+ gru_config['interval'],
+ gru_config['exponent'])
+ )
+ self.gru_a_flops_per_step = calculate_gru_flops_per_step(self.gru_a,
+ gru_config['params'], drop_input=True)
+ else:
+ self.gru_a_flops_per_step = calculate_gru_flops_per_step(self.gru_a, drop_input=True)
+
+ # GRU B
+ if 'gru_b' in config['sparsification']:
+ gru_config = config['sparsification']['gru_b']
+ task_list = [(self.gru_b, gru_config['params'])]
+ self.sparsifier.append(GRUSparsifier(task_list,
+ gru_config['start'],
+ gru_config['stop'],
+ gru_config['interval'],
+ gru_config['exponent'])
+ )
+ self.gru_b_flops_per_step = calculate_gru_flops_per_step(self.gru_b,
+ gru_config['params'])
+ else:
+ self.gru_b_flops_per_step = calculate_gru_flops_per_step(self.gru_b)
+
+
+
+ # dual FCs
+ self.dual_fc = []
+ for i in range(self.substeps_b):
+ dim = self.subconditioner_b.get_output_dim(i)
+ self.dual_fc.append(DualFC(dim, self.output_levels))
+ self.add_module(f"dual_fc_{i}", self.dual_fc[-1])
+
+ def get_gflops(self, fs, verbose=False, hierarchical_sampling=False):
+ gflops = 0
+
+ # frame rate network
+ conditioning_dim = self.feature_conditioning_dim
+ feature_channels = self.feature_channels
+ frame_rate = fs / self.frame_size
+ frame_rate_network_complexity = 1e-9 * 2 * (5 * conditioning_dim + 3 * feature_channels) * conditioning_dim * frame_rate
+ if verbose:
+ print(f"frame rate network: {frame_rate_network_complexity} GFLOPS")
+ gflops += frame_rate_network_complexity
+
+ # gru a
+ gru_a_rate = fs / self.group_size_gru_a
+ gru_a_complexity = 1e-9 * gru_a_rate * self.gru_a_flops_per_step
+ if verbose:
+ print(f"gru A: {gru_a_complexity} GFLOPS")
+ gflops += gru_a_complexity
+
+ # subconditioning a
+ subcond_a_rate = fs / self.substeps_b
+ subconditioning_a_complexity = 1e-9 * self.subconditioner_a.get_average_flops_per_step() * subcond_a_rate
+ if verbose:
+ print(f"subconditioning A: {subconditioning_a_complexity} GFLOPS")
+ gflops += subconditioning_a_complexity
+
+ # gru b
+ gru_b_rate = fs / self.substeps_b
+ gru_b_complexity = 1e-9 * gru_b_rate * self.gru_b_flops_per_step
+ if verbose:
+ print(f"gru B: {gru_b_complexity} GFLOPS")
+ gflops += gru_b_complexity
+
+ # subconditioning b
+ subcond_b_rate = fs
+ subconditioning_b_complexity = 1e-9 * self.subconditioner_b.get_average_flops_per_step() * subcond_b_rate
+ if verbose:
+ print(f"subconditioning B: {subconditioning_b_complexity} GFLOPS")
+ gflops += subconditioning_b_complexity
+
+ # dual fcs
+ for i, fc in enumerate(self.dual_fc):
+ rate = fs / len(self.dual_fc)
+ input_size = fc.dense1.in_features
+ output_size = fc.dense1.out_features
+ dual_fc_complexity = 1e-9 * (4 * input_size * output_size + 22 * output_size) * rate
+ if hierarchical_sampling:
+ dual_fc_complexity /= 8
+ if verbose:
+ print(f"dual_fc_{i}: {dual_fc_complexity} GFLOPS")
+ gflops += dual_fc_complexity
+
+ if verbose:
+ print(f'total: {gflops} GFLOPS')
+
+ return gflops
+
+
+
+ def sparsify(self):
+ for sparsifier in self.sparsifier:
+ sparsifier.step()
+
+ def frame_rate_network(self, features, periods):
+
+ embedded_periods = torch.flatten(self.period_embedding(periods), 2, 3)
+ features = torch.concat((features, embedded_periods), dim=-1)
+
+ # convert to channels first and calculate conditioning vector
+ c = torch.permute(features, [0, 2, 1])
+
+ c = torch.tanh(self.feature_conv1(c))
+ c = torch.tanh(self.feature_conv2(c))
+ # back to channels last
+ c = torch.permute(c, [0, 2, 1])
+ c = torch.tanh(self.feature_dense1(c))
+ c = torch.tanh(self.feature_dense2(c))
+
+ return c
+
+ def prepare_signals(self, signals, group_size, signal_idx):
+ """ extracts, delays and groups signals """
+
+ batch_size, sequence_length, num_signals = signals.shape
+
+ # extract signals according to position
+ signals = torch.cat([signals[:, :, i : i + 1] for i in signal_idx],
+ dim=-1)
+
+ # roll back pcm to account for grouping
+ signals = torch.roll(signals, group_size - 1, -2)
+
+ # reshape
+ signals = torch.reshape(signals,
+ (batch_size, sequence_length // group_size, group_size * len(signal_idx)))
+
+ return signals
+
+
+ def sample_rate_network(self, signals, c, gru_states):
+
+ signals_a = self.prepare_signals(signals, self.group_size_gru_a, self.signals_idx)
+ embedded_signals = torch.flatten(self.signal_embedding(signals_a), 2, 3)
+ # features at GRU A rate
+ c_upsampled_a = torch.repeat_interleave(c, self.frame_size // self.gru_a_rate_divider, dim=1)
+ # features at GRU B rate
+ c_upsampled_b = torch.repeat_interleave(c, self.frame_size // self.gru_b_rate_divider, dim=1)
+
+ y = torch.concat((embedded_signals, c_upsampled_a), dim=-1)
+ y, gru_a_state = self.gru_a(y, gru_states[0])
+ # first round of upsampling and subconditioning
+ c_signals_a = self.prepare_signals(signals, self.group_size_subcondition_a, self.signals_idx_a)
+ y = self.subconditioner_a(y, c_signals_a)
+ y = interleave_tensors(y)
+
+ y = torch.concat((y, c_upsampled_b), dim=-1)
+ y, gru_b_state = self.gru_b(y, gru_states[1])
+ c_signals_b = self.prepare_signals(signals, 1, self.signals_idx_b)
+ y = self.subconditioner_b(y, c_signals_b)
+
+ y = [self.dual_fc[i](y[i]) for i in range(self.substeps_b)]
+ y = interleave_tensors(y)
+
+ return y, (gru_a_state, gru_b_state)
+
+ def decoder(self, signals, c, gru_states):
+ embedded_signals = torch.flatten(self.signal_embedding(signals), 2, 3)
+
+ y = torch.concat((embedded_signals, c), dim=-1)
+ y, gru_a_state = self.gru_a(y, gru_states[0])
+ y = torch.concat((y, c), dim=-1)
+ y, gru_b_state = self.gru_b(y, gru_states[1])
+
+ y = self.dual_fc(y)
+
+ return torch.softmax(y, dim=-1), (gru_a_state, gru_b_state)
+
+ def forward(self, features, periods, signals, gru_states):
+
+ c = self.frame_rate_network(features, periods)
+ y, _ = self.sample_rate_network(signals, c, gru_states)
+ log_probs = torch.log_softmax(y, dim=-1)
+
+ return log_probs
+
+ def generate(self, features, periods, lpcs):
+
+ with torch.no_grad():
+ device = self.parameters().__next__().device
+
+ num_frames = features.shape[0] - self.feature_history - self.feature_lookahead
+ lpc_order = lpcs.shape[-1]
+ num_input_signals = len(self.signals)
+ pitch_corr_position = self.input_layout['features']['pitch_corr'][0]
+
+ # signal buffers
+ last_signal = torch.zeros((num_frames * self.frame_size + lpc_order + 1))
+ prediction = torch.zeros((num_frames * self.frame_size + lpc_order + 1))
+ last_error = torch.zeros((num_frames * self.frame_size + lpc_order + 1))
+ output = torch.zeros((num_frames * self.frame_size), dtype=torch.int16)
+ mem = 0
+
+ # state buffers
+ gru_a_state = torch.zeros((1, 1, self.gru_a_units))
+ gru_b_state = torch.zeros((1, 1, self.gru_b_units))
+
+ input_signals = 128 + torch.zeros(self.group_size_gru_a * num_input_signals, dtype=torch.long)
+ # conditioning signals for subconditioner a
+ c_signals_a = 128 + torch.zeros(self.group_size_subcondition_a * len(self.signals_idx_a), dtype=torch.long)
+ # conditioning signals for subconditioner b
+ c_signals_b = 128 + torch.zeros(len(self.signals_idx_b), dtype=torch.long)
+
+ # signal dict
+ signal_dict = {
+ 'prediction' : prediction,
+ 'last_error' : last_error,
+ 'last_signal' : last_signal
+ }
+
+ # push data to device
+ features = features.to(device)
+ periods = periods.to(device)
+ lpcs = lpcs.to(device)
+
+ # run feature encoding
+ c = self.frame_rate_network(features.unsqueeze(0), periods.unsqueeze(0))
+
+ for frame_index in range(num_frames):
+ frame_start = frame_index * self.frame_size
+ pitch_corr = features[frame_index + self.feature_history, pitch_corr_position]
+ a = - torch.flip(lpcs[frame_index + self.feature_history], [0])
+ current_c = c[:, frame_index : frame_index + 1, :]
+
+ for i in range(0, self.frame_size, self.group_size_gru_a):
+ pcm_position = frame_start + i + lpc_order
+ output_position = frame_start + i
+
+ # calculate newest prediction
+ prediction[pcm_position] = torch.sum(last_signal[pcm_position - lpc_order + 1: pcm_position + 1] * a)
+
+ # prepare input
+ for slot in range(self.group_size_gru_a):
+ k = slot - self.group_size_gru_a + 1
+ for idx, name in enumerate(self.signals):
+ input_signals[idx + slot * num_input_signals] = lin2ulawq(
+ signal_dict[name][pcm_position + k]
+ )
+
+
+ # run GRU A
+ embed_signals = self.signal_embedding(input_signals.reshape((1, 1, -1)))
+ embed_signals = torch.flatten(embed_signals, 2)
+ y = torch.cat((embed_signals, current_c), dim=-1)
+ h_a, gru_a_state = self.gru_a(y, gru_a_state)
+
+ # loop over substeps_a
+ for step_a in range(self.substeps_a):
+ # prepare conditioning input
+ for slot in range(self.group_size_subcondition_a):
+ k = slot - self.group_size_subcondition_a + 1
+ for idx, name in enumerate(self.subcondition_signals_a):
+ c_signals_a[idx + slot * num_input_signals] = lin2ulawq(
+ signal_dict[name][pcm_position + k]
+ )
+
+ # subconditioning
+ h_a = self.subconditioner_a.single_step(step_a, h_a, c_signals_a.reshape((1, 1, -1)))
+
+ # run GRU B
+ y = torch.cat((h_a, current_c), dim=-1)
+ h_b, gru_b_state = self.gru_b(y, gru_b_state)
+
+ # loop over substeps b
+ for step_b in range(self.substeps_b):
+ # prepare subconditioning input
+ for idx, name in enumerate(self.subcondition_signals_b):
+ c_signals_b[idx] = lin2ulawq(
+ signal_dict[name][pcm_position]
+ )
+
+ # subcondition
+ h_b = self.subconditioner_b.single_step(step_b, h_b, c_signals_b.reshape((1, 1, -1)))
+
+ # run dual FC
+ probs = torch.softmax(self.dual_fc[step_b](h_b), dim=-1)
+
+ # sample
+ new_exc = ulaw2lin(sample_excitation(probs, pitch_corr))
+
+ # update signals
+ sig = new_exc + prediction[pcm_position]
+ last_error[pcm_position + 1] = new_exc
+ last_signal[pcm_position + 1] = sig
+
+ mem = 0.85 * mem + float(sig)
+ output[output_position] = clip_to_int16(round(mem))
+
+ # increase positions
+ pcm_position += 1
+ output_position += 1
+
+ # calculate next prediction
+ prediction[pcm_position] = torch.sum(last_signal[pcm_position - lpc_order + 1: pcm_position + 1] * a)
+
+ return output
diff --git a/dnn/torch/lpcnet/print_lpcnet_complexity.py b/dnn/torch/lpcnet/print_lpcnet_complexity.py
new file mode 100644
index 00000000..d6072dc5
--- /dev/null
+++ b/dnn/torch/lpcnet/print_lpcnet_complexity.py
@@ -0,0 +1,64 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import argparse
+
+import yaml
+
+from models import model_dict
+
+
+debug = False
+if debug:
+ args = type('dummy', (object,),
+ {
+ 'setup' : 'setups/lpcnet_m/setup_1_4_concatenative.yml',
+ 'hierarchical_sampling' : False
+ })()
+else:
+ parser = argparse.ArgumentParser()
+ parser.add_argument('setup', type=str, help='setup yaml file')
+ parser.add_argument('--hierarchical-sampling', action="store_true", help='whether to assume hierarchical sampling (default=False)', default=False)
+
+ args = parser.parse_args()
+
+with open(args.setup, 'r') as f:
+ setup = yaml.load(f.read(), yaml.FullLoader)
+
+# check model
+if not 'model' in setup['lpcnet']:
+ print(f'warning: did not find model entry in setup, using default lpcnet')
+ model_name = 'lpcnet'
+else:
+ model_name = setup['lpcnet']['model']
+
+# create model
+model = model_dict[model_name](setup['lpcnet']['config'])
+
+gflops = model.get_gflops(16000, verbose=True, hierarchical_sampling=args.hierarchical_sampling)
diff --git a/dnn/torch/lpcnet/scripts/collect_multi_run_results.py b/dnn/torch/lpcnet/scripts/collect_multi_run_results.py
new file mode 100644
index 00000000..ae662a4f
--- /dev/null
+++ b/dnn/torch/lpcnet/scripts/collect_multi_run_results.py
@@ -0,0 +1,190 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import argparse
+import os
+from uuid import UUID
+from collections import OrderedDict
+import pickle
+
+
+import torch
+import numpy as np
+
+import utils
+
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("input", type=str, help="input folder containing multi-run output")
+parser.add_argument("tag", type=str, help="tag for multi-run experiment")
+parser.add_argument("csv", type=str, help="name for output csv")
+
+
+def is_uuid(val):
+ try:
+ UUID(val)
+ return True
+ except:
+ return False
+
+
+def collect_results(folder):
+
+ training_folder = os.path.join(folder, 'training')
+ testing_folder = os.path.join(folder, 'testing')
+
+ # validation loss
+ checkpoint = torch.load(os.path.join(training_folder, 'checkpoints', 'checkpoint_finalize_epoch_1.pth'), map_location='cpu')
+ validation_loss = checkpoint['validation_loss']
+
+ # eval_warpq
+ eval_warpq = utils.data.parse_warpq_scores(os.path.join(training_folder, 'out_finalize.txt'))[-1]
+
+ # testing results
+ testing_results = utils.data.collect_test_stats(os.path.join(testing_folder, 'final'))
+
+ results = OrderedDict()
+ results['eval_loss'] = validation_loss
+ results['eval_warpq'] = eval_warpq
+ results['pesq_mean'] = testing_results['pesq'][0]
+ results['warpq_mean'] = testing_results['warpq'][0]
+ results['pitch_error_mean'] = testing_results['pitch_error'][0]
+ results['voicing_error_mean'] = testing_results['voicing_error'][0]
+
+ return results
+
+def print_csv(path, results, tag, ranks=None, header=True):
+
+ metrics = next(iter(results.values())).keys()
+ if ranks is not None:
+ rank_keys = next(iter(ranks.values())).keys()
+ else:
+ rank_keys = []
+
+ with open(path, 'w') as f:
+ if header:
+ f.write("uuid, tag")
+
+ for metric in metrics:
+ f.write(f", {metric}")
+
+ for rank in rank_keys:
+ f.write(f", {rank}")
+
+ f.write("\n")
+
+
+ for uuid, values in results.items():
+ f.write(f"{uuid}, {tag}")
+
+ for val in values.values():
+ f.write(f", {val:10.8f}")
+
+ for rank in rank_keys:
+ f.write(f", {ranks[uuid][rank]:4d}")
+
+ f.write("\n")
+
+def get_ranks(results):
+
+ metrics = list(next(iter(results.values())).keys())
+
+ positive = {'pesq_mean', 'mix'}
+
+ ranks = OrderedDict()
+ for key in results.keys():
+ ranks[key] = OrderedDict()
+
+ for metric in metrics:
+ sign = -1 if metric in positive else 1
+
+ x = sorted([(key, value[metric]) for key, value in results.items()], key=lambda x: sign * x[1])
+ x = [y[0] for y in x]
+
+ for key in results.keys():
+ ranks[key]['rank_' + metric] = x.index(key) + 1
+
+ return ranks
+
+def analyse_metrics(results):
+ metrics = ['eval_loss', 'pesq_mean', 'warpq_mean', 'pitch_error_mean', 'voicing_error_mean']
+
+ x = []
+ for metric in metrics:
+ x.append([val[metric] for val in results.values()])
+
+ x = np.array(x)
+
+ print(x)
+
+def add_mix_metric(results):
+ metrics = ['eval_loss', 'pesq_mean', 'warpq_mean', 'pitch_error_mean', 'voicing_error_mean']
+
+ x = []
+ for metric in metrics:
+ x.append([val[metric] for val in results.values()])
+
+ x = np.array(x).transpose() * np.array([-1, 1, -1, -1, -1])
+
+ z = (x - np.mean(x, axis=0)) / np.std(x, axis=0)
+
+ print(f"covariance matrix for normalized scores of {metrics}:")
+ print(np.cov(z.transpose()))
+
+ score = np.mean(z, axis=1)
+
+ for i, key in enumerate(results.keys()):
+ results[key]['mix'] = score[i].item()
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+ uuids = sorted([x for x in os.listdir(args.input) if os.path.isdir(os.path.join(args.input, x)) and is_uuid(x)])
+
+
+ results = OrderedDict()
+
+ for uuid in uuids:
+ results[uuid] = collect_results(os.path.join(args.input, uuid))
+
+
+ add_mix_metric(results)
+
+ ranks = get_ranks(results)
+
+
+
+ csv = args.csv if args.csv.endswith('.csv') else args.csv + '.csv'
+
+ print_csv(args.csv, results, args.tag, ranks=ranks)
+
+
+ with open(csv[:-4] + '.pickle', 'wb') as f:
+ pickle.dump(results, f, protocol=pickle.HIGHEST_PROTOCOL) \ No newline at end of file
diff --git a/dnn/torch/lpcnet/scripts/loop_run.sh b/dnn/torch/lpcnet/scripts/loop_run.sh
new file mode 100644
index 00000000..7250f639
--- /dev/null
+++ b/dnn/torch/lpcnet/scripts/loop_run.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+
+case $# in
+ 9) SETUP=$1; OUTDIR=$2; NAME=$3; DEVICE=$4; ROUNDS=$5; LPCNEXT=$6; LPCNET=$7; TESTSUITE=$8; TESTITEMS=$9;;
+ *) echo "loop_run.sh setup outdir name device rounds lpcnext_repo lpcnet_repo testsuite_repo testitems"; exit;;
+esac
+
+
+PYTHON="/home/ubuntu/opt/miniconda3/envs/torch/bin/python"
+TESTFEATURES=${LPCNEXT}/testitems/features/all_0_orig_features.f32
+WARPQREFERENCE=${LPCNEXT}/testitems/wav/all_0_orig.wav
+METRICS="warpq,pesq,pitch_error,voicing_error"
+LPCNETDEMO=${LPCNET}/lpcnet_demo
+
+for ((round = 1; round <= $ROUNDS; round++))
+do
+ echo
+ echo round $round
+
+ UUID=$(uuidgen)
+ TRAINOUT=${OUTDIR}/${UUID}/training
+ TESTOUT=${OUTDIR}/${UUID}/testing
+ CHECKPOINT=${TRAINOUT}/checkpoints/checkpoint_last.pth
+ FINALCHECKPOINT=${TRAINOUT}/checkpoints/checkpoint_finalize_last.pth
+
+ # run training
+ echo "starting training..."
+ $PYTHON $LPCNEXT/train_lpcnet.py $SETUP $TRAINOUT --device $DEVICE --test-features $TESTFEATURES --warpq-reference $WARPQREFERENCE
+
+ # run finalization
+ echo "starting finalization..."
+ $PYTHON $LPCNEXT/train_lpcnet.py $SETUP $TRAINOUT \
+ --device $DEVICE --test-features $TESTFEATURES \
+ --warpq-reference $WARPQREFERENCE \
+ --finalize --initial-checkpoint $CHECKPOINT
+
+ # create test configs
+ $PYTHON $LPCNEXT/make_test_config.py ${OUTDIR}/${UUID}/testconfig.yml "$NAME $UUID" $CHECKPOINT --lpcnet-demo $LPCNETDEMO
+ $PYTHON $LPCNEXT/make_test_config.py ${OUTDIR}/${UUID}/testconfig_finalize.yml "$NAME $UUID finalized" $FINALCHECKPOINT --lpcnet-demo $LPCNETDEMO
+
+ # run tests
+ echo "starting test 1 (no finalization)..."
+ $PYTHON $TESTSUITE/run_test.py ${OUTDIR}/${UUID}/testconfig.yml \
+ $TESTITEMS ${TESTOUT}/prefinal --num-workers 8 \
+ --num-testitems 400 --metrics $METRICS
+
+ echo "starting test 2 (after finalization)..."
+ $PYTHON $TESTSUITE/run_test.py ${OUTDIR}/${UUID}/testconfig_finalize.yml \
+ $TESTITEMS ${TESTOUT}/final --num-workers 8 \
+ --num-testitems 400 --metrics $METRICS
+done
diff --git a/dnn/torch/lpcnet/scripts/make_animation.py b/dnn/torch/lpcnet/scripts/make_animation.py
new file mode 100644
index 00000000..57656ef1
--- /dev/null
+++ b/dnn/torch/lpcnet/scripts/make_animation.py
@@ -0,0 +1,67 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+
+""" script for creating animations from debug data
+
+"""
+
+
+import argparse
+
+
+import sys
+sys.path.append('./')
+
+from utils.endoscopy import make_animation, read_data
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('folder', type=str, help='endoscopy folder with debug output')
+parser.add_argument('output', type=str, help='output file (will be auto-extended with .mp4)')
+
+parser.add_argument('--start-index', type=int, help='index of first sample to be considered', default=0)
+parser.add_argument('--stop-index', type=int, help='index of last sample to be considered', default=-1)
+parser.add_argument('--interval', type=int, help='interval between frames in ms', default=20)
+parser.add_argument('--half-window-length', type=int, help='half size of window for displaying signals', default=80)
+
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+ filename = args.output if args.output.endswith('.mp4') else args.output + '.mp4'
+ data = read_data(args.folder)
+
+ make_animation(
+ data,
+ filename,
+ start_index=args.start_index,
+ stop_index = args.stop_index,
+ half_signal_window_length=args.half_window_length
+ )
diff --git a/dnn/torch/lpcnet/scripts/modify_dataset_target.py b/dnn/torch/lpcnet/scripts/modify_dataset_target.py
new file mode 100644
index 00000000..a70fe169
--- /dev/null
+++ b/dnn/torch/lpcnet/scripts/modify_dataset_target.py
@@ -0,0 +1,17 @@
+import argparse
+
+import numpy as np
+
+
+parser = argparse.ArgumentParser(description="sets s_t to augmented_s_t")
+
+parser.add_argument('datafile', type=str, help='data.s16 file path')
+
+args = parser.parse_args()
+
+data = np.memmap(args.datafile, dtype='int16', mode='readwrite')
+
+# signal is in data[1::2]
+# last augmented signal is in data[0::2]
+
+data[1 : - 1 : 2] = data[2 : : 2]
diff --git a/dnn/torch/lpcnet/scripts/multi_run.sh b/dnn/torch/lpcnet/scripts/multi_run.sh
new file mode 100644
index 00000000..fb0fee14
--- /dev/null
+++ b/dnn/torch/lpcnet/scripts/multi_run.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+case $# in
+ 9) SETUP=$1; OUTDIR=$2; NAME=$3; NUMDEVICES=$4; ROUNDS=$5; LPCNEXT=$6; LPCNET=$7; TESTSUITE=$8; TESTITEMS=$9;;
+ *) echo "multi_run.sh setup outdir name num_devices rounds_per_device lpcnext_repo lpcnet_repo testsuite_repo testitems"; exit;;
+esac
+
+
+LOOPRUN=${LPCNEXT}/loop_run.sh
+
+mkdir -p $OUTDIR
+
+for ((i = 0; i < $NUMDEVICES; i++))
+do
+ echo "launching job queue for device $i"
+ nohup bash $LOOPRUN $SETUP $OUTDIR "$NAME" "cuda:$i" $ROUNDS $LPCNEXT $LPCNET $TESTSUITE $TESTITEMS > $OUTDIR/job_${i}_out.txt &
+done
diff --git a/dnn/torch/lpcnet/scripts/run_inference_test.sh b/dnn/torch/lpcnet/scripts/run_inference_test.sh
new file mode 100644
index 00000000..9f22b03d
--- /dev/null
+++ b/dnn/torch/lpcnet/scripts/run_inference_test.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+
+case $# in
+ 3) FEATURES=$1; FOLDER=$2; PYTHON=$3;;
+ *) echo "run_inference_test.sh <features file> <output folder> <python path>"; exit;;
+esac
+
+
+SCRIPTFOLDER=$(dirname "$0")
+
+mkdir -p $FOLDER/inference_test
+
+# update checkpoints
+for fn in $(find $FOLDER -type f -name "checkpoint*.pth")
+do
+ tmp=$(basename $fn)
+ tmp=${tmp%.pth}
+ epoch=${tmp#checkpoint_epoch_}
+ echo "running inference with checkpoint $fn..."
+ $PYTHON $SCRIPTFOLDER/../test_lpcnet.py $FEATURES $fn $FOLDER/inference_test/output_epoch_${epoch}.wav
+done
diff --git a/dnn/torch/lpcnet/scripts/update_checkpoints.py b/dnn/torch/lpcnet/scripts/update_checkpoints.py
new file mode 100644
index 00000000..8f00a7e5
--- /dev/null
+++ b/dnn/torch/lpcnet/scripts/update_checkpoints.py
@@ -0,0 +1,54 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+""" script for updating checkpoints with new setup entries
+
+ Use this script to update older outputs with newly introduced
+ parameters. (Saves us the trouble of backward compatibility)
+"""
+
+
+import argparse
+
+import torch
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('checkpoint_file', type=str, help='checkpoint to be updated')
+parser.add_argument('--model', type=str, help='model update', default=None)
+
+args = parser.parse_args()
+
+checkpoint = torch.load(args.checkpoint_file, map_location='cpu')
+
+# update model entry
+if type(args.model) != type(None):
+ checkpoint['setup']['lpcnet']['model'] = args.model
+
+torch.save(checkpoint, args.checkpoint_file) \ No newline at end of file
diff --git a/dnn/torch/lpcnet/scripts/update_output_folder.sh b/dnn/torch/lpcnet/scripts/update_output_folder.sh
new file mode 100644
index 00000000..487d4a2d
--- /dev/null
+++ b/dnn/torch/lpcnet/scripts/update_output_folder.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+
+case $# in
+ 3) FOLDER=$1; MODEL=$2; PYTHON=$3;;
+ *) echo "update_output_folder.sh folder model python"; exit;;
+esac
+
+
+SCRIPTFOLDER=$(dirname "$0")
+
+
+# update setup
+echo "updating $FOLDER/setup.py..."
+$PYTHON $SCRIPTFOLDER/update_setups.py $FOLDER/setup.yml --model $MODEL
+
+# update checkpoints
+for fn in $(find $FOLDER -type f -name "checkpoint*.pth")
+do
+ echo "updating $fn..."
+ $PYTHON $SCRIPTFOLDER/update_checkpoints.py $fn --model $MODEL
+done \ No newline at end of file
diff --git a/dnn/torch/lpcnet/scripts/update_setups.py b/dnn/torch/lpcnet/scripts/update_setups.py
new file mode 100644
index 00000000..52f81cf4
--- /dev/null
+++ b/dnn/torch/lpcnet/scripts/update_setups.py
@@ -0,0 +1,57 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+""" script for updating setup files with new setup entries
+
+ Use this script to update older outputs with newly introduced
+ parameters. (Saves us the trouble of backward compatibility)
+"""
+
+import argparse
+
+import yaml
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('setup_file', type=str, help='setup to be updated')
+parser.add_argument('--model', type=str, help='model update', default=None)
+
+args = parser.parse_args()
+
+# load setup
+with open(args.setup_file, 'r') as f:
+ setup = yaml.load(f.read(), yaml.FullLoader)
+
+# update model entry
+if type(args.model) != type(None):
+ setup['lpcnet']['model'] = args.model
+
+# dump result
+with open(args.setup_file, 'w') as f:
+ yaml.dump(setup, f)
diff --git a/dnn/torch/lpcnet/test_lpcnet.py b/dnn/torch/lpcnet/test_lpcnet.py
new file mode 100644
index 00000000..49db8b06
--- /dev/null
+++ b/dnn/torch/lpcnet/test_lpcnet.py
@@ -0,0 +1,89 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import argparse
+
+import torch
+import numpy as np
+
+
+from models import model_dict
+from utils.data import load_features
+from utils.wav import wavwrite16
+
+debug = False
+if debug:
+ args = type('dummy', (object,),
+ {
+ 'features' : 'features.f32',
+ 'checkpoint' : 'checkpoint.pth',
+ 'output' : 'out.wav',
+ 'version' : 2
+ })()
+else:
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument('features', type=str, help='feature file')
+ parser.add_argument('checkpoint', type=str, help='checkpoint file')
+ parser.add_argument('output', type=str, help='output file')
+ parser.add_argument('--version', type=int, help='feature version', default=2)
+
+ args = parser.parse_args()
+
+
+torch.set_num_threads(2)
+
+version = args.version
+feature_file = args.features
+checkpoint_file = args.checkpoint
+
+
+
+output_file = args.output
+if not output_file.endswith('.wav'):
+ output_file += '.wav'
+
+checkpoint = torch.load(checkpoint_file, map_location="cpu")
+
+# check model
+if not 'model' in checkpoint['setup']['lpcnet']:
+ print(f'warning: did not find model entry in setup, using default lpcnet')
+ model_name = 'lpcnet'
+else:
+ model_name = checkpoint['setup']['lpcnet']['model']
+
+model = model_dict[model_name](checkpoint['setup']['lpcnet']['config'])
+
+model.load_state_dict(checkpoint['state_dict'])
+
+data = load_features(feature_file)
+
+output = model.generate(data['features'], data['periods'], data['lpcs'])
+
+wavwrite16(output_file, output.numpy(), 16000)
diff --git a/dnn/torch/lpcnet/train_lpcnet.py b/dnn/torch/lpcnet/train_lpcnet.py
new file mode 100644
index 00000000..c6121c87
--- /dev/null
+++ b/dnn/torch/lpcnet/train_lpcnet.py
@@ -0,0 +1,272 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+import sys
+
+try:
+ import git
+ has_git = True
+except:
+ has_git = False
+
+import yaml
+
+
+import torch
+from torch.optim.lr_scheduler import LambdaLR
+
+from data import LPCNetDataset
+from models import model_dict
+from engine.lpcnet_engine import train_one_epoch, evaluate
+from utils.data import load_features
+from utils.wav import wavwrite16
+
+
+debug = False
+if debug:
+ args = type('dummy', (object,),
+ {
+ 'setup' : 'setup.yml',
+ 'output' : 'testout',
+ 'device' : None,
+ 'test_features' : None,
+ 'finalize': False,
+ 'initial_checkpoint': None,
+ 'no-redirect': False
+ })()
+else:
+ parser = argparse.ArgumentParser("train_lpcnet.py")
+ parser.add_argument('setup', type=str, help='setup yaml file')
+ parser.add_argument('output', type=str, help='output path')
+ parser.add_argument('--device', type=str, help='compute device', default=None)
+ parser.add_argument('--test-features', type=str, help='test feature file in v2 format', default=None)
+ parser.add_argument('--finalize', action='store_true', help='run single training round with lr=1e-5')
+ parser.add_argument('--initial-checkpoint', type=str, help='initial checkpoint', default=None)
+ parser.add_argument('--no-redirect', action='store_true', help='disables re-direction of output')
+
+ args = parser.parse_args()
+
+
+torch.set_num_threads(4)
+
+with open(args.setup, 'r') as f:
+ setup = yaml.load(f.read(), yaml.FullLoader)
+
+if args.finalize:
+ if args.initial_checkpoint is None:
+ raise ValueError('finalization requires initial checkpoint')
+
+ if 'sparsification' in setup['lpcnet']['config']:
+ for sp_job in setup['lpcnet']['config']['sparsification'].values():
+ sp_job['start'], sp_job['stop'] = 0, 0
+
+ setup['training']['lr'] = 1.0e-5
+ setup['training']['lr_decay_factor'] = 0.0
+ setup['training']['epochs'] = 1
+
+ checkpoint_prefix = 'checkpoint_finalize'
+ output_prefix = 'output_finalize'
+ setup_name = 'setup_finalize.yml'
+ output_file='out_finalize.txt'
+else:
+ checkpoint_prefix = 'checkpoint'
+ output_prefix = 'output'
+ setup_name = 'setup.yml'
+ output_file='out.txt'
+
+
+# check model
+if not 'model' in setup['lpcnet']:
+ print(f'warning: did not find model entry in setup, using default lpcnet')
+ model_name = 'lpcnet'
+else:
+ model_name = setup['lpcnet']['model']
+
+# prepare output folder
+if os.path.exists(args.output) and not debug and not args.finalize:
+ print("warning: output folder exists")
+
+ reply = input('continue? (y/n): ')
+ while reply not in {'y', 'n'}:
+ reply = input('continue? (y/n): ')
+
+ if reply == 'n':
+ os._exit()
+else:
+ os.makedirs(args.output, exist_ok=True)
+
+checkpoint_dir = os.path.join(args.output, 'checkpoints')
+os.makedirs(checkpoint_dir, exist_ok=True)
+
+
+# add repo info to setup
+if has_git:
+ working_dir = os.path.split(__file__)[0]
+ try:
+ repo = git.Repo(working_dir)
+ setup['repo'] = dict()
+ hash = repo.head.object.hexsha
+ urls = list(repo.remote().urls)
+ is_dirty = repo.is_dirty()
+
+ if is_dirty:
+ print("warning: repo is dirty")
+
+ setup['repo']['hash'] = hash
+ setup['repo']['urls'] = urls
+ setup['repo']['dirty'] = is_dirty
+ except:
+ has_git = False
+
+# dump setup
+with open(os.path.join(args.output, setup_name), 'w') as f:
+ yaml.dump(setup, f)
+
+# prepare inference test if wanted
+run_inference_test = False
+if type(args.test_features) != type(None):
+ test_features = load_features(args.test_features)
+ inference_test_dir = os.path.join(args.output, 'inference_test')
+ os.makedirs(inference_test_dir, exist_ok=True)
+ run_inference_test = True
+
+# training parameters
+batch_size = setup['training']['batch_size']
+epochs = setup['training']['epochs']
+lr = setup['training']['lr']
+lr_decay_factor = setup['training']['lr_decay_factor']
+
+# load training dataset
+lpcnet_config = setup['lpcnet']['config']
+data = LPCNetDataset( setup['dataset'],
+ features=lpcnet_config['features'],
+ input_signals=lpcnet_config['signals'],
+ target=lpcnet_config['target'],
+ frames_per_sample=setup['training']['frames_per_sample'],
+ feature_history=lpcnet_config['feature_history'],
+ feature_lookahead=lpcnet_config['feature_lookahead'],
+ lpc_gamma=lpcnet_config.get('lpc_gamma', 1))
+
+# load validation dataset if given
+if 'validation_dataset' in setup:
+ validation_data = LPCNetDataset( setup['validation_dataset'],
+ features=lpcnet_config['features'],
+ input_signals=lpcnet_config['signals'],
+ target=lpcnet_config['target'],
+ frames_per_sample=setup['training']['frames_per_sample'],
+ feature_history=lpcnet_config['feature_history'],
+ feature_lookahead=lpcnet_config['feature_lookahead'],
+ lpc_gamma=lpcnet_config.get('lpc_gamma', 1))
+
+ validation_dataloader = torch.utils.data.DataLoader(validation_data, batch_size=batch_size, drop_last=True, num_workers=4)
+
+ run_validation = True
+else:
+ run_validation = False
+
+# create model
+model = model_dict[model_name](setup['lpcnet']['config'])
+
+if args.initial_checkpoint is not None:
+ print(f"loading state dict from {args.initial_checkpoint}...")
+ chkpt = torch.load(args.initial_checkpoint, map_location='cpu')
+ model.load_state_dict(chkpt['state_dict'])
+
+# set compute device
+if type(args.device) == type(None):
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+else:
+ device = torch.device(args.device)
+
+# push model to device
+model.to(device)
+
+# dataloader
+dataloader = torch.utils.data.DataLoader(data, batch_size=batch_size, drop_last=True, shuffle=True, num_workers=4)
+
+# optimizer is introduced to trainable parameters
+parameters = [p for p in model.parameters() if p.requires_grad]
+optimizer = torch.optim.Adam(parameters, lr=lr)
+
+# learning rate scheduler
+scheduler = LambdaLR(optimizer=optimizer, lr_lambda=lambda x : 1 / (1 + lr_decay_factor * x))
+
+# loss
+criterion = torch.nn.NLLLoss()
+
+# model checkpoint
+checkpoint = {
+ 'setup' : setup,
+ 'state_dict' : model.state_dict(),
+ 'loss' : -1
+}
+
+if not args.no_redirect:
+ print(f"re-directing output to {os.path.join(args.output, output_file)}")
+ sys.stdout = open(os.path.join(args.output, output_file), "w")
+
+best_loss = 1e9
+
+for ep in range(1, epochs + 1):
+ print(f"training epoch {ep}...")
+ new_loss = train_one_epoch(model, criterion, optimizer, dataloader, device, scheduler)
+
+
+ # save checkpoint
+ checkpoint['state_dict'] = model.state_dict()
+ checkpoint['loss'] = new_loss
+
+ if run_validation:
+ print("running validation...")
+ validation_loss = evaluate(model, criterion, validation_dataloader, device)
+ checkpoint['validation_loss'] = validation_loss
+
+ if validation_loss < best_loss:
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_best.pth'))
+ best_loss = validation_loss
+
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_epoch_{ep}.pth'))
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_last.pth'))
+
+ # run inference test
+ if run_inference_test:
+ model.to("cpu")
+ print("running inference test...")
+
+ output = model.generate(test_features['features'], test_features['periods'], test_features['lpcs'])
+
+ testfilename = os.path.join(inference_test_dir, output_prefix + f'_epoch_{ep}.wav')
+
+ wavwrite16(testfilename, output.numpy(), 16000)
+
+ model.to(device)
+
+ print()
diff --git a/dnn/torch/lpcnet/utils/__init__.py b/dnn/torch/lpcnet/utils/__init__.py
new file mode 100644
index 00000000..edbbe02c
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/__init__.py
@@ -0,0 +1,4 @@
+from . import sparsification
+from . import data
+from . import pcm
+from . import sample \ No newline at end of file
diff --git a/dnn/torch/lpcnet/utils/data.py b/dnn/torch/lpcnet/utils/data.py
new file mode 100644
index 00000000..5d6659ef
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/data.py
@@ -0,0 +1,141 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+
+import torch
+import numpy as np
+
+def load_features(feature_file, version=2):
+ if version == 2:
+ layout = {
+ 'cepstrum': [0,18],
+ 'periods': [18, 19],
+ 'pitch_corr': [19, 20],
+ 'lpc': [20, 36]
+ }
+ frame_length = 36
+
+ elif version == 1:
+ layout = {
+ 'cepstrum': [0,18],
+ 'periods': [36, 37],
+ 'pitch_corr': [37, 38],
+ 'lpc': [39, 55],
+ }
+ frame_length = 55
+ else:
+ raise ValueError(f'unknown feature version: {version}')
+
+
+ raw_features = torch.from_numpy(np.fromfile(feature_file, dtype='float32'))
+ raw_features = raw_features.reshape((-1, frame_length))
+
+ features = torch.cat(
+ [
+ raw_features[:, layout['cepstrum'][0] : layout['cepstrum'][1]],
+ raw_features[:, layout['pitch_corr'][0] : layout['pitch_corr'][1]]
+ ],
+ dim=1
+ )
+
+ lpcs = raw_features[:, layout['lpc'][0] : layout['lpc'][1]]
+ periods = (0.1 + 50 * raw_features[:, layout['periods'][0] : layout['periods'][1]] + 100).long()
+
+ return {'features' : features, 'periods' : periods, 'lpcs' : lpcs}
+
+
+
+def create_new_data(signal_path, reference_data_path, new_data_path, offset=320, preemph_factor=0.85):
+ ref_data = np.memmap(reference_data_path, dtype=np.int16)
+ signal = np.memmap(signal_path, dtype=np.int16)
+
+ signal_preemph_path = os.path.splitext(signal_path)[0] + '_preemph.raw'
+ signal_preemph = np.memmap(signal_preemph_path, dtype=np.int16, mode='write', shape=signal.shape)
+
+
+ assert len(signal) % 160 == 0
+ num_frames = len(signal) // 160
+ mem = np.zeros(1)
+ for fr in range(len(signal)//160):
+ signal_preemph[fr * 160 : (fr + 1) * 160] = np.convolve(np.concatenate((mem, signal[fr * 160 : (fr + 1) * 160])), [1, -preemph_factor], mode='valid')
+ mem = signal[(fr + 1) * 160 - 1 : (fr + 1) * 160]
+
+ new_data = np.memmap(new_data_path, dtype=np.int16, mode='write', shape=ref_data.shape)
+
+ new_data[:] = 0
+ N = len(signal) - offset
+ new_data[1 : 2*N + 1: 2] = signal_preemph[offset:]
+ new_data[2 : 2*N + 2: 2] = signal_preemph[offset:]
+
+
+def parse_warpq_scores(output_file):
+ """ extracts warpq scores from output file """
+
+ with open(output_file, "r") as f:
+ lines = f.readlines()
+
+ scores = [float(line.split("WARP-Q score:")[-1]) for line in lines if line.startswith("WARP-Q score:")]
+
+ return scores
+
+
+def parse_stats_file(file):
+
+ with open(file, "r") as f:
+ lines = f.readlines()
+
+ mean = float(lines[0].split(":")[-1])
+ bt_mean = float(lines[1].split(":")[-1])
+ top_mean = float(lines[2].split(":")[-1])
+
+ return mean, bt_mean, top_mean
+
+def collect_test_stats(test_folder):
+ """ collects statistics for all discovered metrics from test folder """
+
+ metrics = {'pesq', 'warpq', 'pitch_error', 'voicing_error'}
+
+ results = dict()
+
+ content = os.listdir(test_folder)
+
+ stats_files = [file for file in content if file.startswith('stats_')]
+
+ for file in stats_files:
+ metric = file[len("stats_") : -len(".txt")]
+
+ if metric not in metrics:
+ print(f"warning: unknown metric {metric}")
+
+ mean, bt_mean, top_mean = parse_stats_file(os.path.join(test_folder, file))
+
+ results[metric] = [mean, bt_mean, top_mean]
+
+ return results
diff --git a/dnn/torch/lpcnet/utils/endoscopy.py b/dnn/torch/lpcnet/utils/endoscopy.py
new file mode 100644
index 00000000..141447e2
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/endoscopy.py
@@ -0,0 +1,234 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+""" module for inspecting models during inference """
+
+import os
+
+import yaml
+import matplotlib.pyplot as plt
+import matplotlib.animation as animation
+
+import torch
+import numpy as np
+
+# stores entries {key : {'fid' : fid, 'fs' : fs, 'dim' : dim, 'dtype' : dtype}}
+_state = dict()
+_folder = 'endoscopy'
+
+def get_gru_gates(gru, input, state):
+ hidden_size = gru.hidden_size
+
+ direct = torch.matmul(gru.weight_ih_l0, input.squeeze())
+ recurrent = torch.matmul(gru.weight_hh_l0, state.squeeze())
+
+ # reset gate
+ start, stop = 0 * hidden_size, 1 * hidden_size
+ reset_gate = torch.sigmoid(direct[start : stop] + gru.bias_ih_l0[start : stop] + recurrent[start : stop] + gru.bias_hh_l0[start : stop])
+
+ # update gate
+ start, stop = 1 * hidden_size, 2 * hidden_size
+ update_gate = torch.sigmoid(direct[start : stop] + gru.bias_ih_l0[start : stop] + recurrent[start : stop] + gru.bias_hh_l0[start : stop])
+
+ # new gate
+ start, stop = 2 * hidden_size, 3 * hidden_size
+ new_gate = torch.tanh(direct[start : stop] + gru.bias_ih_l0[start : stop] + reset_gate * (recurrent[start : stop] + gru.bias_hh_l0[start : stop]))
+
+ return {'reset_gate' : reset_gate, 'update_gate' : update_gate, 'new_gate' : new_gate}
+
+
+def init(folder='endoscopy'):
+ """ sets up output folder for endoscopy data """
+
+ global _folder
+ _folder = folder
+
+ if not os.path.exists(folder):
+ os.makedirs(folder)
+ else:
+ print(f"warning: endoscopy folder {folder} exists. Content may be lost or inconsistent results may occur.")
+
+def write_data(key, data, fs):
+ """ appends data to previous data written under key """
+
+ global _state
+
+ # convert to numpy if torch.Tensor is given
+ if isinstance(data, torch.Tensor):
+ data = data.detach().numpy()
+
+ if not key in _state:
+ _state[key] = {
+ 'fid' : open(os.path.join(_folder, key + '.bin'), 'wb'),
+ 'fs' : fs,
+ 'dim' : tuple(data.shape),
+ 'dtype' : str(data.dtype)
+ }
+
+ with open(os.path.join(_folder, key + '.yml'), 'w') as f:
+ f.write(yaml.dump({'fs' : fs, 'dim' : tuple(data.shape), 'dtype' : str(data.dtype).split('.')[-1]}))
+ else:
+ if _state[key]['fs'] != fs:
+ raise ValueError(f"fs changed for key {key}: {_state[key]['fs']} vs. {fs}")
+ if _state[key]['dtype'] != str(data.dtype):
+ raise ValueError(f"dtype changed for key {key}: {_state[key]['dtype']} vs. {str(data.dtype)}")
+ if _state[key]['dim'] != tuple(data.shape):
+ raise ValueError(f"dim changed for key {key}: {_state[key]['dim']} vs. {tuple(data.shape)}")
+
+ _state[key]['fid'].write(data.tobytes())
+
+def close(folder='endoscopy'):
+ """ clean up """
+ for key in _state.keys():
+ _state[key]['fid'].close()
+
+
+def read_data(folder='endoscopy'):
+ """ retrieves written data as numpy arrays """
+
+
+ keys = [name[:-4] for name in os.listdir(folder) if name.endswith('.yml')]
+
+ return_dict = dict()
+
+ for key in keys:
+ with open(os.path.join(folder, key + '.yml'), 'r') as f:
+ value = yaml.load(f.read(), yaml.FullLoader)
+
+ with open(os.path.join(folder, key + '.bin'), 'rb') as f:
+ data = np.frombuffer(f.read(), dtype=value['dtype'])
+
+ value['data'] = data.reshape((-1,) + value['dim'])
+
+ return_dict[key] = value
+
+ return return_dict
+
+def get_best_reshape(shape, target_ratio=1):
+ """ calculated the best 2d reshape of shape given the target ratio (rows/cols)"""
+
+ if len(shape) > 1:
+ pixel_count = 1
+ for s in shape:
+ pixel_count *= s
+ else:
+ pixel_count = shape[0]
+
+ if pixel_count == 1:
+ return (1,)
+
+ num_columns = int((pixel_count / target_ratio)**.5)
+
+ while (pixel_count % num_columns):
+ num_columns -= 1
+
+ num_rows = pixel_count // num_columns
+
+ return (num_rows, num_columns)
+
+def get_type_and_shape(shape):
+
+ # can happen if data is one dimensional
+ if len(shape) == 0:
+ shape = (1,)
+
+ # calculate pixel count
+ if len(shape) > 1:
+ pixel_count = 1
+ for s in shape:
+ pixel_count *= s
+ else:
+ pixel_count = shape[0]
+
+ if pixel_count == 1:
+ return 'plot', (1, )
+
+ # stay with shape if already 2-dimensional
+ if len(shape) == 2:
+ if (shape[0] != pixel_count) or (shape[1] != pixel_count):
+ return 'image', shape
+
+ return 'image', get_best_reshape(shape)
+
+def make_animation(data, filename, start_index=80, stop_index=-80, interval=20, half_signal_window_length=80):
+
+ # determine plot setup
+ num_keys = len(data.keys())
+
+ num_rows = int((num_keys * 3/4) ** .5)
+
+ num_cols = (num_keys + num_rows - 1) // num_rows
+
+ fig, axs = plt.subplots(num_rows, num_cols)
+ fig.set_size_inches(num_cols * 5, num_rows * 5)
+
+ display = dict()
+
+ fs_max = max([val['fs'] for val in data.values()])
+
+ num_samples = max([val['data'].shape[0] for val in data.values()])
+
+ keys = sorted(data.keys())
+
+ # inspect data
+ for i, key in enumerate(keys):
+ axs[i // num_cols, i % num_cols].title.set_text(key)
+
+ display[key] = dict()
+
+ display[key]['type'], display[key]['shape'] = get_type_and_shape(data[key]['dim'])
+ display[key]['down_factor'] = data[key]['fs'] / fs_max
+
+ start_index = max(start_index, half_signal_window_length)
+ while stop_index < 0:
+ stop_index += num_samples
+
+ stop_index = min(stop_index, num_samples - half_signal_window_length)
+
+ # actual plotting
+ frames = []
+ for index in range(start_index, stop_index):
+ ims = []
+ for i, key in enumerate(keys):
+ feature_index = int(round(index * display[key]['down_factor']))
+
+ if display[key]['type'] == 'plot':
+ ims.append(axs[i // num_cols, i % num_cols].plot(data[key]['data'][index - half_signal_window_length : index + half_signal_window_length], marker='P', markevery=[half_signal_window_length], animated=True, color='blue')[0])
+
+ elif display[key]['type'] == 'image':
+ ims.append(axs[i // num_cols, i % num_cols].imshow(data[key]['data'][index].reshape(display[key]['shape']), animated=True))
+
+ frames.append(ims)
+
+ ani = animation.ArtistAnimation(fig, frames, interval=interval, blit=True, repeat_delay=1000)
+
+ if not filename.endswith('.mp4'):
+ filename += '.mp4'
+
+ ani.save(filename) \ No newline at end of file
diff --git a/dnn/torch/lpcnet/utils/layers/__init__.py b/dnn/torch/lpcnet/utils/layers/__init__.py
new file mode 100644
index 00000000..4a58f221
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/layers/__init__.py
@@ -0,0 +1,3 @@
+from .dual_fc import DualFC
+from .subconditioner import AdditiveSubconditioner, ModulativeSubconditioner, ConcatenativeSubconditioner
+from .pcm_embeddings import PCMEmbedding, DifferentiablePCMEmbedding \ No newline at end of file
diff --git a/dnn/torch/lpcnet/utils/layers/dual_fc.py b/dnn/torch/lpcnet/utils/layers/dual_fc.py
new file mode 100644
index 00000000..25d9a5f9
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/layers/dual_fc.py
@@ -0,0 +1,44 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+
+class DualFC(nn.Module):
+ def __init__(self, input_dim, output_dim):
+ super(DualFC, self).__init__()
+
+ self.dense1 = nn.Linear(input_dim, output_dim)
+ self.dense2 = nn.Linear(input_dim, output_dim)
+
+ self.alpha = nn.Parameter(torch.tensor([0.5]), requires_grad=True)
+ self.beta = nn.Parameter(torch.tensor([0.5]), requires_grad=True)
+
+ def forward(self, x):
+ return self.alpha * torch.tanh(self.dense1(x)) + self.beta * torch.tanh(self.dense2(x))
diff --git a/dnn/torch/lpcnet/utils/layers/pcm_embeddings.py b/dnn/torch/lpcnet/utils/layers/pcm_embeddings.py
new file mode 100644
index 00000000..603a17ab
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/layers/pcm_embeddings.py
@@ -0,0 +1,71 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+""" module implementing PCM embeddings for LPCNet """
+
+import math as m
+
+import torch
+from torch import nn
+
+
+class PCMEmbedding(nn.Module):
+ def __init__(self, embed_dim=128, num_levels=256):
+ super(PCMEmbedding, self).__init__()
+
+ self.embed_dim = embed_dim
+ self.num_levels = num_levels
+
+ self.embedding = nn.Embedding(self.num_levels, self.num_dim)
+
+ # initialize
+ with torch.no_grad():
+ num_rows, num_cols = self.num_levels, self.embed_dim
+ a = m.sqrt(12) * (torch.rand(num_rows, num_cols) - 0.5)
+ for i in range(num_rows):
+ a[i, :] += m.sqrt(12) * (i - num_rows / 2)
+ self.embedding.weight[:, :] = 0.1 * a
+
+ def forward(self, x):
+ return self.embeddint(x)
+
+
+class DifferentiablePCMEmbedding(PCMEmbedding):
+ def __init__(self, embed_dim, num_levels=256):
+ super(DifferentiablePCMEmbedding, self).__init__(embed_dim, num_levels)
+
+ def forward(self, x):
+ x_int = (x - torch.floor(x)).detach().long()
+ x_frac = x - x_int
+ x_next = torch.minimum(x_int + 1, self.num_levels)
+
+ embed_0 = self.embedding(x_int)
+ embed_1 = self.embedding(x_next)
+
+ return (1 - x_frac) * embed_0 + x_frac * embed_1
diff --git a/dnn/torch/lpcnet/utils/layers/subconditioner.py b/dnn/torch/lpcnet/utils/layers/subconditioner.py
new file mode 100644
index 00000000..691eb449
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/layers/subconditioner.py
@@ -0,0 +1,497 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+from re import sub
+import torch
+from torch import nn
+
+
+
+
+def get_subconditioner( method,
+ number_of_subsamples,
+ pcm_embedding_size,
+ state_size,
+ pcm_levels,
+ number_of_signals,
+ **kwargs):
+
+ subconditioner_dict = {
+ 'additive' : AdditiveSubconditioner,
+ 'concatenative' : ConcatenativeSubconditioner,
+ 'modulative' : ModulativeSubconditioner
+ }
+
+ return subconditioner_dict[method](number_of_subsamples,
+ pcm_embedding_size, state_size, pcm_levels, number_of_signals, **kwargs)
+
+
+class Subconditioner(nn.Module):
+ def __init__(self):
+ """ upsampling by subconditioning
+
+ Upsamples a sequence of states conditioning on pcm signals and
+ optionally a feature vector.
+ """
+ super(Subconditioner, self).__init__()
+
+ def forward(self, states, signals, features=None):
+ raise Exception("Base class should not be called")
+
+ def single_step(self, index, state, signals, features):
+ raise Exception("Base class should not be called")
+
+ def get_output_dim(self, index):
+ raise Exception("Base class should not be called")
+
+
+class AdditiveSubconditioner(Subconditioner):
+ def __init__(self,
+ number_of_subsamples,
+ pcm_embedding_size,
+ state_size,
+ pcm_levels,
+ number_of_signals,
+ **kwargs):
+ """ subconditioning by addition """
+
+ super(AdditiveSubconditioner, self).__init__()
+
+ self.number_of_subsamples = number_of_subsamples
+ self.pcm_embedding_size = pcm_embedding_size
+ self.state_size = state_size
+ self.pcm_levels = pcm_levels
+ self.number_of_signals = number_of_signals
+
+ if self.pcm_embedding_size != self.state_size:
+ raise ValueError('For additive subconditioning state and embedding '
+ + f'sizes must match but but got {self.state_size} and {self.pcm_embedding_size}')
+
+ self.embeddings = [None]
+ for i in range(1, self.number_of_subsamples):
+ embedding = nn.Embedding(self.pcm_levels, self.pcm_embedding_size)
+ self.add_module('pcm_embedding_' + str(i), embedding)
+ self.embeddings.append(embedding)
+
+ def forward(self, states, signals):
+ """ creates list of subconditioned states
+
+ Parameters:
+ -----------
+ states : torch.tensor
+ states of shape (batch, seq_length // s, state_size)
+ signals : torch.tensor
+ signals of shape (batch, seq_length, number_of_signals)
+
+ Returns:
+ --------
+ c_states : list of torch.tensor
+ list of s subconditioned states
+ """
+
+ s = self.number_of_subsamples
+
+ c_states = [states]
+ new_states = states
+ for i in range(1, self.number_of_subsamples):
+ embed = self.embeddings[i](signals[:, i::s])
+ # reduce signal dimension
+ embed = torch.sum(embed, dim=2)
+
+ new_states = new_states + embed
+ c_states.append(new_states)
+
+ return c_states
+
+ def single_step(self, index, state, signals):
+ """ carry out single step for inference
+
+ Parameters:
+ -----------
+ index : int
+ position in subconditioning batch
+
+ state : torch.tensor
+ state to sub-condition
+
+ signals : torch.tensor
+ signals for subconditioning, all but the last dimensions
+ must match those of state
+
+ Returns:
+ c_state : torch.tensor
+ subconditioned state
+ """
+
+ if index == 0:
+ c_state = state
+ else:
+ embed_signals = self.embeddings[index](signals)
+ c = torch.sum(embed_signals, dim=-2)
+ c_state = state + c
+
+ return c_state
+
+ def get_output_dim(self, index):
+ return self.state_size
+
+ def get_average_flops_per_step(self):
+ s = self.number_of_subsamples
+ flops = (s - 1) / s * self.number_of_signals * self.pcm_embedding_size
+ return flops
+
+
+class ConcatenativeSubconditioner(Subconditioner):
+ def __init__(self,
+ number_of_subsamples,
+ pcm_embedding_size,
+ state_size,
+ pcm_levels,
+ number_of_signals,
+ recurrent=True,
+ **kwargs):
+ """ subconditioning by concatenation """
+
+ super(ConcatenativeSubconditioner, self).__init__()
+
+ self.number_of_subsamples = number_of_subsamples
+ self.pcm_embedding_size = pcm_embedding_size
+ self.state_size = state_size
+ self.pcm_levels = pcm_levels
+ self.number_of_signals = number_of_signals
+ self.recurrent = recurrent
+
+ self.embeddings = []
+ start_index = 0
+ if self.recurrent:
+ start_index = 1
+ self.embeddings.append(None)
+
+ for i in range(start_index, self.number_of_subsamples):
+ embedding = nn.Embedding(self.pcm_levels, self.pcm_embedding_size)
+ self.add_module('pcm_embedding_' + str(i), embedding)
+ self.embeddings.append(embedding)
+
+ def forward(self, states, signals):
+ """ creates list of subconditioned states
+
+ Parameters:
+ -----------
+ states : torch.tensor
+ states of shape (batch, seq_length // s, state_size)
+ signals : torch.tensor
+ signals of shape (batch, seq_length, number_of_signals)
+
+ Returns:
+ --------
+ c_states : list of torch.tensor
+ list of s subconditioned states
+ """
+ s = self.number_of_subsamples
+
+ if self.recurrent:
+ c_states = [states]
+ start = 1
+ else:
+ c_states = []
+ start = 0
+
+ new_states = states
+ for i in range(start, self.number_of_subsamples):
+ embed = self.embeddings[i](signals[:, i::s])
+ # reduce signal dimension
+ embed = torch.flatten(embed, -2)
+
+ if self.recurrent:
+ new_states = torch.cat((new_states, embed), dim=-1)
+ else:
+ new_states = torch.cat((states, embed), dim=-1)
+
+ c_states.append(new_states)
+
+ return c_states
+
+ def single_step(self, index, state, signals):
+ """ carry out single step for inference
+
+ Parameters:
+ -----------
+ index : int
+ position in subconditioning batch
+
+ state : torch.tensor
+ state to sub-condition
+
+ signals : torch.tensor
+ signals for subconditioning, all but the last dimensions
+ must match those of state
+
+ Returns:
+ c_state : torch.tensor
+ subconditioned state
+ """
+
+ if index == 0 and self.recurrent:
+ c_state = state
+ else:
+ embed_signals = self.embeddings[index](signals)
+ c = torch.flatten(embed_signals, -2)
+ if not self.recurrent and index > 0:
+ # overwrite previous conditioning vector
+ c_state = torch.cat((state[...,:self.state_size], c), dim=-1)
+ else:
+ c_state = torch.cat((state, c), dim=-1)
+ return c_state
+
+ return c_state
+
+ def get_average_flops_per_step(self):
+ return 0
+
+ def get_output_dim(self, index):
+ if self.recurrent:
+ return self.state_size + index * self.pcm_embedding_size * self.number_of_signals
+ else:
+ return self.state_size + self.pcm_embedding_size * self.number_of_signals
+
+class ModulativeSubconditioner(Subconditioner):
+ def __init__(self,
+ number_of_subsamples,
+ pcm_embedding_size,
+ state_size,
+ pcm_levels,
+ number_of_signals,
+ state_recurrent=False,
+ **kwargs):
+ """ subconditioning by modulation """
+
+ super(ModulativeSubconditioner, self).__init__()
+
+ self.number_of_subsamples = number_of_subsamples
+ self.pcm_embedding_size = pcm_embedding_size
+ self.state_size = state_size
+ self.pcm_levels = pcm_levels
+ self.number_of_signals = number_of_signals
+ self.state_recurrent = state_recurrent
+
+ self.hidden_size = self.pcm_embedding_size * self.number_of_signals
+
+ if self.state_recurrent:
+ self.hidden_size += self.pcm_embedding_size
+ self.state_transform = nn.Linear(self.state_size, self.pcm_embedding_size)
+
+ self.embeddings = [None]
+ self.alphas = [None]
+ self.betas = [None]
+
+ for i in range(1, self.number_of_subsamples):
+ embedding = nn.Embedding(self.pcm_levels, self.pcm_embedding_size)
+ self.add_module('pcm_embedding_' + str(i), embedding)
+ self.embeddings.append(embedding)
+
+ self.alphas.append(nn.Linear(self.hidden_size, self.state_size))
+ self.add_module('alpha_dense_' + str(i), self.alphas[-1])
+
+ self.betas.append(nn.Linear(self.hidden_size, self.state_size))
+ self.add_module('beta_dense_' + str(i), self.betas[-1])
+
+
+
+ def forward(self, states, signals):
+ """ creates list of subconditioned states
+
+ Parameters:
+ -----------
+ states : torch.tensor
+ states of shape (batch, seq_length // s, state_size)
+ signals : torch.tensor
+ signals of shape (batch, seq_length, number_of_signals)
+
+ Returns:
+ --------
+ c_states : list of torch.tensor
+ list of s subconditioned states
+ """
+ s = self.number_of_subsamples
+
+ c_states = [states]
+ new_states = states
+ for i in range(1, self.number_of_subsamples):
+ embed = self.embeddings[i](signals[:, i::s])
+ # reduce signal dimension
+ embed = torch.flatten(embed, -2)
+
+ if self.state_recurrent:
+ comp_states = self.state_transform(new_states)
+ embed = torch.cat((embed, comp_states), dim=-1)
+
+ alpha = torch.tanh(self.alphas[i](embed))
+ beta = torch.tanh(self.betas[i](embed))
+
+ # new state obtained by modulating previous state
+ new_states = torch.tanh((1 + alpha) * new_states + beta)
+
+ c_states.append(new_states)
+
+ return c_states
+
+ def single_step(self, index, state, signals):
+ """ carry out single step for inference
+
+ Parameters:
+ -----------
+ index : int
+ position in subconditioning batch
+
+ state : torch.tensor
+ state to sub-condition
+
+ signals : torch.tensor
+ signals for subconditioning, all but the last dimensions
+ must match those of state
+
+ Returns:
+ c_state : torch.tensor
+ subconditioned state
+ """
+
+ if index == 0:
+ c_state = state
+ else:
+ embed_signals = self.embeddings[index](signals)
+ c = torch.flatten(embed_signals, -2)
+ if self.state_recurrent:
+ r_state = self.state_transform(state)
+ c = torch.cat((c, r_state), dim=-1)
+ alpha = torch.tanh(self.alphas[index](c))
+ beta = torch.tanh(self.betas[index](c))
+ c_state = torch.tanh((1 + alpha) * state + beta)
+ return c_state
+
+ return c_state
+
+ def get_output_dim(self, index):
+ return self.state_size
+
+ def get_average_flops_per_step(self):
+ s = self.number_of_subsamples
+
+ # estimate activation by 10 flops
+ # c_state = torch.tanh((1 + alpha) * state + beta)
+ flops = 13 * self.state_size
+
+ # hidden size
+ hidden_size = self.number_of_signals * self.pcm_embedding_size
+ if self.state_recurrent:
+ hidden_size += self.pcm_embedding_size
+
+ # counting 2 * A * B flops for Linear(A, B)
+ # alpha = torch.tanh(self.alphas[index](c))
+ # beta = torch.tanh(self.betas[index](c))
+ flops += 4 * hidden_size * self.state_size + 20 * self.state_size
+
+ # r_state = self.state_transform(state)
+ if self.state_recurrent:
+ flops += 2 * self.state_size * self.pcm_embedding_size
+
+ # average over steps
+ flops *= (s - 1) / s
+
+ return flops
+
+class ComparitiveSubconditioner(Subconditioner):
+ def __init__(self,
+ number_of_subsamples,
+ pcm_embedding_size,
+ state_size,
+ pcm_levels,
+ number_of_signals,
+ error_index=-1,
+ apply_gate=True,
+ normalize=False):
+ """ subconditioning by comparison """
+
+ super(ComparitiveSubconditioner, self).__init__()
+
+ self.comparison_size = self.pcm_embedding_size
+ self.error_position = error_index
+ self.apply_gate = apply_gate
+ self.normalize = normalize
+
+ self.state_transform = nn.Linear(self.state_size, self.comparison_size)
+
+ self.alpha_dense = nn.Linear(self.number_of_signales * self.pcm_embedding_size, self.state_size)
+ self.beta_dense = nn.Linear(self.number_of_signales * self.pcm_embedding_size, self.state_size)
+
+ if self.apply_gate:
+ self.gate_dense = nn.Linear(self.pcm_embedding_size, self.state_size)
+
+ # embeddings and state transforms
+ self.embeddings = [None]
+ self.alpha_denses = [None]
+ self.beta_denses = [None]
+ self.state_transforms = [nn.Linear(self.state_size, self.comparison_size)]
+ self.add_module('state_transform_0', self.state_transforms[0])
+
+ for i in range(1, self.number_of_subsamples):
+ embedding = nn.Embedding(self.pcm_levels, self.pcm_embedding_size)
+ self.add_module('pcm_embedding_' + str(i), embedding)
+ self.embeddings.append(embedding)
+
+ state_transform = nn.Linear(self.state_size, self.comparison_size)
+ self.add_module('state_transform_' + str(i), state_transform)
+ self.state_transforms.append(state_transform)
+
+ self.alpha_denses.append(nn.Linear(self.number_of_signales * self.pcm_embedding_size, self.state_size))
+ self.add_module('alpha_dense_' + str(i), self.alpha_denses[-1])
+
+ self.beta_denses.append(nn.Linear(self.number_of_signales * self.pcm_embedding_size, self.state_size))
+ self.add_module('beta_dense_' + str(i), self.beta_denses[-1])
+
+ def forward(self, states, signals):
+ s = self.number_of_subsamples
+
+ c_states = [states]
+ new_states = states
+ for i in range(1, self.number_of_subsamples):
+ embed = self.embeddings[i](signals[:, i::s])
+ # reduce signal dimension
+ embed = torch.flatten(embed, -2)
+
+ comp_states = self.state_transforms[i](new_states)
+
+ alpha = torch.tanh(self.alpha_dense(embed))
+ beta = torch.tanh(self.beta_dense(embed))
+
+ # new state obtained by modulating previous state
+ new_states = torch.tanh((1 + alpha) * comp_states + beta)
+
+ c_states.append(new_states)
+
+ return c_states
diff --git a/dnn/torch/lpcnet/utils/misc.py b/dnn/torch/lpcnet/utils/misc.py
new file mode 100644
index 00000000..b295d6c4
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/misc.py
@@ -0,0 +1,65 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+
+
+def find(a, v):
+ try:
+ idx = a.index(v)
+ except:
+ idx = -1
+ return idx
+
+def interleave_tensors(tensors, dim=-2):
+ """ interleave list of tensors along sequence dimension """
+
+ x = torch.cat([x.unsqueeze(dim) for x in tensors], dim=dim)
+ x = torch.flatten(x, dim - 1, dim)
+
+ return x
+
+def _interleave(x, pcm_levels=256):
+
+ repeats = pcm_levels // (2*x.size(-1))
+ x = x.unsqueeze(-1)
+ p = torch.flatten(torch.repeat_interleave(torch.cat((x, 1 - x), dim=-1), repeats, dim=-1), -2)
+
+ return p
+
+def get_pdf_from_tree(x):
+ pcm_levels = x.size(-1)
+
+ p = _interleave(x[..., 1:2])
+ n = 4
+ while n <= pcm_levels:
+ p = p * _interleave(x[..., n//2:n])
+ n *= 2
+
+ return p \ No newline at end of file
diff --git a/dnn/torch/lpcnet/utils/pcm.py b/dnn/torch/lpcnet/utils/pcm.py
new file mode 100644
index 00000000..7e985b84
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/pcm.py
@@ -0,0 +1,35 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+
+def clip_to_int16(x):
+ int_min = -2**15
+ int_max = 2**15 - 1
+ x_clipped = max(int_min, min(x, int_max))
+ return x_clipped
diff --git a/dnn/torch/lpcnet/utils/sample.py b/dnn/torch/lpcnet/utils/sample.py
new file mode 100644
index 00000000..63f9e529
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/sample.py
@@ -0,0 +1,44 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+
+
+def sample_excitation(probs, pitch_corr):
+
+ norm = lambda x : x / (x.sum() + 1e-18)
+
+ # lowering the temperature
+ probs = norm(probs ** (1 + max(0, 1.5 * pitch_corr - 0.5)))
+ # cut-off tails
+ probs = norm(torch.maximum(probs - 0.002 , torch.FloatTensor([0])))
+ # sample
+ exc = torch.multinomial(probs.squeeze(), 1)
+
+ return exc
diff --git a/dnn/torch/lpcnet/utils/sparsification/__init__.py b/dnn/torch/lpcnet/utils/sparsification/__init__.py
new file mode 100644
index 00000000..ebfa9d9a
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/sparsification/__init__.py
@@ -0,0 +1,2 @@
+from .gru_sparsifier import GRUSparsifier
+from .common import sparsify_matrix, calculate_gru_flops_per_step \ No newline at end of file
diff --git a/dnn/torch/lpcnet/utils/sparsification/common.py b/dnn/torch/lpcnet/utils/sparsification/common.py
new file mode 100644
index 00000000..2600cd01
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/sparsification/common.py
@@ -0,0 +1,121 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+
+def sparsify_matrix(matrix : torch.tensor, density : float, block_size, keep_diagonal : bool=False, return_mask : bool=False):
+ """ sparsifies matrix with specified block size
+
+ Parameters:
+ -----------
+ matrix : torch.tensor
+ matrix to sparsify
+ density : int
+ target density
+ block_size : [int, int]
+ block size dimensions
+ keep_diagonal : bool
+ If true, the diagonal will be kept. This option requires block_size[0] == block_size[1] and defaults to False
+ """
+
+ m, n = matrix.shape
+ m1, n1 = block_size
+
+ if m % m1 or n % n1:
+ raise ValueError(f"block size {(m1, n1)} does not divide matrix size {(m, n)}")
+
+ # extract diagonal if keep_diagonal = True
+ if keep_diagonal:
+ if m != n:
+ raise ValueError("Attempting to sparsify non-square matrix with keep_diagonal=True")
+
+ to_spare = torch.diag(torch.diag(matrix))
+ matrix = matrix - to_spare
+ else:
+ to_spare = torch.zeros_like(matrix)
+
+ # calculate energy in sub-blocks
+ x = torch.reshape(matrix, (m // m1, m1, n // n1, n1))
+ x = x ** 2
+ block_energies = torch.sum(torch.sum(x, dim=3), dim=1)
+
+ number_of_blocks = (m * n) // (m1 * n1)
+ number_of_survivors = round(number_of_blocks * density)
+
+ # masking threshold
+ if number_of_survivors == 0:
+ threshold = 0
+ else:
+ threshold = torch.sort(torch.flatten(block_energies)).values[-number_of_survivors]
+
+ # create mask
+ mask = torch.ones_like(block_energies)
+ mask[block_energies < threshold] = 0
+ mask = torch.repeat_interleave(mask, m1, dim=0)
+ mask = torch.repeat_interleave(mask, n1, dim=1)
+
+ # perform masking
+ masked_matrix = mask * matrix + to_spare
+
+ if return_mask:
+ return masked_matrix, mask
+ else:
+ return masked_matrix
+
+def calculate_gru_flops_per_step(gru, sparsification_dict=dict(), drop_input=False):
+ input_size = gru.input_size
+ hidden_size = gru.hidden_size
+ flops = 0
+
+ input_density = (
+ sparsification_dict.get('W_ir', [1])[0]
+ + sparsification_dict.get('W_in', [1])[0]
+ + sparsification_dict.get('W_iz', [1])[0]
+ ) / 3
+
+ recurrent_density = (
+ sparsification_dict.get('W_hr', [1])[0]
+ + sparsification_dict.get('W_hn', [1])[0]
+ + sparsification_dict.get('W_hz', [1])[0]
+ ) / 3
+
+ # input matrix vector multiplications
+ if not drop_input:
+ flops += 2 * 3 * input_size * hidden_size * input_density
+
+ # recurrent matrix vector multiplications
+ flops += 2 * 3 * hidden_size * hidden_size * recurrent_density
+
+ # biases
+ flops += 6 * hidden_size
+
+ # activations estimated by 10 flops per activation
+ flops += 30 * hidden_size
+
+ return flops
diff --git a/dnn/torch/lpcnet/utils/sparsification/gru_sparsifier.py b/dnn/torch/lpcnet/utils/sparsification/gru_sparsifier.py
new file mode 100644
index 00000000..4dfdaf0a
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/sparsification/gru_sparsifier.py
@@ -0,0 +1,187 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+
+from .common import sparsify_matrix
+
+
+class GRUSparsifier:
+ def __init__(self, task_list, start, stop, interval, exponent=3):
+ """ Sparsifier for torch.nn.GRUs
+
+ Parameters:
+ -----------
+ task_list : list
+ task_list contains a list of tuples (gru, sparsify_dict), where gru is an instance
+ of torch.nn.GRU and sparsify_dic is a dictionary with keys in {'W_ir', 'W_iz', 'W_in',
+ 'W_hr', 'W_hz', 'W_hn'} corresponding to the input and recurrent weights for the reset,
+ update, and new gate. The values of sparsify_dict are tuples (density, [m, n], keep_diagonal),
+ where density is the target density in [0, 1], [m, n] is the shape sub-blocks to which
+ sparsification is applied and keep_diagonal is a bool variable indicating whether the diagonal
+ should be kept.
+
+ start : int
+ training step after which sparsification will be started.
+
+ stop : int
+ training step after which sparsification will be completed.
+
+ interval : int
+ sparsification interval for steps between start and stop. After stop sparsification will be
+ carried out after every call to GRUSparsifier.step()
+
+ exponent : float
+ Interpolation exponent for sparsification interval. In step i sparsification will be carried out
+ with density (alpha + target_density * (1 * alpha)), where
+ alpha = ((stop - i) / (start - stop)) ** exponent
+
+ Example:
+ --------
+ >>> import torch
+ >>> gru = torch.nn.GRU(10, 20)
+ >>> sparsify_dict = {
+ ... 'W_ir' : (0.5, [2, 2], False),
+ ... 'W_iz' : (0.6, [2, 2], False),
+ ... 'W_in' : (0.7, [2, 2], False),
+ ... 'W_hr' : (0.1, [4, 4], True),
+ ... 'W_hz' : (0.2, [4, 4], True),
+ ... 'W_hn' : (0.3, [4, 4], True),
+ ... }
+ >>> sparsifier = GRUSparsifier([(gru, sparsify_dict)], 0, 100, 50)
+ >>> for i in range(100):
+ ... sparsifier.step()
+ """
+ # just copying parameters...
+ self.start = start
+ self.stop = stop
+ self.interval = interval
+ self.exponent = exponent
+ self.task_list = task_list
+
+ # ... and setting counter to 0
+ self.step_counter = 0
+
+ self.last_masks = {key : None for key in ['W_ir', 'W_in', 'W_iz', 'W_hr', 'W_hn', 'W_hz']}
+
+ def step(self, verbose=False):
+ """ carries out sparsification step
+
+ Call this function after optimizer.step in your
+ training loop.
+
+ Parameters:
+ ----------
+ verbose : bool
+ if true, densities are printed out
+
+ Returns:
+ --------
+ None
+
+ """
+ # compute current interpolation factor
+ self.step_counter += 1
+
+ if self.step_counter < self.start:
+ return
+ elif self.step_counter < self.stop:
+ # update only every self.interval-th interval
+ if self.step_counter % self.interval:
+ return
+
+ alpha = ((self.stop - self.step_counter) / (self.stop - self.start)) ** self.exponent
+ else:
+ alpha = 0
+
+
+ with torch.no_grad():
+ for gru, params in self.task_list:
+ hidden_size = gru.hidden_size
+
+ # input weights
+ for i, key in enumerate(['W_ir', 'W_iz', 'W_in']):
+ if key in params:
+ density = alpha + (1 - alpha) * params[key][0]
+ if verbose:
+ print(f"[{self.step_counter}]: {key} density: {density}")
+
+ gru.weight_ih_l0[i * hidden_size : (i+1) * hidden_size, : ], new_mask = sparsify_matrix(
+ gru.weight_ih_l0[i * hidden_size : (i + 1) * hidden_size, : ],
+ density, # density
+ params[key][1], # block_size
+ params[key][2], # keep_diagonal (might want to set this to False)
+ return_mask=True
+ )
+
+ if type(self.last_masks[key]) != type(None):
+ if not torch.all(self.last_masks[key] == new_mask) and self.step_counter > self.stop:
+ print(f"sparsification mask {key} changed for gru {gru}")
+
+ self.last_masks[key] = new_mask
+
+ # recurrent weights
+ for i, key in enumerate(['W_hr', 'W_hz', 'W_hn']):
+ if key in params:
+ density = alpha + (1 - alpha) * params[key][0]
+ if verbose:
+ print(f"[{self.step_counter}]: {key} density: {density}")
+ gru.weight_hh_l0[i * hidden_size : (i+1) * hidden_size, : ], new_mask = sparsify_matrix(
+ gru.weight_hh_l0[i * hidden_size : (i + 1) * hidden_size, : ],
+ density,
+ params[key][1], # block_size
+ params[key][2], # keep_diagonal (might want to set this to False)
+ return_mask=True
+ )
+
+ if type(self.last_masks[key]) != type(None):
+ if not torch.all(self.last_masks[key] == new_mask) and self.step_counter > self.stop:
+ print(f"sparsification mask {key} changed for gru {gru}")
+
+ self.last_masks[key] = new_mask
+
+
+
+if __name__ == "__main__":
+ print("Testing sparsifier")
+
+ gru = torch.nn.GRU(10, 20)
+ sparsify_dict = {
+ 'W_ir' : (0.5, [2, 2], False),
+ 'W_iz' : (0.6, [2, 2], False),
+ 'W_in' : (0.7, [2, 2], False),
+ 'W_hr' : (0.1, [4, 4], True),
+ 'W_hz' : (0.2, [4, 4], True),
+ 'W_hn' : (0.3, [4, 4], True),
+ }
+
+ sparsifier = GRUSparsifier([(gru, sparsify_dict)], 0, 100, 10)
+
+ for i in range(100):
+ sparsifier.step(verbose=True)
diff --git a/dnn/torch/lpcnet/utils/templates.py b/dnn/torch/lpcnet/utils/templates.py
new file mode 100644
index 00000000..89068562
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/templates.py
@@ -0,0 +1,157 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+from models import multi_rate_lpcnet
+import copy
+
+setup_dict = dict()
+
+dataset_template_v2 = {
+ 'version' : 2,
+ 'feature_file' : 'features.f32',
+ 'signal_file' : 'data.s16',
+ 'frame_length' : 160,
+ 'feature_frame_length' : 36,
+ 'signal_frame_length' : 2,
+ 'feature_dtype' : 'float32',
+ 'signal_dtype' : 'int16',
+ 'feature_frame_layout' : {'cepstrum': [0,18], 'periods': [18, 19], 'pitch_corr': [19, 20], 'lpc': [20, 36]},
+ 'signal_frame_layout' : {'last_signal' : 0, 'signal': 1} # signal, last_signal, error, prediction
+}
+
+dataset_template_v1 = {
+ 'version' : 1,
+ 'feature_file' : 'features.f32',
+ 'signal_file' : 'data.u8',
+ 'frame_length' : 160,
+ 'feature_frame_length' : 55,
+ 'signal_frame_length' : 4,
+ 'feature_dtype' : 'float32',
+ 'signal_dtype' : 'uint8',
+ 'feature_frame_layout' : {'cepstrum': [0,18], 'periods': [36, 37], 'pitch_corr': [37, 38], 'lpc': [39, 55]},
+ 'signal_frame_layout' : {'last_signal' : 0, 'prediction' : 1, 'last_error': 2, 'error': 3} # signal, last_signal, error, prediction
+}
+
+# lpcnet
+
+lpcnet_config = {
+ 'frame_size' : 160,
+ 'gru_a_units' : 384,
+ 'gru_b_units' : 64,
+ 'feature_conditioning_dim' : 128,
+ 'feature_conv_kernel_size' : 3,
+ 'period_levels' : 257,
+ 'period_embedding_dim' : 64,
+ 'signal_embedding_dim' : 128,
+ 'signal_levels' : 256,
+ 'feature_dimension' : 19,
+ 'output_levels' : 256,
+ 'lpc_gamma' : 0.9,
+ 'features' : ['cepstrum', 'periods', 'pitch_corr'],
+ 'signals' : ['last_signal', 'prediction', 'last_error'],
+ 'input_layout' : { 'signals' : {'last_signal' : 0, 'prediction' : 1, 'last_error' : 2},
+ 'features' : {'cepstrum' : [0, 18], 'pitch_corr' : [18, 19]} },
+ 'target' : 'error',
+ 'feature_history' : 2,
+ 'feature_lookahead' : 2,
+ 'sparsification' : {
+ 'gru_a' : {
+ 'start' : 10000,
+ 'stop' : 30000,
+ 'interval' : 100,
+ 'exponent' : 3,
+ 'params' : {
+ 'W_hr' : (0.05, [4, 8], True),
+ 'W_hz' : (0.05, [4, 8], True),
+ 'W_hn' : (0.2, [4, 8], True)
+ },
+ },
+ 'gru_b' : {
+ 'start' : 10000,
+ 'stop' : 30000,
+ 'interval' : 100,
+ 'exponent' : 3,
+ 'params' : {
+ 'W_ir' : (0.5, [4, 8], False),
+ 'W_iz' : (0.5, [4, 8], False),
+ 'W_in' : (0.5, [4, 8], False)
+ },
+ }
+ },
+ 'add_reference_phase' : False,
+ 'reference_phase_dim' : 0
+}
+
+
+
+# multi rate
+subconditioning = {
+ 'subconditioning_a' : {
+ 'number_of_subsamples' : 2,
+ 'method' : 'modulative',
+ 'signals' : ['last_signal', 'prediction', 'last_error'],
+ 'pcm_embedding_size' : 64,
+ 'kwargs' : dict()
+
+ },
+ 'subconditioning_b' : {
+ 'number_of_subsamples' : 2,
+ 'method' : 'modulative',
+ 'signals' : ['last_signal', 'prediction', 'last_error'],
+ 'pcm_embedding_size' : 64,
+ 'kwargs' : dict()
+ }
+}
+
+multi_rate_lpcnet_config = lpcnet_config.copy()
+multi_rate_lpcnet_config['subconditioning'] = subconditioning
+
+training_default = {
+ 'batch_size' : 256,
+ 'epochs' : 20,
+ 'lr' : 1e-3,
+ 'lr_decay_factor' : 2.5e-5,
+ 'adam_betas' : [0.9, 0.99],
+ 'frames_per_sample' : 15
+}
+
+lpcnet_setup = {
+ 'dataset' : '/local/datasets/lpcnet_training',
+ 'lpcnet' : {'config' : lpcnet_config, 'model': 'lpcnet'},
+ 'training' : training_default
+}
+
+multi_rate_lpcnet_setup = copy.deepcopy(lpcnet_setup)
+multi_rate_lpcnet_setup['lpcnet']['config'] = multi_rate_lpcnet_config
+multi_rate_lpcnet_setup['lpcnet']['model'] = 'multi_rate'
+
+setup_dict = {
+ 'lpcnet' : lpcnet_setup,
+ 'multi_rate' : multi_rate_lpcnet_setup
+}
diff --git a/dnn/torch/lpcnet/utils/ulaw.py b/dnn/torch/lpcnet/utils/ulaw.py
new file mode 100644
index 00000000..05c395e9
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/ulaw.py
@@ -0,0 +1,58 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import math as m
+
+import torch
+
+
+
+def ulaw2lin(u):
+ scale_1 = 32768.0 / 255.0
+ u = u - 128
+ s = torch.sign(u)
+ u = torch.abs(u)
+ return s * scale_1 * (torch.exp(u / 128. * m.log(256)) - 1)
+
+
+def lin2ulawq(x):
+ scale = 255.0 / 32768.0
+ s = torch.sign(x)
+ x = torch.abs(x)
+ u = s * (128 * torch.log(1 + scale * x) / m.log(256))
+ u = torch.clip(128 + torch.round(u), 0, 255)
+ return u
+
+def lin2ulaw(x):
+ scale = 255.0 / 32768.0
+ s = torch.sign(x)
+ x = torch.abs(x)
+ u = s * (128 * torch.log(1 + scale * x) / torch.log(256))
+ u = torch.clip(128 + u, 0, 255)
+ return u \ No newline at end of file
diff --git a/dnn/torch/lpcnet/utils/wav.py b/dnn/torch/lpcnet/utils/wav.py
new file mode 100644
index 00000000..d955c328
--- /dev/null
+++ b/dnn/torch/lpcnet/utils/wav.py
@@ -0,0 +1,43 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import wave
+
+def wavwrite16(filename, x, fs):
+ """ writes x as int16 to file with name filename
+
+ If x.dtype is int16 x is written as is. Otherwise,
+ it is scaled by 2**15 - 1 and converted to int16.
+ """
+ if x.dtype != 'int16':
+ x = ((2**15 - 1) * x).astype('int16')
+
+ with wave.open(filename, 'wb') as f:
+ f.setparams((1, 2, fs, len(x), 'NONE', ""))
+ f.writeframes(x.tobytes()) \ No newline at end of file
diff --git a/dnn/torch/neural-pitch/README.md b/dnn/torch/neural-pitch/README.md
new file mode 100644
index 00000000..6323ead5
--- /dev/null
+++ b/dnn/torch/neural-pitch/README.md
@@ -0,0 +1,18 @@
+## Neural Pitch Estimation
+
+- Dataset Installation
+ 1. Download and unzip PTDB Dataset:
+ wget https://www2.spsc.tugraz.at/databases/PTDB-TUG/SPEECH_DATA_ZIPPED.zip
+ unzip SPEECH_DATA_ZIPPED.zip
+
+ 2. Inside "SPEECH DATA" above, run ptdb_process.sh to combine male/female
+
+ 3. To Download and combine demand, simply run download_demand.sh
+
+- LPCNet preparation
+ 1. To extract xcorr, add lpcnet_extractor.c and add relevant functions to lpcnet_enc.c, add source for headers/c files and Makefile.am, and compile to generate ./lpcnet_xcorr_extractor object
+
+- Dataset Augmentation and training (check out arguments to each of the following)
+ 1. Run data_augmentation.py
+ 2. Run training.py using augmented data
+ 3. Run experiments.py
diff --git a/dnn/torch/neural-pitch/data_augmentation.py b/dnn/torch/neural-pitch/data_augmentation.py
new file mode 100644
index 00000000..ee7a3cab
--- /dev/null
+++ b/dnn/torch/neural-pitch/data_augmentation.py
@@ -0,0 +1,149 @@
+"""
+Perform Data Augmentation (Gain, Additive Noise, Random Filtering) on Input TTS Data
+1. Read in chunks and compute clean pitch first
+2. Then add in augmentation (Noise/Level/Response)
+ - Adds filtered noise from the "Demand" dataset, https://zenodo.org/record/1227121#.XRKKxYhKiUk
+ - When using the Demand Dataset, consider each channel as a possible noise input, and keep the first 4 minutes of noise for training
+3. Use this "augmented" audio for feature computation, and compute pitch using CREPE on the clean input
+
+Notes: To ensure consistency with the discovered CREPE offset, we do the following
+- We pad the input audio to the zero-centered CREPE estimator with 80 zeros
+- We pad the input audio to our feature computation with 160 zeros to center them
+"""
+
+import argparse
+parser = argparse.ArgumentParser()
+
+parser.add_argument('data', type=str, help='input raw audio data')
+parser.add_argument('output', type=str, help='output directory')
+parser.add_argument('path_lpcnet_extractor', type=str, help='path to LPCNet extractor object file (generated on compilation)')
+parser.add_argument('noise_dataset', type=str, help='Location of the Demand Datset')
+parser.add_argument('--flag_xcorr', type=bool, help='Flag to additionally dump xcorr features',choices=[True,False],default = False,required = False)
+parser.add_argument('--fraction_input_use', type=float, help='Fraction of input data to consider',default = 0.3,required = False)
+parser.add_argument('--gpu_index', type=int, help='GPU index to use if multiple GPUs',default = 0,required = False)
+parser.add_argument('--choice_augment', type=str, help='Choice of noise augmentation, either use additive synthetic noise or add noise from the demand dataset',choices = ['demand','synthetic'],default = "demand",required = False)
+parser.add_argument('--fraction_clean', type=float, help='Fraction of data to keep clean (that is not augment with anything)',default = 0.2,required = False)
+parser.add_argument('--chunk_size', type=int, help='Number of samples to augment with for each iteration',default = 80000,required = False)
+parser.add_argument('--N', type=int, help='STFT window size',default = 320,required = False)
+parser.add_argument('--H', type=int, help='STFT Hop size',default = 160,required = False)
+parser.add_argument('--freq_keep', type=int, help='Number of Frequencies to keep',default = 30,required = False)
+
+args = parser.parse_args()
+
+import os
+os.environ["CUDA_VISIBLE_DEVICES"] = str(args.gpu_index)
+
+from utils import stft, random_filter
+
+import numpy as np
+import tqdm
+import crepe
+import random
+import glob
+import subprocess
+
+data_full = np.memmap(args.data, dtype=np.int16,mode = 'r')
+data = data_full[:(int)(args.fraction_input_use*data_full.shape[0])]
+
+# list_features = []
+list_cents = []
+list_confidences = []
+
+N = args.N
+H = args.H
+freq_keep = args.freq_keep
+# Minimum/Maximum periods, decided by LPCNet
+min_period = 32
+max_period = 256
+f_ref = 16000/max_period
+chunk_size = args.chunk_size
+num_frames_chunk = chunk_size//H
+list_indices_keep = np.concatenate([np.arange(freq_keep), (N//2 + 1) + np.arange(freq_keep), 2*(N//2 + 1) + np.arange(freq_keep)])
+
+output_IF = np.memmap(args.output + '_iffeat.f32', dtype=np.float32, shape=(((data.shape[0]//chunk_size - 1)//1)*num_frames_chunk,list_indices_keep.shape[0]), mode='w+')
+if args.flag_xcorr:
+ output_xcorr = np.memmap(args.output + '_xcorr.f32', dtype=np.float32, shape=(((data.shape[0]//chunk_size - 1)//1)*num_frames_chunk,257), mode='w+')
+
+fraction_clean = args.fraction_clean
+
+noise_dataset = args.noise_dataset
+
+for i in tqdm.trange((data.shape[0]//chunk_size - 1)//1):
+ chunk = data[i*chunk_size:(i + 1)*chunk_size]/(2**15 - 1)
+
+ # Clean Pitch/Confidence Estimate
+ # Padding input to CREPE by 80 samples to ensure it aligns
+ _, pitch, confidence, _ = crepe.predict(np.concatenate([np.zeros(80),chunk]), 16000, center=True, viterbi=True,verbose=0)
+ cent = 1200*np.log2(np.divide(pitch, f_ref, out=np.zeros_like(pitch), where=pitch!=0) + 1.0e-8)
+
+ # Filter out of range pitches/confidences
+ confidence[pitch < 16000/max_period] = 0
+ confidence[pitch > 16000/min_period] = 0
+
+ # Keep fraction of data clean, augment only 1 minus the fraction
+ if (np.random.rand() > fraction_clean):
+ # Response, generate controlled/random 2nd order IIR filter and filter chunk
+ chunk = random_filter(chunk)
+
+ # Level/Gain response {scale by random gain between 1.0e-3 and 10}
+ # Generate random gain in dB and then convert to scale
+ g_dB = np.random.uniform(low = -60, high = 20, size = 1)
+ # g_dB = 0
+ g = 10**(g_dB/20)
+
+ # Noise Addition {Add random SNR 2nd order randomly colored noise}
+ # Generate noise SNR value and add corresponding noise
+ snr_dB = np.random.uniform(low = -20, high = 30, size = 1)
+
+ if args.choice_augment == 'synthetic':
+ n = np.random.randn(chunk_size)
+ else:
+ list_noisefiles = noise_dataset + '*.wav'
+ noise_file = random.choice(glob.glob(list_noisefiles))
+ n = np.memmap(noise_file, dtype=np.int16,mode = 'r')/(2**15 - 1)
+ rand_range = np.random.randint(low = 0, high = (n.shape[0] - 16000*60 - chunk.shape[0])) # 16000 is subtracted because we will use the last 1 minutes of noise for testing
+ n = n[rand_range:rand_range + chunk.shape[0]]
+
+ # Randomly filter the sampled noise as well
+ n = random_filter(n)
+ # generate random prime number between 0,500 and make those samples of noise 0 (to prevent GRU from picking up temporal patterns)
+ Nprime = random.choice([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541])
+ n[chunk_size - Nprime:] = np.zeros(Nprime)
+ snr_multiplier = np.sqrt((np.sum(np.abs(chunk)**2)/np.sum(np.abs(n)**2))*10**(-snr_dB/10))
+
+ chunk = g*(chunk + snr_multiplier*n)
+
+ # Zero pad input audio by 160 to center the frames
+ spec = stft(x = np.concatenate([np.zeros(160),chunk]), w = 'boxcar', N = N, H = H).T
+ phase_diff = spec*np.conj(np.roll(spec,1,axis = -1))
+ phase_diff = phase_diff/(np.abs(phase_diff) + 1.0e-8)
+ feature = np.concatenate([np.log(np.abs(spec) + 1.0e-8),np.real(phase_diff),np.imag(phase_diff)],axis = 0).T
+ feature = feature[:,list_indices_keep]
+
+ if args.flag_xcorr:
+ # Dump noisy audio into temp file
+ data_temp = np.memmap('./temp_augment.raw', dtype=np.int16, shape=(chunk.shape[0]), mode='w+')
+ # data_temp[:chunk.shape[0]] = (chunk/(np.max(np.abs(chunk)))*(2**15 - 1)).astype(np.int16)
+ data_temp[:chunk.shape[0]] = ((chunk)*(2**15 - 1)).astype(np.int16)
+
+ subprocess.run([args.path_lpcnet_extractor, './temp_augment.raw', './temp_augment_xcorr.f32'])
+ feature_xcorr = np.flip(np.fromfile('./temp_augment_xcorr.f32', dtype='float32').reshape((-1,256),order = 'C'),axis = 1)
+ ones_zero_lag = np.expand_dims(np.ones(feature_xcorr.shape[0]),-1)
+ feature_xcorr = np.concatenate([ones_zero_lag,feature_xcorr],axis = -1)
+
+ os.remove('./temp_augment.raw')
+ os.remove('./temp_augment_xcorr.f32')
+ num_frames = min(cent.shape[0],feature.shape[0],feature_xcorr.shape[0],num_frames_chunk)
+ feature = feature[:num_frames,:]
+ cent = cent[:num_frames]
+ confidence = confidence[:num_frames]
+ feature_xcorr = feature_xcorr[:num_frames]
+ output_IF[i*num_frames_chunk:(i + 1)*num_frames_chunk,:] = feature
+ output_xcorr[i*num_frames_chunk:(i + 1)*num_frames_chunk,:] = feature_xcorr
+ list_cents.append(cent)
+ list_confidences.append(confidence)
+
+list_cents = np.hstack(list_cents)
+list_confidences = np.hstack(list_confidences)
+
+np.save(args.output + '_pitches',np.vstack([list_cents,list_confidences]))
diff --git a/dnn/torch/neural-pitch/download_demand.sh b/dnn/torch/neural-pitch/download_demand.sh
new file mode 100644
index 00000000..0cff06af
--- /dev/null
+++ b/dnn/torch/neural-pitch/download_demand.sh
@@ -0,0 +1,43 @@
+wget https://zenodo.org/record/1227121/files/DKITCHEN_16k.zip
+
+wget https://zenodo.org/record/1227121/files/DLIVING_16k.zip
+
+wget https://zenodo.org/record/1227121/files/DWASHING_16k.zip
+
+wget https://zenodo.org/record/1227121/files/NFIELD_16k.zip
+
+wget https://zenodo.org/record/1227121/files/NPARK_16k.zip
+
+wget https://zenodo.org/record/1227121/files/NRIVER_16k.zip
+
+wget https://zenodo.org/record/1227121/files/OHALLWAY_16k.zip
+
+wget https://zenodo.org/record/1227121/files/OMEETING_16k.zip
+
+wget https://zenodo.org/record/1227121/files/OOFFICE_16k.zip
+
+wget https://zenodo.org/record/1227121/files/PCAFETER_16k.zip
+
+wget https://zenodo.org/record/1227121/files/PRESTO_16k.zip
+
+wget https://zenodo.org/record/1227121/files/PSTATION_16k.zip
+
+wget https://zenodo.org/record/1227121/files/TMETRO_16k.zip
+
+wget https://zenodo.org/record/1227121/files/TCAR_16k.zip
+
+wget https://zenodo.org/record/1227121/files/TBUS_16k.zip
+
+wget https://zenodo.org/record/1227121/files/STRAFFIC_16k.zip
+
+wget https://zenodo.org/record/1227121/files/SPSQUARE_16k.zip
+
+unzip '*.zip'
+
+mkdir -p ./combined_demand_channels/
+for file in */*.wav; do
+parentdir="$(dirname "$file")"
+echo $parentdir
+fname="$(basename "$file")"
+cp $file ./combined_demand_channels/$parentdir+$fname
+done
diff --git a/dnn/torch/neural-pitch/evaluation.py b/dnn/torch/neural-pitch/evaluation.py
new file mode 100644
index 00000000..38ba5765
--- /dev/null
+++ b/dnn/torch/neural-pitch/evaluation.py
@@ -0,0 +1,349 @@
+"""
+Evaluation script to compute the Raw Pitch Accuracy
+Procedure:
+ - Look at all voiced frames in file
+ - Compute number of pitches in those frames that lie within a 50 cent threshold
+ RPA = (Total number of pitches within threshold summed across all files)/(Total number of voiced frames summed accross all files)
+"""
+
+import os
+os.environ["CUDA_VISIBLE_DEVICES"] = "0"
+
+from prettytable import PrettyTable
+import numpy as np
+import glob
+import random
+import tqdm
+import torch
+import librosa
+import json
+from utils import stft, random_filter, feature_xform
+import subprocess
+import crepe
+
+from models import PitchDNN, PitchDNNIF, PitchDNNXcorr
+
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+
+def rca(reference,input,voicing,thresh = 25):
+ idx_voiced = np.where(voicing != 0)[0]
+ acc = np.where(np.abs(reference - input)[idx_voiced] < thresh)[0]
+ return acc.shape[0]
+
+def sweep_rca(reference,input,voicing,thresh = 25,ind_arr = np.arange(-10,10)):
+ l = []
+ for i in ind_arr:
+ l.append(rca(reference,np.roll(input,i),voicing,thresh))
+ l = np.array(l)
+
+ return np.max(l)
+
+def rpa(model,device = 'cpu',data_format = 'if'):
+ list_files = glob.glob('/home/ubuntu/Code/Datasets/SPEECH DATA/combined_mic_16k_raw/*.raw')
+ dir_f0 = '/home/ubuntu/Code/Datasets/SPEECH DATA/combine_f0_ptdb/'
+ # random_shuffle = list(np.random.permutation(len(list_files)))
+ random.shuffle(list_files)
+ list_files = list_files[:1000]
+
+ C_all = 0
+ C_all_m = 0
+ C_all_f = 0
+ list_rca_model_all = []
+ list_rca_male_all = []
+ list_rca_female_all = []
+
+ thresh = 50
+ N = 320
+ H = 160
+ freq_keep = 30
+
+ for idx in tqdm.trange(len(list_files)):
+ audio_file = list_files[idx]
+ file_name = os.path.basename(list_files[idx])[:-4]
+
+ audio = np.memmap(list_files[idx], dtype=np.int16)/(2**15 - 1)
+ offset = 432
+ audio = audio[offset:]
+ rmse = np.squeeze(librosa.feature.rms(y = audio,frame_length = 320,hop_length = 160))
+
+ spec = stft(x = np.concatenate([np.zeros(160),audio]), w = 'boxcar', N = N, H = H).T
+ phase_diff = spec*np.conj(np.roll(spec,1,axis = -1))
+ phase_diff = phase_diff/(np.abs(phase_diff) + 1.0e-8)
+ idx_save = np.concatenate([np.arange(freq_keep),(N//2 + 1) + np.arange(freq_keep),2*(N//2 + 1) + np.arange(freq_keep)])
+ feature = np.concatenate([np.log(np.abs(spec) + 1.0e-8),np.real(phase_diff),np.imag(phase_diff)],axis = 0).T
+ feature_if = feature[:,idx_save]
+
+ data_temp = np.memmap('./temp.raw', dtype=np.int16, shape=(audio.shape[0]), mode='w+')
+ data_temp[:audio.shape[0]] = (audio/(np.max(np.abs(audio)))*(2**15 - 1)).astype(np.int16)
+
+ subprocess.run(["../../../lpcnet_xcorr_extractor", './temp.raw', './temp_xcorr.f32'])
+ feature_xcorr = np.flip(np.fromfile('./temp_xcorr.f32', dtype='float32').reshape((-1,256),order = 'C'),axis = 1)
+ ones_zero_lag = np.expand_dims(np.ones(feature_xcorr.shape[0]),-1)
+ feature_xcorr = np.concatenate([ones_zero_lag,feature_xcorr],axis = -1)
+ # feature_xcorr = feature_xform(feature_xcorr)
+
+ os.remove('./temp.raw')
+ os.remove('./temp_xcorr.f32')
+
+ if data_format == 'if':
+ feature = feature_if
+ elif data_format == 'xcorr':
+ feature = feature_xcorr
+ else:
+ indmin = min(feature_if.shape[0],feature_xcorr.shape[0])
+ feature = np.concatenate([feature_xcorr[:indmin,:],feature_if[:indmin,:]],-1)
+
+
+ pitch_file_name = dir_f0 + "ref" + os.path.basename(list_files[idx])[3:-4] + ".f0"
+ pitch = np.loadtxt(pitch_file_name)[:,0]
+ voicing = np.loadtxt(pitch_file_name)[:,1]
+ indmin = min(voicing.shape[0],rmse.shape[0],pitch.shape[0])
+ pitch = pitch[:indmin]
+ voicing = voicing[:indmin]
+ rmse = rmse[:indmin]
+ voicing = voicing*(rmse > 0.05*np.max(rmse))
+ if "mic_F" in audio_file:
+ idx_correct = np.where(pitch < 125)
+ voicing[idx_correct] = 0
+
+ cent = np.rint(1200*np.log2(np.divide(pitch, (16000/256), out=np.zeros_like(pitch), where=pitch!=0) + 1.0e-8)).astype('int')
+
+
+ model_cents = model(torch.from_numpy(np.copy(np.expand_dims(feature,0))).float().to(device))
+ model_cents = 20*model_cents.argmax(dim=1).cpu().detach().squeeze().numpy()
+
+ num_frames = min(cent.shape[0],model_cents.shape[0])
+ pitch = pitch[:num_frames]
+ cent = cent[:num_frames]
+ voicing = voicing[:num_frames]
+ model_cents = model_cents[:num_frames]
+
+ voicing_all = np.copy(voicing)
+ # Forcefully make regions where pitch is <65 or greater than 500 unvoiced for relevant accurate pitch comparisons for our model
+ force_out_of_pitch = np.where(np.logical_or(pitch < 65,pitch > 500)==True)
+ voicing_all[force_out_of_pitch] = 0
+ C_all = C_all + np.where(voicing_all != 0)[0].shape[0]
+
+ list_rca_model_all.append(rca(cent,model_cents,voicing_all,thresh))
+
+ if "mic_M" in audio_file:
+ list_rca_male_all.append(rca(cent,model_cents,voicing_all,thresh))
+ C_all_m = C_all_m + np.where(voicing_all != 0)[0].shape[0]
+ else:
+ list_rca_female_all.append(rca(cent,model_cents,voicing_all,thresh))
+ C_all_f = C_all_f + np.where(voicing_all != 0)[0].shape[0]
+
+ list_rca_model_all = np.array(list_rca_model_all)
+ list_rca_male_all = np.array(list_rca_male_all)
+ list_rca_female_all = np.array(list_rca_female_all)
+
+
+ x = PrettyTable()
+
+ x.field_names = ["Experiment", "Mean RPA"]
+ x.add_row(["Both all pitches", np.sum(list_rca_model_all)/C_all])
+
+ x.add_row(["Male all pitches", np.sum(list_rca_male_all)/C_all_m])
+
+ x.add_row(["Female all pitches", np.sum(list_rca_female_all)/C_all_f])
+
+ print(x)
+
+ return None
+
+def cycle_eval(checkpoint_list, noise_type = 'synthetic', noise_dataset = None, list_snr = [-20,-15,-10,-5,0,5,10,15,20], ptdb_dataset_path = None,fraction = 0.1,thresh = 50):
+ """
+ Cycle through SNR evaluation for list of checkpoints
+ """
+ list_files = glob.glob(ptdb_dataset_path + 'combined_mic_16k/*.raw')
+ dir_f0 = ptdb_dataset_path + 'combined_reference_f0/'
+ random.shuffle(list_files)
+ list_files = list_files[:(int)(fraction*len(list_files))]
+
+ dict_models = {}
+ list_snr.append(np.inf)
+
+ for f in checkpoint_list:
+ if (f!='crepe') and (f!='lpcnet'):
+
+ checkpoint = torch.load(f, map_location='cpu')
+ dict_params = checkpoint['config']
+ if dict_params['data_format'] == 'if':
+ from models import large_if_ccode as model
+ pitch_nn = PitchDNNIF(dict_params['freq_keep']*3,dict_params['gru_dim'],dict_params['output_dim'])
+ elif dict_params['data_format'] == 'xcorr':
+ from models import large_xcorr as model
+ pitch_nn = PitchDNNXcorr(dict_params['xcorr_dim'],dict_params['gru_dim'],dict_params['output_dim'])
+ else:
+ from models import large_joint as model
+ pitch_nn = PitchDNN(dict_params['freq_keep']*3,dict_params['xcorr_dim'],dict_params['gru_dim'],dict_params['output_dim'])
+
+ pitch_nn.load_state_dict(checkpoint['state_dict'])
+
+ N = dict_params['window_size']
+ H = dict_params['hop_factor']
+ freq_keep = dict_params['freq_keep']
+
+ list_mean = []
+ list_std = []
+ for snr_dB in list_snr:
+ C_all = 0
+ C_correct = 0
+ for idx in tqdm.trange(len(list_files)):
+ audio_file = list_files[idx]
+ file_name = os.path.basename(list_files[idx])[:-4]
+
+ audio = np.memmap(list_files[idx], dtype=np.int16)/(2**15 - 1)
+ offset = 432
+ audio = audio[offset:]
+ rmse = np.squeeze(librosa.feature.rms(y = audio,frame_length = N,hop_length = H))
+
+ if noise_type != 'synthetic':
+ list_noisefiles = noise_dataset + '*.wav'
+ noise_file = random.choice(glob.glob(list_noisefiles))
+ n = np.memmap(noise_file, dtype=np.int16,mode = 'r')/(2**15 - 1)
+ rand_range = np.random.randint(low = 0, high = (16000*60*5 - audio.shape[0])) # Last 1 minute of noise used for testing
+ n = n[rand_range:rand_range + audio.shape[0]]
+ else:
+ n = np.random.randn(audio.shape[0])
+ n = random_filter(n)
+
+ snr_multiplier = np.sqrt((np.sum(np.abs(audio)**2)/np.sum(np.abs(n)**2))*10**(-snr_dB/10))
+ audio = audio + snr_multiplier*n
+
+ spec = stft(x = np.concatenate([np.zeros(160),audio]), w = 'boxcar', N = N, H = H).T
+ phase_diff = spec*np.conj(np.roll(spec,1,axis = -1))
+ phase_diff = phase_diff/(np.abs(phase_diff) + 1.0e-8)
+ idx_save = np.concatenate([np.arange(freq_keep),(N//2 + 1) + np.arange(freq_keep),2*(N//2 + 1) + np.arange(freq_keep)])
+ feature = np.concatenate([np.log(np.abs(spec) + 1.0e-8),np.real(phase_diff),np.imag(phase_diff)],axis = 0).T
+ feature_if = feature[:,idx_save]
+
+ data_temp = np.memmap('./temp.raw', dtype=np.int16, shape=(audio.shape[0]), mode='w+')
+ # data_temp[:audio.shape[0]] = (audio/(np.max(np.abs(audio)))*(2**15 - 1)).astype(np.int16)
+ data_temp[:audio.shape[0]] = ((audio)*(2**15 - 1)).astype(np.int16)
+
+ subprocess.run(["../../../lpcnet_xcorr_extractor", './temp.raw', './temp_xcorr.f32'])
+ feature_xcorr = np.flip(np.fromfile('./temp_xcorr.f32', dtype='float32').reshape((-1,256),order = 'C'),axis = 1)
+ ones_zero_lag = np.expand_dims(np.ones(feature_xcorr.shape[0]),-1)
+ feature_xcorr = np.concatenate([ones_zero_lag,feature_xcorr],axis = -1)
+
+ os.remove('./temp.raw')
+ os.remove('./temp_xcorr.f32')
+
+ if dict_params['data_format'] == 'if':
+ feature = feature_if
+ elif dict_params['data_format'] == 'xcorr':
+ feature = feature_xcorr
+ else:
+ indmin = min(feature_if.shape[0],feature_xcorr.shape[0])
+ feature = np.concatenate([feature_xcorr[:indmin,:],feature_if[:indmin,:]],-1)
+
+ pitch_file_name = dir_f0 + "ref" + os.path.basename(list_files[idx])[3:-4] + ".f0"
+ pitch = np.loadtxt(pitch_file_name)[:,0]
+ voicing = np.loadtxt(pitch_file_name)[:,1]
+ indmin = min(voicing.shape[0],rmse.shape[0],pitch.shape[0])
+ pitch = pitch[:indmin]
+ voicing = voicing[:indmin]
+ rmse = rmse[:indmin]
+ voicing = voicing*(rmse > 0.05*np.max(rmse))
+ if "mic_F" in audio_file:
+ idx_correct = np.where(pitch < 125)
+ voicing[idx_correct] = 0
+
+ cent = np.rint(1200*np.log2(np.divide(pitch, (16000/256), out=np.zeros_like(pitch), where=pitch!=0) + 1.0e-8)).astype('int')
+
+ model_cents = pitch_nn(torch.from_numpy(np.copy(np.expand_dims(feature,0))).float().to(device))
+ model_cents = 20*model_cents.argmax(dim=1).cpu().detach().squeeze().numpy()
+
+ num_frames = min(cent.shape[0],model_cents.shape[0])
+ pitch = pitch[:num_frames]
+ cent = cent[:num_frames]
+ voicing = voicing[:num_frames]
+ model_cents = model_cents[:num_frames]
+
+ voicing_all = np.copy(voicing)
+ # Forcefully make regions where pitch is <65 or greater than 500 unvoiced for relevant accurate pitch comparisons for our model
+ force_out_of_pitch = np.where(np.logical_or(pitch < 65,pitch > 500)==True)
+ voicing_all[force_out_of_pitch] = 0
+ C_all = C_all + np.where(voicing_all != 0)[0].shape[0]
+
+ C_correct = C_correct + rca(cent,model_cents,voicing_all,thresh)
+ list_mean.append(C_correct/C_all)
+ else:
+ fname = f
+ list_mean = []
+ list_std = []
+ for snr_dB in list_snr:
+ C_all = 0
+ C_correct = 0
+ for idx in tqdm.trange(len(list_files)):
+ audio_file = list_files[idx]
+ file_name = os.path.basename(list_files[idx])[:-4]
+
+ audio = np.memmap(list_files[idx], dtype=np.int16)/(2**15 - 1)
+ offset = 432
+ audio = audio[offset:]
+ rmse = np.squeeze(librosa.feature.rms(y = audio,frame_length = 320,hop_length = 160))
+
+ if noise_type != 'synthetic':
+ list_noisefiles = noise_dataset + '*.wav'
+ noise_file = random.choice(glob.glob(list_noisefiles))
+ n = np.memmap(noise_file, dtype=np.int16,mode = 'r')/(2**15 - 1)
+ rand_range = np.random.randint(low = 0, high = (16000*60*5 - audio.shape[0])) # Last 1 minute of noise used for testing
+ n = n[rand_range:rand_range + audio.shape[0]]
+ else:
+ n = np.random.randn(audio.shape[0])
+ n = random_filter(n)
+
+ snr_multiplier = np.sqrt((np.sum(np.abs(audio)**2)/np.sum(np.abs(n)**2))*10**(-snr_dB/10))
+ audio = audio + snr_multiplier*n
+
+ if (f == 'crepe'):
+ _, model_frequency, _, _ = crepe.predict(np.concatenate([np.zeros(80),audio]), 16000, viterbi=True,center=True,verbose=0)
+ model_cents = 1200*np.log2(model_frequency/(16000/256) + 1.0e-8)
+ else:
+ data_temp = np.memmap('./temp.raw', dtype=np.int16, shape=(audio.shape[0]), mode='w+')
+ # data_temp[:audio.shape[0]] = (audio/(np.max(np.abs(audio)))*(2**15 - 1)).astype(np.int16)
+ data_temp[:audio.shape[0]] = ((audio)*(2**15 - 1)).astype(np.int16)
+
+ subprocess.run(["../../../lpcnet_xcorr_extractor", './temp.raw', './temp_xcorr.f32', './temp_period.f32'])
+ feature_xcorr = np.fromfile('./temp_period.f32', dtype='float32')
+ model_cents = 1200*np.log2((256/feature_xcorr + 1.0e-8) + 1.0e-8)
+
+ os.remove('./temp.raw')
+ os.remove('./temp_xcorr.f32')
+ os.remove('./temp_period.f32')
+
+
+ pitch_file_name = dir_f0 + "ref" + os.path.basename(list_files[idx])[3:-4] + ".f0"
+ pitch = np.loadtxt(pitch_file_name)[:,0]
+ voicing = np.loadtxt(pitch_file_name)[:,1]
+ indmin = min(voicing.shape[0],rmse.shape[0],pitch.shape[0])
+ pitch = pitch[:indmin]
+ voicing = voicing[:indmin]
+ rmse = rmse[:indmin]
+ voicing = voicing*(rmse > 0.05*np.max(rmse))
+ if "mic_F" in audio_file:
+ idx_correct = np.where(pitch < 125)
+ voicing[idx_correct] = 0
+
+ cent = np.rint(1200*np.log2(np.divide(pitch, (16000/256), out=np.zeros_like(pitch), where=pitch!=0) + 1.0e-8)).astype('int')
+ num_frames = min(cent.shape[0],model_cents.shape[0])
+ pitch = pitch[:num_frames]
+ cent = cent[:num_frames]
+ voicing = voicing[:num_frames]
+ model_cents = model_cents[:num_frames]
+
+ voicing_all = np.copy(voicing)
+ # Forcefully make regions where pitch is <65 or greater than 500 unvoiced for relevant accurate pitch comparisons for our model
+ force_out_of_pitch = np.where(np.logical_or(pitch < 65,pitch > 500)==True)
+ voicing_all[force_out_of_pitch] = 0
+ C_all = C_all + np.where(voicing_all != 0)[0].shape[0]
+
+ C_correct = C_correct + rca(cent,model_cents,voicing_all,thresh)
+ list_mean.append(C_correct/C_all)
+ dict_models[fname] = {}
+ dict_models[fname]['list_SNR'] = list_mean[:-1]
+ dict_models[fname]['inf'] = list_mean[-1]
+
+ return dict_models
diff --git a/dnn/torch/neural-pitch/experiments.py b/dnn/torch/neural-pitch/experiments.py
new file mode 100644
index 00000000..bc8ea7e3
--- /dev/null
+++ b/dnn/torch/neural-pitch/experiments.py
@@ -0,0 +1,38 @@
+"""
+Running the experiments;
+ 1. RCA vs SNR for our models, CREPE, LPCNet
+"""
+
+import argparse
+parser = argparse.ArgumentParser()
+
+parser.add_argument('ptdb_root', type=str, help='Root Directory for PTDB generated by running ptdb_process.sh ')
+parser.add_argument('output', type=str, help='Output dump file name')
+parser.add_argument('method', type=str, help='Output Directory to save experiment dumps',choices=['model','lpcnet','crepe'])
+parser.add_argument('--noise_dataset', type=str, help='Location of the Demand Datset',default = './',required=False)
+parser.add_argument('--noise_type', type=str, help='Type of additive noise',default = 'synthetic',choices=['synthetic','demand'],required=False)
+parser.add_argument('--pth_file', type=str, help='.pth file to analyze',default = './',required = False)
+parser.add_argument('--fraction_files_analyze', type=float, help='Fraction of PTDB dataset to test on',default = 1,required = False)
+parser.add_argument('--threshold_rca', type=float, help='Cent threshold when computing RCA',default = 50,required = False)
+parser.add_argument('--gpu_index', type=int, help='GPU index to use if multiple GPUs',default = 0,required = False)
+
+args = parser.parse_args()
+
+import os
+os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
+os.environ["CUDA_VISIBLE_DEVICES"] = str(args.gpu_index)
+
+import json
+from evaluation import cycle_eval
+
+if args.method == 'model':
+ dict_store = cycle_eval([args.pth_file], noise_type = args.noise_type, noise_dataset = args.noise_dataset, list_snr = [-20,-15,-10,-5,0,5,10,15,20], ptdb_dataset_path = args.ptdb_root,fraction = args.fraction_files_analyze,thresh = args.threshold_rca)
+else:
+ dict_store = cycle_eval([args.method], noise_type = args.noise_type, noise_dataset = args.noise_dataset, list_snr = [-20,-15,-10,-5,0,5,10,15,20], ptdb_dataset_path = args.ptdb_root,fraction = args.fraction_files_analyze,thresh = args.threshold_rca)
+
+dict_store["method"] = args.method
+if args.method == 'model':
+ dict_store['pth'] = args.pth_file
+
+with open(args.output, 'w') as fp:
+ json.dump(dict_store, fp)
diff --git a/dnn/torch/neural-pitch/export_neuralpitch_weights.py b/dnn/torch/neural-pitch/export_neuralpitch_weights.py
new file mode 100644
index 00000000..577ec882
--- /dev/null
+++ b/dnn/torch/neural-pitch/export_neuralpitch_weights.py
@@ -0,0 +1,109 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../weight-exchange'))
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('checkpoint', type=str, help='model checkpoint')
+parser.add_argument('output_dir', type=str, help='output folder')
+
+args = parser.parse_args()
+
+import torch
+import numpy as np
+
+from models import PitchDNN
+from wexchange.torch import dump_torch_weights
+from wexchange.c_export import CWriter, print_vector
+
+def c_export(args, model):
+
+ message = f"Auto generated from checkpoint {os.path.basename(args.checkpoint)}"
+
+ writer = CWriter(os.path.join(args.output_dir, "pitchdnn_data"), message=message, model_struct_name='PitchDNN')
+ writer.header.write(
+f"""
+#include "opus_types.h"
+"""
+ )
+
+ dense_layers = [
+ ('if_upsample.0', "dense_if_upsampler_1"),
+ ('if_upsample.2', "dense_if_upsampler_2"),
+ ('downsample.0', "dense_downsampler"),
+ ("upsample.0", "dense_final_upsampler")
+ ]
+
+
+ for name, export_name in dense_layers:
+ layer = model.get_submodule(name)
+ dump_torch_weights(writer, layer, name=export_name, verbose=True, quantize=True, scale=None)
+
+ conv_layers = [
+ ('conv.1', "conv2d_1"),
+ ('conv.4', "conv2d_2")
+ ]
+
+
+ for name, export_name in conv_layers:
+ layer = model.get_submodule(name)
+ dump_torch_weights(writer, layer, name=export_name, verbose=True)
+
+
+ gru_layers = [
+ ("GRU", "gru_1"),
+ ]
+
+ max_rnn_units = max([dump_torch_weights(writer, model.get_submodule(name), export_name, verbose=True, input_sparse=False, quantize=True, scale=None, recurrent_scale=None)
+ for name, export_name in gru_layers])
+
+ writer.header.write(
+f"""
+
+#define PITCH_DNN_MAX_RNN_UNITS {max_rnn_units}
+
+"""
+ )
+
+ writer.close()
+
+
+if __name__ == "__main__":
+
+ os.makedirs(args.output_dir, exist_ok=True)
+ model = PitchDNN()
+ checkpoint = torch.load(args.checkpoint, map_location='cpu')
+ model.load_state_dict(checkpoint['state_dict'])
+ c_export(args, model)
diff --git a/dnn/torch/neural-pitch/models.py b/dnn/torch/neural-pitch/models.py
new file mode 100644
index 00000000..ce4977fd
--- /dev/null
+++ b/dnn/torch/neural-pitch/models.py
@@ -0,0 +1,178 @@
+"""
+Pitch Estimation Models and dataloaders
+ - Classification Based (Input features, output logits)
+"""
+
+import torch
+import numpy as np
+
+class PitchDNNIF(torch.nn.Module):
+
+ def __init__(self, input_dim=88, gru_dim=64, output_dim=192):
+ super().__init__()
+
+ self.activation = torch.nn.Tanh()
+ self.initial = torch.nn.Linear(input_dim, gru_dim)
+ self.hidden = torch.nn.Linear(gru_dim, gru_dim)
+ self.gru = torch.nn.GRU(input_size=gru_dim, hidden_size=gru_dim, batch_first=True)
+ self.upsample = torch.nn.Linear(gru_dim, output_dim)
+
+ def forward(self, x):
+
+ x = self.initial(x)
+ x = self.activation(x)
+ x = self.hidden(x)
+ x = self.activation(x)
+ x,_ = self.gru(x)
+ x = self.upsample(x)
+ x = self.activation(x)
+ x = x.permute(0,2,1)
+
+ return x
+
+class PitchDNNXcorr(torch.nn.Module):
+
+ def __init__(self, input_dim=90, gru_dim=64, output_dim=192):
+ super().__init__()
+
+ self.activation = torch.nn.Tanh()
+
+ self.conv = torch.nn.Sequential(
+ torch.nn.ZeroPad2d((2, 0, 1, 1)),
+ torch.nn.Conv2d(1, 8, 3, bias=True),
+ self.activation,
+ torch.nn.ZeroPad2d((2,0,1,1)),
+ torch.nn.Conv2d(8, 8, 3, bias=True),
+ self.activation,
+ torch.nn.ZeroPad2d((2,0,1,1)),
+ torch.nn.Conv2d(8, 1, 3, bias=True),
+ self.activation,
+ )
+
+ self.downsample = torch.nn.Sequential(
+ torch.nn.Linear(input_dim, gru_dim),
+ self.activation
+ )
+ self.GRU = torch.nn.GRU(input_size=gru_dim, hidden_size=gru_dim, num_layers=1, batch_first=True)
+ self.upsample = torch.nn.Sequential(
+ torch.nn.Linear(gru_dim,output_dim),
+ self.activation
+ )
+
+ def forward(self, x):
+ x = self.conv(x.unsqueeze(-1).permute(0,3,2,1)).squeeze(1)
+ x,_ = self.GRU(self.downsample(x.permute(0,2,1)))
+ x = self.upsample(x).permute(0,2,1)
+
+ return x
+
+class PitchDNN(torch.nn.Module):
+ """
+ Joint IF-xcorr
+ 1D CNN on IF, merge with xcorr, 2D CNN on merged + GRU
+ """
+
+ def __init__(self,input_IF_dim=88, input_xcorr_dim=224, gru_dim=64, output_dim=192):
+ super().__init__()
+
+ self.activation = torch.nn.Tanh()
+
+ self.if_upsample = torch.nn.Sequential(
+ torch.nn.Linear(input_IF_dim,64),
+ self.activation,
+ torch.nn.Linear(64,64),
+ self.activation,
+ )
+
+ self.conv = torch.nn.Sequential(
+ torch.nn.ZeroPad2d((2,0,1,1)),
+ torch.nn.Conv2d(1, 4, 3, bias=True),
+ self.activation,
+ torch.nn.ZeroPad2d((2,0,1,1)),
+ torch.nn.Conv2d(4, 1, 3, bias=True),
+ self.activation,
+ )
+
+ self.downsample = torch.nn.Sequential(
+ torch.nn.Linear(64 + input_xcorr_dim, gru_dim),
+ self.activation
+ )
+ self.GRU = torch.nn.GRU(input_size=gru_dim, hidden_size=gru_dim, num_layers=1, batch_first=True)
+ self.upsample = torch.nn.Sequential(
+ torch.nn.Linear(gru_dim, output_dim)
+ )
+
+ def forward(self, x):
+ xcorr_feat = x[:,:,:224]
+ if_feat = x[:,:,224:]
+ xcorr_feat = self.conv(xcorr_feat.unsqueeze(-1).permute(0,3,2,1)).squeeze(1).permute(0,2,1)
+ if_feat = self.if_upsample(if_feat)
+ x = torch.cat([xcorr_feat,if_feat],axis = - 1)
+ x,_ = self.GRU(self.downsample(x))
+ x = self.upsample(x).permute(0,2,1)
+
+ return x
+
+
+# Dataloaders
+class Loader(torch.utils.data.Dataset):
+ def __init__(self, features_if, file_pitch, confidence_threshold=0.4, dimension_if=30, context=100):
+ self.if_feat = np.memmap(features_if, dtype=np.float32).reshape(-1,3*dimension_if)
+
+ # Resolution of 20 cents
+ self.cents = np.rint(np.load(file_pitch)[0,:]/20)
+ self.cents = np.clip(self.cents,0,179)
+ self.confidence = np.load(file_pitch)[1,:]
+
+ # Filter confidence for CREPE
+ self.confidence[self.confidence < confidence_threshold] = 0
+ self.context = context
+ # Clip both to same size
+ size_common = min(self.if_feat.shape[0], self.cents.shape[0])
+ self.if_feat = self.if_feat[:size_common,:]
+ self.cents = self.cents[:size_common]
+ self.confidence = self.confidence[:size_common]
+
+ frame_max = self.if_feat.shape[0]//context
+ self.if_feat = np.reshape(self.if_feat[:frame_max*context, :],(frame_max, context,3*dimension_if))
+ self.cents = np.reshape(self.cents[:frame_max * context],(frame_max, context))
+ self.confidence = np.reshape(self.confidence[:frame_max*context],(frame_max, context))
+
+ def __len__(self):
+ return self.if_feat.shape[0]
+
+ def __getitem__(self, index):
+ return torch.from_numpy(self.if_feat[index,:,:]), torch.from_numpy(self.cents[index]), torch.from_numpy(self.confidence[index])
+
+class PitchDNNDataloader(torch.utils.data.Dataset):
+ def __init__(self, features, file_pitch, confidence_threshold=0.4, context=100, choice_data='both'):
+ self.feat = np.memmap(features, mode='r', dtype=np.int8).reshape(-1,312)
+ self.xcorr = self.feat[:,:224]
+ self.if_feat = self.feat[:,224:]
+ ground_truth = np.memmap(file_pitch, mode='r', dtype=np.float32).reshape(-1,2)
+ self.cents = np.rint(60*np.log2(ground_truth[:,0]/62.5))
+ mask = (self.cents>=0).astype('float32') * (self.cents<=180).astype('float32')
+ self.cents = np.clip(self.cents,0,179)
+ self.confidence = ground_truth[:,1] * mask
+ # Filter confidence for CREPE
+ self.confidence[self.confidence < confidence_threshold] = 0
+ self.context = context
+
+ self.choice_data = choice_data
+
+ frame_max = self.if_feat.shape[0]//context
+ self.if_feat = np.reshape(self.if_feat[:frame_max*context,:], (frame_max, context, 88))
+ self.cents = np.reshape(self.cents[:frame_max*context], (frame_max,context))
+ self.xcorr = np.reshape(self.xcorr[:frame_max*context,:], (frame_max,context, 224))
+ self.confidence = np.reshape(self.confidence[:frame_max*context], (frame_max, context))
+
+ def __len__(self):
+ return self.if_feat.shape[0]
+
+ def __getitem__(self, index):
+ if self.choice_data == 'both':
+ return torch.cat([torch.from_numpy((1./127)*self.xcorr[index,:,:]), torch.from_numpy((1./127)*self.if_feat[index,:,:])], dim=-1), torch.from_numpy(self.cents[index]), torch.from_numpy(self.confidence[index])
+ elif self.choice_data == 'if':
+ return torch.from_numpy((1./127)*self.if_feat[index,:,:]),torch.from_numpy(self.cents[index]),torch.from_numpy(self.confidence[index])
+ else:
+ return torch.from_numpy((1./127)*self.xcorr[index,:,:]),torch.from_numpy(self.cents[index]),torch.from_numpy(self.confidence[index])
diff --git a/dnn/torch/neural-pitch/neural_pitch_update.py b/dnn/torch/neural-pitch/neural_pitch_update.py
new file mode 100644
index 00000000..aa2caf99
--- /dev/null
+++ b/dnn/torch/neural-pitch/neural_pitch_update.py
@@ -0,0 +1,179 @@
+import argparse
+parser = argparse.ArgumentParser()
+
+parser.add_argument('features', type=str, help='Features generated from dump_data')
+parser.add_argument('data', type=str, help='Data generated from dump_data (offset by 5ms)')
+parser.add_argument('output', type=str, help='output .f32 feature file with replaced neural pitch')
+parser.add_argument('checkpoint', type=str, help='model checkpoint file')
+parser.add_argument('path_lpcnet_extractor', type=str, help='path to LPCNet extractor object file (generated on compilation)')
+parser.add_argument('--device', type=str, help='compute device',default = None,required = False)
+parser.add_argument('--replace_xcorr', type = bool, default = False, help='Replace LPCNet xcorr with updated one')
+
+args = parser.parse_args()
+
+import os
+
+from utils import stft, random_filter
+import subprocess
+import numpy as np
+import json
+import torch
+import tqdm
+
+from models import PitchDNNIF, PitchDNNXcorr, PitchDNN
+
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+if device is not None:
+ device = torch.device(args.device)
+
+# Loading the appropriate model
+checkpoint = torch.load(args.checkpoint, map_location='cpu')
+dict_params = checkpoint['config']
+
+if dict_params['data_format'] == 'if':
+ pitch_nn = PitchDNNIF(dict_params['freq_keep']*3, dict_params['gru_dim'], dict_params['output_dim'])
+elif dict_params['data_format'] == 'xcorr':
+ pitch_nn = PitchDNNXcorr(dict_params['xcorr_dim'], dict_params['gru_dim'], dict_params['output_dim'])
+else:
+ pitch_nn = PitchDNN(dict_params['freq_keep']*3, dict_params['xcorr_dim'], dict_params['gru_dim'], dict_params['output_dim'])
+
+pitch_nn.load_state_dict(checkpoint['state_dict'])
+pitch_nn = pitch_nn.to(device)
+
+N = dict_params['window_size']
+H = dict_params['hop_factor']
+freq_keep = dict_params['freq_keep']
+
+os.environ["OMP_NUM_THREADS"] = "16"
+
+
+def run_lpc(signal, lpcs, frame_length=160):
+ num_frames, lpc_order = lpcs.shape
+
+ prediction = np.concatenate(
+ [- np.convolve(signal[i * frame_length : (i + 1) * frame_length + lpc_order - 1], lpcs[i], mode='valid') for i in range(num_frames)]
+ )
+ error = signal[lpc_order :] - prediction
+
+ return prediction, error
+
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+ features = np.memmap(args.features, dtype=np.float32,mode = 'r').reshape((-1, 36))
+ data = np.memmap(args.data, dtype=np.int16,mode = 'r').reshape((-1, 2))
+
+ num_frames = features.shape[0]
+ feature_dim = features.shape[1]
+
+ assert feature_dim == 36
+
+ output = np.memmap(args.output, dtype=np.float32, shape=(num_frames, feature_dim), mode='w+')
+ output[:, :36] = features
+
+ # lpc coefficients and signal
+ lpcs = features[:, 20:36]
+ sig = data[:, 1]
+
+ # parameters
+
+ # constants
+ pitch_min = 32
+ pitch_max = 256
+ lpc_order = 16
+ fs = 16000
+ frame_length = 160
+ overlap_frames = 100
+ chunk_size = 10000
+ history_length = frame_length * overlap_frames
+ history = np.zeros(history_length, dtype=np.int16)
+ pitch_position=18
+ xcorr_position=19
+ conf_position=36
+
+ num_frames = len(sig) // 160 - 1
+
+ frame_start = 0
+ frame_stop = min(frame_start + chunk_size, num_frames)
+ signal_start = 0
+ signal_stop = frame_stop * frame_length
+
+ niters = (num_frames - 1)//chunk_size
+ for i in tqdm.trange(niters):
+ if (frame_start > num_frames - 1):
+ break
+ chunk = np.concatenate((history, sig[signal_start:signal_stop]))
+ chunk_la = np.concatenate((history, sig[signal_start:signal_stop + 80]))
+
+ # Feature computation
+ spec = stft(x = np.concatenate([np.zeros(80),chunk_la/(2**15 - 1)]), w = 'boxcar', N = N, H = H).T
+ phase_diff = spec*np.conj(np.roll(spec,1,axis = -1))
+ phase_diff = phase_diff/(np.abs(phase_diff) + 1.0e-8)
+ idx_save = np.concatenate([np.arange(freq_keep),(N//2 + 1) + np.arange(freq_keep),2*(N//2 + 1) + np.arange(freq_keep)])
+ feature = np.concatenate([np.log(np.abs(spec) + 1.0e-8),np.real(phase_diff),np.imag(phase_diff)],axis = 0).T
+ feature_if = feature[:,idx_save]
+
+ data_temp = np.memmap('./temp_featcompute_' + dict_params['data_format'] + '_.raw', dtype=np.int16, shape=(chunk.shape[0]), mode='w+')
+ data_temp[:chunk.shape[0]] = chunk_la[80:].astype(np.int16)
+
+ subprocess.run([args.path_lpcnet_extractor, './temp_featcompute_' + dict_params['data_format'] + '_.raw', './temp_featcompute_xcorr_' + dict_params['data_format'] + '_.raw'])
+ feature_xcorr = np.flip(np.fromfile('./temp_featcompute_xcorr_' + dict_params['data_format'] + '_.raw', dtype='float32').reshape((-1,256),order = 'C'),axis = 1)
+ ones_zero_lag = np.expand_dims(np.ones(feature_xcorr.shape[0]),-1)
+ feature_xcorr = np.concatenate([ones_zero_lag,feature_xcorr],axis = -1)
+
+ os.remove('./temp_featcompute_' + dict_params['data_format'] + '_.raw')
+ os.remove('./temp_featcompute_xcorr_' + dict_params['data_format'] + '_.raw')
+
+ if dict_params['data_format'] == 'if':
+ feature = feature_if
+ elif dict_params['data_format'] == 'xcorr':
+ feature = feature_xcorr
+ else:
+ indmin = min(feature_if.shape[0],feature_xcorr.shape[0])
+ feature = np.concatenate([feature_xcorr[:indmin,:],feature_if[:indmin,:]],-1)
+
+ # Compute pitch with my model
+ model_cents = pitch_nn(torch.from_numpy(np.copy(np.expand_dims(feature,0))).float().to(device))
+ model_cents = 20*model_cents.argmax(dim=1).cpu().detach().squeeze().numpy()
+ frequency = 62.5*2**(model_cents/1200)
+
+ frequency = frequency[overlap_frames : overlap_frames + frame_stop - frame_start]
+
+ # convert frequencies to periods
+ periods = np.round(fs / frequency)
+
+ periods = np.clip(periods, pitch_min, pitch_max)
+
+ output[frame_start:frame_stop, pitch_position] = (periods - 100) / 50
+
+ frame_offset = (pitch_max + frame_length - 1) // frame_length
+ offset = frame_offset * frame_length
+ padding = lpc_order
+
+
+ if frame_start < frame_offset:
+ lpc_coeffs = np.concatenate((np.zeros((frame_offset - frame_start, lpc_order), dtype=np.float32), lpcs[:frame_stop]))
+ else:
+ lpc_coeffs = lpcs[frame_start - frame_offset : frame_stop]
+
+ pred, error = run_lpc(chunk[history_length - offset - padding :], lpc_coeffs, frame_length=frame_length)
+
+ xcorr = np.zeros(frame_stop - frame_start)
+ for i, p in enumerate(periods.astype(np.int16)):
+ if p > 0:
+ f1 = error[offset + i * frame_length : offset + (i + 1) * frame_length]
+ f2 = error[offset + i * frame_length - p : offset + (i + 1) * frame_length - p]
+ xcorr[i] = np.dot(f1, f2) / np.sqrt(np.dot(f1, f1) * np.dot(f2, f2) + 1e-6)
+
+ output[frame_start:frame_stop, xcorr_position] = xcorr - 0.5
+
+ # update buffers and indices
+ history = chunk[-history_length :]
+
+ frame_start += chunk_size
+ frame_stop += chunk_size
+ frame_stop = min(frame_stop, num_frames)
+
+ signal_start = frame_start * frame_length
+ signal_stop = frame_stop * frame_length
diff --git a/dnn/torch/neural-pitch/ptdb_process.sh b/dnn/torch/neural-pitch/ptdb_process.sh
new file mode 100644
index 00000000..f4df5465
--- /dev/null
+++ b/dnn/torch/neural-pitch/ptdb_process.sh
@@ -0,0 +1,34 @@
+# Copy into PTDB root directory and run to combine all the male/female raw audio/references into below directories
+
+# Make folder for combined audio
+mkdir -p './combined_mic_16k/'
+# Make folder for combined pitch reference
+mkdir -p './combined_reference_f0/'
+
+# Resample Male Audio
+for i in ./MALE/MIC/**/*.wav; do
+j="$(basename "$i" .wav)"
+echo $j
+sox -r 48000 -b 16 -e signed-integer "$i" -r 16000 -b 16 -e signed-integer ./combined_mic_16k/$j.raw
+done
+
+# Resample Female Audio
+for i in ./FEMALE/MIC/**/*.wav; do
+j="$(basename "$i" .wav)"
+echo $j
+sox -r 48000 -b 16 -e signed-integer "$i" -r 16000 -b 16 -e signed-integer ./combined_mic_16k/$j.raw
+done
+
+# Shift Male reference pitch files
+for i in ./MALE/REF/**/*.f0; do
+j="$(basename "$i" .wav)"
+echo $j
+cp "$i" ./combined_reference_f0/
+done
+
+# Shift Female reference pitch files
+for i in ./FEMALE/REF/**/*.f0; do
+j="$(basename "$i" .wav)"
+echo $j
+cp "$i" ./combined_reference_f0/
+done \ No newline at end of file
diff --git a/dnn/torch/neural-pitch/run_crepe.py b/dnn/torch/neural-pitch/run_crepe.py
new file mode 100644
index 00000000..25d65241
--- /dev/null
+++ b/dnn/torch/neural-pitch/run_crepe.py
@@ -0,0 +1,72 @@
+"""
+Perform Data Augmentation (Gain, Additive Noise, Random Filtering) on Input TTS Data
+1. Read in chunks and compute clean pitch first
+2. Then add in augmentation (Noise/Level/Response)
+ - Adds filtered noise from the "Demand" dataset, https://zenodo.org/record/1227121#.XRKKxYhKiUk
+ - When using the Demand Dataset, consider each channel as a possible noise input, and keep the first 4 minutes of noise for training
+3. Use this "augmented" audio for feature computation, and compute pitch using CREPE on the clean input
+
+Notes: To ensure consistency with the discovered CREPE offset, we do the following
+- We pad the input audio to the zero-centered CREPE estimator with 80 zeros
+- We pad the input audio to our feature computation with 160 zeros to center them
+"""
+
+import argparse
+parser = argparse.ArgumentParser()
+
+parser.add_argument('data', type=str, help='input raw audio data')
+parser.add_argument('output', type=str, help='output directory')
+parser.add_argument('--gpu-index', type=int, help='GPU index to use if multiple GPUs',default = 0,required = False)
+parser.add_argument('--chunk-size-frames', type=int, help='Number of frames to process at a time',default = 100000,required = False)
+
+args = parser.parse_args()
+
+import os
+os.environ["CUDA_VISIBLE_DEVICES"] = str(args.gpu_index)
+
+import numpy as np
+import tqdm
+import crepe
+
+data = np.memmap(args.data, dtype=np.int16,mode = 'r')
+
+# list_features = []
+list_cents = []
+list_confidences = []
+
+min_period = 32
+max_period = 256
+f_ref = 16000/max_period
+chunk_size_frames = args.chunk_size_frames
+chunk_size = chunk_size_frames*160
+
+nb_chunks = (data.shape[0]+79)//chunk_size+1
+
+output_data = np.zeros((0,2),dtype='float32')
+
+for i in tqdm.trange(nb_chunks):
+ if i==0:
+ chunk = np.concatenate([np.zeros(80),data[:chunk_size-80]])
+ elif i==nb_chunks-1:
+ chunk = data[i*chunk_size-80:]
+ else:
+ chunk = data[i*chunk_size-80:(i+1)*chunk_size-80]
+ chunk = chunk/np.array(32767.,dtype='float32')
+
+ # Clean Pitch/Confidence Estimate
+ # Padding input to CREPE by 80 samples to ensure it aligns
+ _, pitch, confidence, _ = crepe.predict(chunk, 16000, center=True, viterbi=True,verbose=0)
+ pitch = pitch[:chunk_size_frames]
+ confidence = confidence[:chunk_size_frames]
+
+
+ # Filter out of range pitches/confidences
+ confidence[pitch < 16000/max_period] = 0
+ confidence[pitch > 16000/min_period] = 0
+ pitch = np.reshape(pitch, (-1, 1))
+ confidence = np.reshape(confidence, (-1, 1))
+ out = np.concatenate([pitch, confidence], axis=-1, dtype='float32')
+ output_data = np.concatenate([output_data, out], axis=0)
+
+
+output_data.tofile(args.output)
diff --git a/dnn/torch/neural-pitch/training.py b/dnn/torch/neural-pitch/training.py
new file mode 100644
index 00000000..62da1351
--- /dev/null
+++ b/dnn/torch/neural-pitch/training.py
@@ -0,0 +1,162 @@
+"""
+Training the neural pitch estimator
+
+"""
+
+import os
+import argparse
+parser = argparse.ArgumentParser()
+
+parser.add_argument('features', type=str, help='.f32 IF Features for training (generated by augmentation script)')
+parser.add_argument('features_pitch', type=str, help='.npy Pitch file for training (generated by augmentation script)')
+parser.add_argument('output_folder', type=str, help='Output directory to store the model weights and config')
+parser.add_argument('data_format', type=str, help='Choice of Input Data',choices=['if','xcorr','both'])
+parser.add_argument('--gpu_index', type=int, help='GPU index to use if multiple GPUs',default = 0,required = False)
+parser.add_argument('--confidence_threshold', type=float, help='Confidence value below which pitch will be neglected during training',default = 0.4,required = False)
+parser.add_argument('--context', type=int, help='Sequence length during training',default = 100,required = False)
+parser.add_argument('--N', type=int, help='STFT window size',default = 320,required = False)
+parser.add_argument('--H', type=int, help='STFT Hop size',default = 160,required = False)
+parser.add_argument('--xcorr_dimension', type=int, help='Dimension of Input cross-correlation',default = 257,required = False)
+parser.add_argument('--freq_keep', type=int, help='Number of Frequencies to keep',default = 30,required = False)
+parser.add_argument('--gru_dim', type=int, help='GRU Dimension',default = 64,required = False)
+parser.add_argument('--output_dim', type=int, help='Output dimension',default = 192,required = False)
+parser.add_argument('--learning_rate', type=float, help='Learning Rate',default = 1.0e-3,required = False)
+parser.add_argument('--epochs', type=int, help='Number of training epochs',default = 50,required = False)
+parser.add_argument('--choice_cel', type=str, help='Choice of Cross Entropy Loss (default or robust)',choices=['default','robust'],default = 'default',required = False)
+parser.add_argument('--prefix', type=str, help="prefix for model export, default: model", default='model')
+parser.add_argument('--initial-checkpoint', type=str, help='initial checkpoint to start training from, default: None', default=None)
+
+
+args = parser.parse_args()
+
+# import os
+# os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
+# os.environ["CUDA_VISIBLE_DEVICES"] = str(args.gpu_index)
+
+# Fixing the seeds for reproducability
+import time
+np_seed = int(time.time())
+torch_seed = int(time.time())
+
+import torch
+torch.manual_seed(torch_seed)
+import numpy as np
+np.random.seed(np_seed)
+from utils import count_parameters
+import tqdm
+from models import PitchDNN, PitchDNNIF, PitchDNNXcorr, PitchDNNDataloader
+
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+
+
+if args.data_format == 'if':
+ pitch_nn = PitchDNNIF(3 * args.freq_keep - 2, args.gru_dim, args.output_dim)
+elif args.data_format == 'xcorr':
+ pitch_nn = PitchDNNXcorr(args.xcorr_dimension, args.gru_dim, args.output_dim)
+else:
+ pitch_nn = PitchDNN(3 * args.freq_keep - 2, 224, args.gru_dim, args.output_dim)
+
+if type(args.initial_checkpoint) != type(None):
+ checkpoint = torch.load(args.initial_checkpoint, map_location='cpu')
+ pitch_nn.load_state_dict(checkpoint['state_dict'], strict=False)
+
+
+dataset_training = PitchDNNDataloader(args.features,args.features_pitch,args.confidence_threshold,args.context,args.data_format)
+
+def loss_custom(logits,labels,confidence,choice = 'default',nmax = 192,q = 0.7):
+ logits_softmax = torch.nn.Softmax(dim = 1)(logits).permute(0,2,1)
+ labels_one_hot = torch.nn.functional.one_hot(labels.long(),nmax)
+
+ if choice == 'default':
+ # Categorical Cross Entropy
+ CE = -torch.sum(torch.log(logits_softmax*labels_one_hot + 1.0e-6)*labels_one_hot,dim=-1)
+ CE = torch.mean(confidence*CE)
+
+ else:
+ # Robust Cross Entropy
+ CE = (1.0/q)*(1 - torch.sum(torch.pow(logits_softmax*labels_one_hot + 1.0e-7,q),dim=-1) )
+ CE = torch.sum(confidence*CE)
+
+ return CE
+
+def accuracy(logits,labels,confidence,choice = 'default',nmax = 192,q = 0.7):
+ logits_softmax = torch.nn.Softmax(dim = 1)(logits).permute(0,2,1)
+ pred_pitch = torch.argmax(logits_softmax, 2)
+ accuracy = (pred_pitch != labels.long())*1.
+ return 1.-torch.mean(confidence*accuracy)
+
+train_dataset, test_dataset = torch.utils.data.random_split(dataset_training, [0.95,0.05], generator=torch.Generator().manual_seed(torch_seed))
+
+batch_size = 256
+train_dataloader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True, num_workers=0, pin_memory=False)
+test_dataloader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True, num_workers=0, pin_memory=False)
+
+pitch_nn = pitch_nn.to(device)
+num_params = count_parameters(pitch_nn)
+learning_rate = args.learning_rate
+model_opt = torch.optim.Adam(pitch_nn.parameters(), lr = learning_rate)
+
+num_epochs = args.epochs
+
+for epoch in range(num_epochs):
+ losses = []
+ accs = []
+ pitch_nn.train()
+ with tqdm.tqdm(train_dataloader) as train_epoch:
+ for i, (xi, yi, ci) in enumerate(train_epoch):
+ yi, xi, ci = yi.to(device, non_blocking=True), xi.to(device, non_blocking=True), ci.to(device, non_blocking=True)
+ pi = pitch_nn(xi.float())
+ loss = loss_custom(logits = pi,labels = yi,confidence = ci,choice = args.choice_cel,nmax = args.output_dim)
+ acc = accuracy(logits = pi,labels = yi,confidence = ci,choice = args.choice_cel,nmax = args.output_dim)
+ acc = acc.detach()
+
+ model_opt.zero_grad()
+ loss.backward()
+ model_opt.step()
+
+ losses.append(loss.item())
+ accs.append(acc.item())
+ avg_loss = np.mean(losses)
+ avg_acc = np.mean(accs)
+ train_epoch.set_postfix({"Train Epoch" : epoch, "Train Loss":avg_loss, "acc" : avg_acc.item()})
+
+ if epoch % 5 == 0:
+ pitch_nn.eval()
+ losses = []
+ with tqdm.tqdm(test_dataloader) as test_epoch:
+ for i, (xi, yi, ci) in enumerate(test_epoch):
+ yi, xi, ci = yi.to(device, non_blocking=True), xi.to(device, non_blocking=True), ci.to(device, non_blocking=True)
+ pi = pitch_nn(xi.float())
+ loss = loss_custom(logits = pi,labels = yi,confidence = ci,choice = args.choice_cel,nmax = args.output_dim)
+ losses.append(loss.item())
+ avg_loss = np.mean(losses)
+ test_epoch.set_postfix({"Epoch" : epoch, "Test Loss":avg_loss})
+
+pitch_nn.eval()
+
+config = dict(
+ data_format=args.data_format,
+ epochs=num_epochs,
+ window_size= args.N,
+ hop_factor= args.H,
+ freq_keep=args.freq_keep,
+ batch_size=batch_size,
+ learning_rate=learning_rate,
+ confidence_threshold=args.confidence_threshold,
+ model_parameters=num_params,
+ np_seed=np_seed,
+ torch_seed=torch_seed,
+ xcorr_dim=args.xcorr_dimension,
+ dim_input=3*args.freq_keep - 2,
+ gru_dim=args.gru_dim,
+ output_dim=args.output_dim,
+ choice_cel=args.choice_cel,
+ context=args.context,
+)
+
+model_save_path = os.path.join(args.output_folder, f"{args.prefix}_{args.data_format}.pth")
+checkpoint = {
+ 'state_dict': pitch_nn.state_dict(),
+ 'config': config
+}
+torch.save(checkpoint, model_save_path)
diff --git a/dnn/torch/neural-pitch/utils.py b/dnn/torch/neural-pitch/utils.py
new file mode 100644
index 00000000..8930ad19
--- /dev/null
+++ b/dnn/torch/neural-pitch/utils.py
@@ -0,0 +1,59 @@
+"""
+Utility functions that are commonly used
+"""
+
+import numpy as np
+from scipy.signal import windows, lfilter
+from prettytable import PrettyTable
+
+
+# Source: https://gist.github.com/thongonary/026210fc186eb5056f2b6f1ca362d912
+def count_parameters(model):
+ table = PrettyTable(["Modules", "Parameters"])
+ total_params = 0
+ for name, parameter in model.named_parameters():
+ if not parameter.requires_grad: continue
+ param = parameter.numel()
+ table.add_row([name, param])
+ total_params+=param
+ print(table)
+ print(f"Total Trainable Params: {total_params}")
+ return total_params
+
+def stft(x, w = 'boxcar', N = 320, H = 160):
+ x = np.concatenate([x,np.zeros(N)])
+ # win_custom = np.concatenate([windows.hann(80)[:40],np.ones(240),windows.hann(80)[40:]])
+ return np.stack([np.fft.rfft(x[i:i + N]*windows.get_window(w,N)) for i in np.arange(0,x.shape[0]-N,H)])
+
+def random_filter(x):
+ # Randomly filter x with second order IIR filter with coefficients in between -3/8,3/8
+ filter_coeff = np.random.uniform(low = -3.0/8, high = 3.0/8, size = 4)
+ b = [1,filter_coeff[0],filter_coeff[1]]
+ a = [1,filter_coeff[2],filter_coeff[3]]
+ return lfilter(b,a,x)
+
+def feature_xform(feature):
+ """
+ Take as input the (N * 256) xcorr features output by LPCNet and perform the following
+ 1. Downsample and Upsample by 2 (followed by smoothing)
+ 2. Append positional embeddings (of dim k) coresponding to each xcorr lag
+ """
+
+ from scipy.signal import resample_poly, lfilter
+
+
+ feature_US = lfilter([0.25,0.5,0.25],[1],resample_poly(feature,2,1,axis = 1),axis = 1)[:,:feature.shape[1]]
+ feature_DS = lfilter([0.5,0.5],[1],resample_poly(feature,1,2,axis = 1),axis = 1)
+ Z_append = np.zeros((feature.shape[0],feature.shape[1] - feature_DS.shape[1]))
+ feature_DS = np.concatenate([feature_DS,Z_append],axis = -1)
+
+ # pos_embedding = []
+ # for i in range(k):
+ # pos_embedding.append(np.cos((2**i)*np.pi*((np.repeat(np.arange(feature.shape[1]).reshape(feature.shape[1],1),feature.shape[0],axis = 1)).T/(2*feature.shape[1]))))
+
+ # pos_embedding = np.stack(pos_embedding,axis = -1)
+
+ feature = np.stack((feature_DS,feature,feature_US),axis = -1)
+ # feature = np.concatenate((feature,pos_embedding),axis = -1)
+
+ return feature
diff --git a/dnn/torch/osce/README.md b/dnn/torch/osce/README.md
new file mode 100644
index 00000000..74d1f505
--- /dev/null
+++ b/dnn/torch/osce/README.md
@@ -0,0 +1,65 @@
+# Opus Speech Coding Enhancement
+
+This folder hosts models for enhancing Opus SILK.
+
+## Environment setup
+The code is tested with python 3.11. Conda setup is done via
+
+
+`conda create -n osce python=3.11`
+
+`conda activate osce`
+
+`python -m pip install -r requirements.txt`
+
+
+## Generating training data
+First step is to convert all training items to 16 kHz and 16 bit pcm and then concatenate them. A convenient way to do this is to create a file list and then run
+
+`python scripts/concatenator.py filelist 16000 dataset/clean.s16 --db_min -40 --db_max 0`
+
+which on top provides some random scaling.
+
+Second step is to run a patched version of opus_demo in the dataset folder, which will produce the coded output and add feature files. To build the patched opus_demo binary, check out the exp-neural-silk-enhancement branch and build opus_demo the usual way. Then run
+
+`cd dataset && <path_to_patched_opus_demo>/opus_demo voip 16000 1 9000 -silk_random_switching 249 clean.s16 coded.s16 `
+
+The argument to -silk_random_switching specifies the number of frames after which parameters are switched randomly.
+
+## Regression loss based training
+Create a default setup for LACE or NoLACE via
+
+`python make_default_setup.py model.yml --model lace/nolace --path2dataset <path2dataset>`
+
+Then run
+
+`python train_model.py model.yml <output folder> --no-redirect`
+
+for running the training script in foreground or
+
+`nohup python train_model.py model.yml <output folder> &`
+
+to run it in background. In the latter case the output is written to `<output folder>/out.txt`.
+
+## Adversarial training (NoLACE only)
+Create a default setup for NoLACE via
+
+`python make_default_setup.py nolace_adv.yml --model nolace --adversarial --path2dataset <path2dataset>`
+
+Then run
+
+`python adv_train_model.py nolace_adv.yml <output folder> --no-redirect`
+
+for running the training script in foreground or
+
+`nohup python adv_train_model.py nolace_adv.yml <output folder> &`
+
+to run it in background. In the latter case the output is written to `<output folder>/out.txt`.
+
+## Inference
+Generating inference data is analogous to generating training data. Given an item 'item1.wav' run
+`mkdir item1.se && sox item1.wav -r 16000 -e signed-integer -b 16 item1.raw && cd item1.se && <path_to_patched_opus_demo>/opus_demo voip 16000 1 <bitrate> ../item1.raw noisy.s16`
+
+The folder item1.se then serves as input for the test_model.py script or for the --testdata argument of train_model.py resp. adv_train_model.py
+
+Checkpoints of pre-trained models are located here: https://media.xiph.org/lpcnet/models/lace-20231019.tar.gz \ No newline at end of file
diff --git a/dnn/torch/osce/adv_train_model.py b/dnn/torch/osce/adv_train_model.py
new file mode 100644
index 00000000..7db859e4
--- /dev/null
+++ b/dnn/torch/osce/adv_train_model.py
@@ -0,0 +1,462 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+import sys
+import math as m
+import random
+
+import yaml
+
+from tqdm import tqdm
+
+try:
+ import git
+ has_git = True
+except:
+ has_git = False
+
+import torch
+from torch.optim.lr_scheduler import LambdaLR
+import torch.nn.functional as F
+
+from scipy.io import wavfile
+import numpy as np
+import pesq
+
+from data import SilkEnhancementSet
+from models import model_dict
+
+
+from utils.silk_features import load_inference_data
+from utils.misc import count_parameters, retain_grads, get_grad_norm, create_weights
+
+from losses.stft_loss import MRSTFTLoss, MRLogMelLoss
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('setup', type=str, help='setup yaml file')
+parser.add_argument('output', type=str, help='output path')
+parser.add_argument('--device', type=str, help='compute device', default=None)
+parser.add_argument('--initial-checkpoint', type=str, help='initial checkpoint', default=None)
+parser.add_argument('--testdata', type=str, help='path to features and signal for testing', default=None)
+parser.add_argument('--no-redirect', action='store_true', help='disables re-direction of stdout')
+
+args = parser.parse_args()
+
+
+torch.set_num_threads(4)
+
+with open(args.setup, 'r') as f:
+ setup = yaml.load(f.read(), yaml.FullLoader)
+
+checkpoint_prefix = 'checkpoint'
+output_prefix = 'output'
+setup_name = 'setup.yml'
+output_file='out.txt'
+
+
+# check model
+if not 'name' in setup['model']:
+ print(f'warning: did not find model entry in setup, using default PitchPostFilter')
+ model_name = 'pitchpostfilter'
+else:
+ model_name = setup['model']['name']
+
+# prepare output folder
+if os.path.exists(args.output):
+ print("warning: output folder exists")
+
+ reply = input('continue? (y/n): ')
+ while reply not in {'y', 'n'}:
+ reply = input('continue? (y/n): ')
+
+ if reply == 'n':
+ os._exit()
+else:
+ os.makedirs(args.output, exist_ok=True)
+
+checkpoint_dir = os.path.join(args.output, 'checkpoints')
+os.makedirs(checkpoint_dir, exist_ok=True)
+
+# add repo info to setup
+if has_git:
+ working_dir = os.path.split(__file__)[0]
+ try:
+ repo = git.Repo(working_dir, search_parent_directories=True)
+ setup['repo'] = dict()
+ hash = repo.head.object.hexsha
+ urls = list(repo.remote().urls)
+ is_dirty = repo.is_dirty()
+
+ if is_dirty:
+ print("warning: repo is dirty")
+
+ setup['repo']['hash'] = hash
+ setup['repo']['urls'] = urls
+ setup['repo']['dirty'] = is_dirty
+ except:
+ has_git = False
+
+# dump setup
+with open(os.path.join(args.output, setup_name), 'w') as f:
+ yaml.dump(setup, f)
+
+
+ref = None
+if args.testdata is not None:
+
+ testsignal, features, periods, numbits = load_inference_data(args.testdata, **setup['data'])
+
+ inference_test = True
+ inference_folder = os.path.join(args.output, 'inference_test')
+ os.makedirs(os.path.join(args.output, 'inference_test'), exist_ok=True)
+
+ try:
+ ref = np.fromfile(os.path.join(args.testdata, 'clean.s16'), dtype=np.int16)
+ except:
+ pass
+else:
+ inference_test = False
+
+# training parameters
+batch_size = setup['training']['batch_size']
+epochs = setup['training']['epochs']
+lr = setup['training']['lr']
+lr_decay_factor = setup['training']['lr_decay_factor']
+lr_gen = lr * setup['training']['gen_lr_reduction']
+lambda_feat = setup['training']['lambda_feat']
+lambda_reg = setup['training']['lambda_reg']
+adv_target = setup['training'].get('adv_target', 'target')
+
+# load training dataset
+data_config = setup['data']
+data = SilkEnhancementSet(setup['dataset'], **data_config)
+
+# load validation dataset if given
+if 'validation_dataset' in setup:
+ validation_data = SilkEnhancementSet(setup['validation_dataset'], **data_config)
+
+ validation_dataloader = torch.utils.data.DataLoader(validation_data, batch_size=batch_size, drop_last=True, num_workers=4)
+
+ run_validation = True
+else:
+ run_validation = False
+
+# create model
+model = model_dict[model_name](*setup['model']['args'], **setup['model']['kwargs'])
+
+# create discriminator
+disc_name = setup['discriminator']['name']
+disc = model_dict[disc_name](
+ *setup['discriminator']['args'], **setup['discriminator']['kwargs']
+)
+
+# set compute device
+if type(args.device) == type(None):
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+else:
+ device = torch.device(args.device)
+
+# dataloader
+dataloader = torch.utils.data.DataLoader(data, batch_size=batch_size, drop_last=True, shuffle=True, num_workers=4)
+
+# optimizer is introduced to trainable parameters
+parameters = [p for p in model.parameters() if p.requires_grad]
+optimizer = torch.optim.Adam(parameters, lr=lr_gen)
+
+# disc optimizer
+parameters = [p for p in disc.parameters() if p.requires_grad]
+optimizer_disc = torch.optim.Adam(parameters, lr=lr, betas=[0.5, 0.9])
+
+# learning rate scheduler
+scheduler = LambdaLR(optimizer=optimizer, lr_lambda=lambda x : 1 / (1 + lr_decay_factor * x))
+
+if args.initial_checkpoint is not None:
+ print(f"loading state dict from {args.initial_checkpoint}...")
+ chkpt = torch.load(args.initial_checkpoint, map_location=device)
+ model.load_state_dict(chkpt['state_dict'])
+
+ if 'disc_state_dict' in chkpt:
+ print(f"loading discriminator state dict from {args.initial_checkpoint}...")
+ disc.load_state_dict(chkpt['disc_state_dict'])
+
+ if 'optimizer_state_dict' in chkpt:
+ print(f"loading optimizer state dict from {args.initial_checkpoint}...")
+ optimizer.load_state_dict(chkpt['optimizer_state_dict'])
+
+ if 'disc_optimizer_state_dict' in chkpt:
+ print(f"loading discriminator optimizer state dict from {args.initial_checkpoint}...")
+ optimizer_disc.load_state_dict(chkpt['disc_optimizer_state_dict'])
+
+ if 'scheduler_state_disc' in chkpt:
+ print(f"loading scheduler state dict from {args.initial_checkpoint}...")
+ scheduler.load_state_dict(chkpt['scheduler_state_dict'])
+
+ # if 'torch_rng_state' in chkpt:
+ # print(f"setting torch RNG state from {args.initial_checkpoint}...")
+ # torch.set_rng_state(chkpt['torch_rng_state'])
+
+ if 'numpy_rng_state' in chkpt:
+ print(f"setting numpy RNG state from {args.initial_checkpoint}...")
+ np.random.set_state(chkpt['numpy_rng_state'])
+
+ if 'python_rng_state' in chkpt:
+ print(f"setting Python RNG state from {args.initial_checkpoint}...")
+ random.setstate(chkpt['python_rng_state'])
+
+# loss
+w_l1 = setup['training']['loss']['w_l1']
+w_lm = setup['training']['loss']['w_lm']
+w_slm = setup['training']['loss']['w_slm']
+w_sc = setup['training']['loss']['w_sc']
+w_logmel = setup['training']['loss']['w_logmel']
+w_wsc = setup['training']['loss']['w_wsc']
+w_xcorr = setup['training']['loss']['w_xcorr']
+w_sxcorr = setup['training']['loss']['w_sxcorr']
+w_l2 = setup['training']['loss']['w_l2']
+
+w_sum = w_l1 + w_lm + w_sc + w_logmel + w_wsc + w_slm + w_xcorr + w_sxcorr + w_l2
+
+stftloss = MRSTFTLoss(sc_weight=w_sc, log_mag_weight=w_lm, wsc_weight=w_wsc, smooth_log_mag_weight=w_slm, sxcorr_weight=w_sxcorr).to(device)
+logmelloss = MRLogMelLoss().to(device)
+
+def xcorr_loss(y_true, y_pred):
+ dims = list(range(1, len(y_true.shape)))
+
+ loss = 1 - torch.sum(y_true * y_pred, dim=dims) / torch.sqrt(torch.sum(y_true ** 2, dim=dims) * torch.sum(y_pred ** 2, dim=dims) + 1e-9)
+
+ return torch.mean(loss)
+
+def td_l2_norm(y_true, y_pred):
+ dims = list(range(1, len(y_true.shape)))
+
+ loss = torch.mean((y_true - y_pred) ** 2, dim=dims) / (torch.mean(y_pred ** 2, dim=dims) ** .5 + 1e-6)
+
+ return loss.mean()
+
+def td_l1(y_true, y_pred, pow=0):
+ dims = list(range(1, len(y_true.shape)))
+ tmp = torch.mean(torch.abs(y_true - y_pred), dim=dims) / ((torch.mean(torch.abs(y_pred), dim=dims) + 1e-9) ** pow)
+
+ return torch.mean(tmp)
+
+def criterion(x, y):
+
+ return (w_l1 * td_l1(x, y, pow=1) + stftloss(x, y) + w_logmel * logmelloss(x, y)
+ + w_xcorr * xcorr_loss(x, y) + w_l2 * td_l2_norm(x, y)) / w_sum
+
+
+# model checkpoint
+checkpoint = {
+ 'setup' : setup,
+ 'state_dict' : model.state_dict(),
+ 'loss' : -1
+}
+
+
+if not args.no_redirect:
+ print(f"re-directing output to {os.path.join(args.output, output_file)}")
+ sys.stdout = open(os.path.join(args.output, output_file), "w")
+
+
+print("summary:")
+
+print(f"generator: {count_parameters(model.cpu()) / 1e6:5.3f} M parameters")
+if hasattr(model, 'flop_count'):
+ print(f"generator: {model.flop_count(16000) / 1e6:5.3f} MFLOPS")
+print(f"discriminator: {count_parameters(disc.cpu()) / 1e6:5.3f} M parameters")
+
+if ref is not None:
+ noisy = np.fromfile(os.path.join(args.testdata, 'noisy.s16'), dtype=np.int16)
+ initial_mos = pesq.pesq(16000, ref, noisy, mode='wb')
+ print(f"initial MOS (PESQ): {initial_mos}")
+
+best_loss = 1e9
+log_interval = 10
+
+
+m_r = 0
+m_f = 0
+s_r = 1
+s_f = 1
+
+def optimizer_to(optim, device):
+ for param in optim.state.values():
+ if isinstance(param, torch.Tensor):
+ param.data = param.data.to(device)
+ if param._grad is not None:
+ param._grad.data = param._grad.data.to(device)
+ elif isinstance(param, dict):
+ for subparam in param.values():
+ if isinstance(subparam, torch.Tensor):
+ subparam.data = subparam.data.to(device)
+ if subparam._grad is not None:
+ subparam._grad.data = subparam._grad.data.to(device)
+
+optimizer_to(optimizer, device)
+optimizer_to(optimizer_disc, device)
+
+retain_grads(model)
+retain_grads(disc)
+
+for ep in range(1, epochs + 1):
+ print(f"training epoch {ep}...")
+
+ model.to(device)
+ disc.to(device)
+ model.train()
+ disc.train()
+
+ running_disc_loss = 0
+ running_adv_loss = 0
+ running_feature_loss = 0
+ running_reg_loss = 0
+ running_disc_grad_norm = 0
+ running_model_grad_norm = 0
+
+ with tqdm(dataloader, unit='batch', file=sys.stdout) as tepoch:
+ for i, batch in enumerate(tepoch):
+
+ # set gradients to zero
+ optimizer.zero_grad()
+
+ # push batch to device
+ for key in batch:
+ batch[key] = batch[key].to(device)
+
+ target = batch['target'].to(device)
+ disc_target = batch[adv_target].to(device)
+
+ # calculate model output
+ output = model(batch['signals'].permute(0, 2, 1), batch['features'], batch['periods'], batch['numbits'])
+
+ # discriminator update
+ scores_gen = disc(output.detach())
+ scores_real = disc(disc_target.unsqueeze(1))
+
+ disc_loss = 0
+ for score in scores_gen:
+ disc_loss += (((score[-1]) ** 2)).mean()
+ m_f = 0.9 * m_f + 0.1 * score[-1].detach().mean().cpu().item()
+ s_f = 0.9 * s_f + 0.1 * score[-1].detach().std().cpu().item()
+
+ for score in scores_real:
+ disc_loss += (((1 - score[-1]) ** 2)).mean()
+ m_r = 0.9 * m_r + 0.1 * score[-1].detach().mean().cpu().item()
+ s_r = 0.9 * s_r + 0.1 * score[-1].detach().std().cpu().item()
+
+ disc_loss = 0.5 * disc_loss / len(scores_gen)
+ winning_chance = 0.5 * m.erfc( (m_r - m_f) / m.sqrt(2 * (s_f**2 + s_r**2)) )
+
+ disc.zero_grad()
+ disc_loss.backward()
+
+ running_disc_grad_norm += get_grad_norm(disc).detach().cpu().item()
+
+ optimizer_disc.step()
+
+ # generator update
+ scores_gen = disc(output)
+
+ # calculate loss
+ loss_reg = criterion(output.squeeze(1), target)
+
+ num_discs = len(scores_gen)
+ gen_loss = 0
+ for score in scores_gen:
+ gen_loss += (((1 - score[-1]) ** 2)).mean() / num_discs
+
+ loss_feat = 0
+ for k in range(num_discs):
+ num_layers = len(scores_gen[k]) - 1
+ f = 4 / num_discs / num_layers
+ for l in range(num_layers):
+ loss_feat += f * F.l1_loss(scores_gen[k][l], scores_real[k][l].detach())
+
+ model.zero_grad()
+
+ (gen_loss + lambda_feat * loss_feat + lambda_reg * loss_reg).backward()
+
+ optimizer.step()
+
+ # sparsification
+ if hasattr(model, 'sparsifier'):
+ model.sparsifier()
+
+ running_model_grad_norm += get_grad_norm(model).detach().cpu().item()
+ running_adv_loss += gen_loss.detach().cpu().item()
+ running_disc_loss += disc_loss.detach().cpu().item()
+ running_feature_loss += lambda_feat * loss_feat.detach().cpu().item()
+ running_reg_loss += lambda_reg * loss_reg.detach().cpu().item()
+
+ # update status bar
+ if i % log_interval == 0:
+ tepoch.set_postfix(adv_loss=f"{running_adv_loss/(i + 1):8.7f}",
+ disc_loss=f"{running_disc_loss/(i + 1):8.7f}",
+ feat_loss=f"{running_feature_loss/(i + 1):8.7f}",
+ reg_loss=f"{running_reg_loss/(i + 1):8.7f}",
+ model_gradnorm=f"{running_model_grad_norm/(i+1):8.7f}",
+ disc_gradnorm=f"{running_disc_grad_norm/(i+1):8.7f}",
+ wc=f"{100*winning_chance:5.2f}%")
+
+
+ # save checkpoint
+ checkpoint['state_dict'] = model.state_dict()
+ checkpoint['disc_state_dict'] = disc.state_dict()
+ checkpoint['optimizer_state_dict'] = optimizer.state_dict()
+ checkpoint['disc_optimizer_state_dict'] = optimizer_disc.state_dict()
+ checkpoint['scheduler_state_dict'] = scheduler.state_dict()
+ checkpoint['torch_rng_state'] = torch.get_rng_state()
+ checkpoint['numpy_rng_state'] = np.random.get_state()
+ checkpoint['python_rng_state'] = random.getstate()
+ checkpoint['adv_loss'] = running_adv_loss/(i + 1)
+ checkpoint['disc_loss'] = running_disc_loss/(i + 1)
+ checkpoint['feature_loss'] = running_feature_loss/(i + 1)
+ checkpoint['reg_loss'] = running_reg_loss/(i + 1)
+
+
+ if inference_test:
+ print("running inference test...")
+ out = model.process(testsignal, features, periods, numbits).cpu().numpy()
+ wavfile.write(os.path.join(inference_folder, f'{model_name}_epoch_{ep}.wav'), 16000, out)
+ if ref is not None:
+ mos = pesq.pesq(16000, ref, out, mode='wb')
+ print(f"MOS (PESQ): {mos}")
+
+
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_epoch_{ep}.pth'))
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_last.pth'))
+
+
+ print()
+
+print('Done')
diff --git a/dnn/torch/osce/adv_train_vocoder.py b/dnn/torch/osce/adv_train_vocoder.py
new file mode 100644
index 00000000..73e3c9b0
--- /dev/null
+++ b/dnn/torch/osce/adv_train_vocoder.py
@@ -0,0 +1,451 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+import sys
+import math as m
+import random
+
+import yaml
+
+from tqdm import tqdm
+
+try:
+ import git
+ has_git = True
+except:
+ has_git = False
+
+import torch
+from torch.optim.lr_scheduler import LambdaLR
+import torch.nn.functional as F
+
+from scipy.io import wavfile
+import numpy as np
+import pesq
+
+from data import LPCNetVocodingDataset
+from models import model_dict
+
+
+from utils.lpcnet_features import load_lpcnet_features
+from utils.misc import count_parameters
+
+from losses.stft_loss import MRSTFTLoss, MRLogMelLoss
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('setup', type=str, help='setup yaml file')
+parser.add_argument('output', type=str, help='output path')
+parser.add_argument('--device', type=str, help='compute device', default=None)
+parser.add_argument('--initial-checkpoint', type=str, help='initial checkpoint', default=None)
+parser.add_argument('--test-features', type=str, help='path to features for testing', default=None)
+parser.add_argument('--no-redirect', action='store_true', help='disables re-direction of stdout')
+
+args = parser.parse_args()
+
+
+torch.set_num_threads(4)
+
+with open(args.setup, 'r') as f:
+ setup = yaml.load(f.read(), yaml.FullLoader)
+
+checkpoint_prefix = 'checkpoint'
+output_prefix = 'output'
+setup_name = 'setup.yml'
+output_file='out.txt'
+
+
+# check model
+if not 'name' in setup['model']:
+ print(f'warning: did not find model entry in setup, using default PitchPostFilter')
+ model_name = 'pitchpostfilter'
+else:
+ model_name = setup['model']['name']
+
+# prepare output folder
+if os.path.exists(args.output):
+ print("warning: output folder exists")
+
+ reply = input('continue? (y/n): ')
+ while reply not in {'y', 'n'}:
+ reply = input('continue? (y/n): ')
+
+ if reply == 'n':
+ os._exit()
+else:
+ os.makedirs(args.output, exist_ok=True)
+
+checkpoint_dir = os.path.join(args.output, 'checkpoints')
+os.makedirs(checkpoint_dir, exist_ok=True)
+
+# add repo info to setup
+if has_git:
+ working_dir = os.path.split(__file__)[0]
+ try:
+ repo = git.Repo(working_dir, search_parent_directories=True)
+ setup['repo'] = dict()
+ hash = repo.head.object.hexsha
+ urls = list(repo.remote().urls)
+ is_dirty = repo.is_dirty()
+
+ if is_dirty:
+ print("warning: repo is dirty")
+
+ setup['repo']['hash'] = hash
+ setup['repo']['urls'] = urls
+ setup['repo']['dirty'] = is_dirty
+ except:
+ has_git = False
+
+# dump setup
+with open(os.path.join(args.output, setup_name), 'w') as f:
+ yaml.dump(setup, f)
+
+
+ref = None
+# prepare inference test if wanted
+inference_test = False
+if type(args.test_features) != type(None):
+ test_features = load_lpcnet_features(args.test_features)
+ features = test_features['features']
+ periods = test_features['periods']
+ inference_folder = os.path.join(args.output, 'inference_test')
+ os.makedirs(inference_folder, exist_ok=True)
+ inference_test = True
+
+
+# training parameters
+batch_size = setup['training']['batch_size']
+epochs = setup['training']['epochs']
+lr = setup['training']['lr']
+lr_decay_factor = setup['training']['lr_decay_factor']
+lr_gen = lr * setup['training']['gen_lr_reduction']
+lambda_feat = setup['training']['lambda_feat']
+lambda_reg = setup['training']['lambda_reg']
+adv_target = setup['training'].get('adv_target', 'target')
+
+
+# load training dataset
+data_config = setup['data']
+data = LPCNetVocodingDataset(setup['dataset'], **data_config)
+
+# load validation dataset if given
+if 'validation_dataset' in setup:
+ validation_data = LPCNetVocodingDataset(setup['validation_dataset'], **data_config)
+
+ validation_dataloader = torch.utils.data.DataLoader(validation_data, batch_size=batch_size, drop_last=True, num_workers=4)
+
+ run_validation = True
+else:
+ run_validation = False
+
+# create model
+model = model_dict[model_name](*setup['model']['args'], **setup['model']['kwargs'])
+
+
+# create discriminator
+disc_name = setup['discriminator']['name']
+disc = model_dict[disc_name](
+ *setup['discriminator']['args'], **setup['discriminator']['kwargs']
+)
+
+
+
+# set compute device
+if type(args.device) == type(None):
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+else:
+ device = torch.device(args.device)
+
+
+
+# dataloader
+dataloader = torch.utils.data.DataLoader(data, batch_size=batch_size, drop_last=True, shuffle=True, num_workers=4)
+
+# optimizer is introduced to trainable parameters
+parameters = [p for p in model.parameters() if p.requires_grad]
+optimizer = torch.optim.Adam(parameters, lr=lr_gen)
+
+# disc optimizer
+parameters = [p for p in disc.parameters() if p.requires_grad]
+optimizer_disc = torch.optim.Adam(parameters, lr=lr, betas=[0.5, 0.9])
+
+# learning rate scheduler
+scheduler = LambdaLR(optimizer=optimizer, lr_lambda=lambda x : 1 / (1 + lr_decay_factor * x))
+
+if args.initial_checkpoint is not None:
+ print(f"loading state dict from {args.initial_checkpoint}...")
+ chkpt = torch.load(args.initial_checkpoint, map_location=device)
+ model.load_state_dict(chkpt['state_dict'])
+
+ if 'disc_state_dict' in chkpt:
+ print(f"loading discriminator state dict from {args.initial_checkpoint}...")
+ disc.load_state_dict(chkpt['disc_state_dict'])
+
+ if 'optimizer_state_dict' in chkpt:
+ print(f"loading optimizer state dict from {args.initial_checkpoint}...")
+ optimizer.load_state_dict(chkpt['optimizer_state_dict'])
+
+ if 'disc_optimizer_state_dict' in chkpt:
+ print(f"loading discriminator optimizer state dict from {args.initial_checkpoint}...")
+ optimizer_disc.load_state_dict(chkpt['disc_optimizer_state_dict'])
+
+ if 'scheduler_state_disc' in chkpt:
+ print(f"loading scheduler state dict from {args.initial_checkpoint}...")
+ scheduler.load_state_dict(chkpt['scheduler_state_dict'])
+
+ # if 'torch_rng_state' in chkpt:
+ # print(f"setting torch RNG state from {args.initial_checkpoint}...")
+ # torch.set_rng_state(chkpt['torch_rng_state'])
+
+ if 'numpy_rng_state' in chkpt:
+ print(f"setting numpy RNG state from {args.initial_checkpoint}...")
+ np.random.set_state(chkpt['numpy_rng_state'])
+
+ if 'python_rng_state' in chkpt:
+ print(f"setting Python RNG state from {args.initial_checkpoint}...")
+ random.setstate(chkpt['python_rng_state'])
+
+# loss
+w_l1 = setup['training']['loss']['w_l1']
+w_lm = setup['training']['loss']['w_lm']
+w_slm = setup['training']['loss']['w_slm']
+w_sc = setup['training']['loss']['w_sc']
+w_logmel = setup['training']['loss']['w_logmel']
+w_wsc = setup['training']['loss']['w_wsc']
+w_xcorr = setup['training']['loss']['w_xcorr']
+w_sxcorr = setup['training']['loss']['w_sxcorr']
+w_l2 = setup['training']['loss']['w_l2']
+
+w_sum = w_l1 + w_lm + w_sc + w_logmel + w_wsc + w_slm + w_xcorr + w_sxcorr + w_l2
+
+stftloss = MRSTFTLoss(sc_weight=w_sc, log_mag_weight=w_lm, wsc_weight=w_wsc, smooth_log_mag_weight=w_slm, sxcorr_weight=w_sxcorr).to(device)
+logmelloss = MRLogMelLoss().to(device)
+
+def xcorr_loss(y_true, y_pred):
+ dims = list(range(1, len(y_true.shape)))
+
+ loss = 1 - torch.sum(y_true * y_pred, dim=dims) / torch.sqrt(torch.sum(y_true ** 2, dim=dims) * torch.sum(y_pred ** 2, dim=dims) + 1e-9)
+
+ return torch.mean(loss)
+
+def td_l2_norm(y_true, y_pred):
+ dims = list(range(1, len(y_true.shape)))
+
+ loss = torch.mean((y_true - y_pred) ** 2, dim=dims) / (torch.mean(y_pred ** 2, dim=dims) ** .5 + 1e-6)
+
+ return loss.mean()
+
+def td_l1(y_true, y_pred, pow=0):
+ dims = list(range(1, len(y_true.shape)))
+ tmp = torch.mean(torch.abs(y_true - y_pred), dim=dims) / ((torch.mean(torch.abs(y_pred), dim=dims) + 1e-9) ** pow)
+
+ return torch.mean(tmp)
+
+def criterion(x, y):
+
+ return (w_l1 * td_l1(x, y, pow=1) + stftloss(x, y) + w_logmel * logmelloss(x, y)
+ + w_xcorr * xcorr_loss(x, y) + w_l2 * td_l2_norm(x, y)) / w_sum
+
+
+# model checkpoint
+checkpoint = {
+ 'setup' : setup,
+ 'state_dict' : model.state_dict(),
+ 'loss' : -1
+}
+
+
+if not args.no_redirect:
+ print(f"re-directing output to {os.path.join(args.output, output_file)}")
+ sys.stdout = open(os.path.join(args.output, output_file), "w")
+
+
+print("summary:")
+
+print(f"generator: {count_parameters(model.cpu()) / 1e6:5.3f} M parameters")
+if hasattr(model, 'flop_count'):
+ print(f"generator: {model.flop_count(16000) / 1e6:5.3f} MFLOPS")
+print(f"discriminator: {count_parameters(disc.cpu()) / 1e6:5.3f} M parameters")
+
+if ref is not None:
+ noisy = np.fromfile(os.path.join(args.testdata, 'noisy.s16'), dtype=np.int16)
+ initial_mos = pesq.pesq(16000, ref, noisy, mode='wb')
+ print(f"initial MOS (PESQ): {initial_mos}")
+
+best_loss = 1e9
+log_interval = 10
+
+
+m_r = 0
+m_f = 0
+s_r = 1
+s_f = 1
+
+def optimizer_to(optim, device):
+ for param in optim.state.values():
+ if isinstance(param, torch.Tensor):
+ param.data = param.data.to(device)
+ if param._grad is not None:
+ param._grad.data = param._grad.data.to(device)
+ elif isinstance(param, dict):
+ for subparam in param.values():
+ if isinstance(subparam, torch.Tensor):
+ subparam.data = subparam.data.to(device)
+ if subparam._grad is not None:
+ subparam._grad.data = subparam._grad.data.to(device)
+
+optimizer_to(optimizer, device)
+optimizer_to(optimizer_disc, device)
+
+
+for ep in range(1, epochs + 1):
+ print(f"training epoch {ep}...")
+
+ model.to(device)
+ disc.to(device)
+ model.train()
+ disc.train()
+
+ running_disc_loss = 0
+ running_adv_loss = 0
+ running_feature_loss = 0
+ running_reg_loss = 0
+
+ with tqdm(dataloader, unit='batch', file=sys.stdout) as tepoch:
+ for i, batch in enumerate(tepoch):
+
+ # set gradients to zero
+ optimizer.zero_grad()
+
+ # push batch to device
+ for key in batch:
+ batch[key] = batch[key].to(device)
+
+ target = batch['target'].to(device)
+ disc_target = batch[adv_target].to(device)
+
+ # calculate model output
+ output = model(batch['features'], batch['periods'])
+
+ # discriminator update
+ scores_gen = disc(output.detach())
+ scores_real = disc(disc_target.unsqueeze(1))
+
+ disc_loss = 0
+ for scale in scores_gen:
+ disc_loss += ((scale[-1]) ** 2).mean()
+ m_f = 0.9 * m_f + 0.1 * scale[-1].detach().mean().cpu().item()
+ s_f = 0.9 * s_f + 0.1 * scale[-1].detach().std().cpu().item()
+
+ for scale in scores_real:
+ disc_loss += ((1 - scale[-1]) ** 2).mean()
+ m_r = 0.9 * m_r + 0.1 * scale[-1].detach().mean().cpu().item()
+ s_r = 0.9 * s_r + 0.1 * scale[-1].detach().std().cpu().item()
+
+ disc_loss = 0.5 * disc_loss / len(scores_gen)
+ winning_chance = 0.5 * m.erfc( (m_r - m_f) / m.sqrt(2 * (s_f**2 + s_r**2)) )
+
+ disc.zero_grad()
+ disc_loss.backward()
+ optimizer_disc.step()
+
+ # generator update
+ scores_gen = disc(output)
+
+
+ # calculate loss
+ loss_reg = criterion(output.squeeze(1), target)
+
+ num_discs = len(scores_gen)
+ loss_gen = 0
+ for scale in scores_gen:
+ loss_gen += ((1 - scale[-1]) ** 2).mean() / num_discs
+
+ loss_feat = 0
+ for k in range(num_discs):
+ num_layers = len(scores_gen[k]) - 1
+ f = 4 / num_discs / num_layers
+ for l in range(num_layers):
+ loss_feat += f * F.l1_loss(scores_gen[k][l], scores_real[k][l].detach())
+
+ model.zero_grad()
+
+ (loss_gen + lambda_feat * loss_feat + lambda_reg * loss_reg).backward()
+
+ optimizer.step()
+
+ running_adv_loss += loss_gen.detach().cpu().item()
+ running_disc_loss += disc_loss.detach().cpu().item()
+ running_feature_loss += lambda_feat * loss_feat.detach().cpu().item()
+ running_reg_loss += lambda_reg * loss_reg.detach().cpu().item()
+
+ # update status bar
+ if i % log_interval == 0:
+ tepoch.set_postfix(adv_loss=f"{running_adv_loss/(i + 1):8.7f}",
+ disc_loss=f"{running_disc_loss/(i + 1):8.7f}",
+ feat_loss=f"{running_feature_loss/(i + 1):8.7f}",
+ reg_loss=f"{running_reg_loss/(i + 1):8.7f}",
+ wc=f"{100*winning_chance:5.2f}%")
+
+
+ # save checkpoint
+ checkpoint['state_dict'] = model.state_dict()
+ checkpoint['disc_state_dict'] = disc.state_dict()
+ checkpoint['optimizer_state_dict'] = optimizer.state_dict()
+ checkpoint['disc_optimizer_state_dict'] = optimizer_disc.state_dict()
+ checkpoint['scheduler_state_dict'] = scheduler.state_dict()
+ checkpoint['torch_rng_state'] = torch.get_rng_state()
+ checkpoint['numpy_rng_state'] = np.random.get_state()
+ checkpoint['python_rng_state'] = random.getstate()
+ checkpoint['adv_loss'] = running_adv_loss/(i + 1)
+ checkpoint['disc_loss'] = running_disc_loss/(i + 1)
+ checkpoint['feature_loss'] = running_feature_loss/(i + 1)
+ checkpoint['reg_loss'] = running_reg_loss/(i + 1)
+
+
+ if inference_test:
+ print("running inference test...")
+ out = model.process(features, periods).cpu().numpy()
+ wavfile.write(os.path.join(inference_folder, f'{model_name}_epoch_{ep}.wav'), 16000, out)
+ if ref is not None:
+ mos = pesq.pesq(16000, ref, out, mode='wb')
+ print(f"MOS (PESQ): {mos}")
+
+
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_epoch_{ep}.pth'))
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_last.pth'))
+
+
+ print()
+
+print('Done')
diff --git a/dnn/torch/osce/create_testvectors.py b/dnn/torch/osce/create_testvectors.py
new file mode 100644
index 00000000..a037d0db
--- /dev/null
+++ b/dnn/torch/osce/create_testvectors.py
@@ -0,0 +1,165 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+
+import torch
+import numpy as np
+
+from models import model_dict
+from utils import endoscopy
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('checkpoint_path', type=str, help='path to folder containing checkpoints "lace_checkpoint.pth" and nolace_checkpoint.pth"')
+parser.add_argument('output_folder', type=str, help='output folder for testvectors')
+parser.add_argument('--debug', action='store_true', help='add debug output to output folder')
+
+
+def create_adaconv_testvector(prefix, adaconv, num_frames, debug=False):
+ feature_dim = adaconv.feature_dim
+ in_channels = adaconv.in_channels
+ out_channels = adaconv.out_channels
+ frame_size = adaconv.frame_size
+
+ features = torch.randn((1, num_frames, feature_dim))
+ x_in = torch.randn((1, in_channels, num_frames * frame_size))
+
+ x_out = adaconv(x_in, features, debug=debug)
+
+ features = features[0].detach().numpy()
+ x_in = x_in[0].reshape(in_channels, num_frames, frame_size).permute(1, 0, 2).detach().numpy()
+ x_out = x_out[0].reshape(out_channels, num_frames, frame_size).permute(1, 0, 2).detach().numpy()
+
+ features.tofile(prefix + '_features.f32')
+ x_in.tofile(prefix + '_x_in.f32')
+ x_out.tofile(prefix + '_x_out.f32')
+
+def create_adacomb_testvector(prefix, adacomb, num_frames, debug=False):
+ feature_dim = adacomb.feature_dim
+ in_channels = 1
+ frame_size = adacomb.frame_size
+
+ features = torch.randn((1, num_frames, feature_dim))
+ x_in = torch.randn((1, in_channels, num_frames * frame_size))
+ p_in = torch.randint(adacomb.kernel_size, 250, (1, num_frames))
+
+ x_out = adacomb(x_in, features, p_in, debug=debug)
+
+ features = features[0].detach().numpy()
+ x_in = x_in[0].permute(1, 0).detach().numpy()
+ p_in = p_in[0].detach().numpy().astype(np.int32)
+ x_out = x_out[0].permute(1, 0).detach().numpy()
+
+ features.tofile(prefix + '_features.f32')
+ x_in.tofile(prefix + '_x_in.f32')
+ p_in.tofile(prefix + '_p_in.s32')
+ x_out.tofile(prefix + '_x_out.f32')
+
+def create_adashape_testvector(prefix, adashape, num_frames):
+ feature_dim = adashape.feature_dim
+ frame_size = adashape.frame_size
+
+ features = torch.randn((1, num_frames, feature_dim))
+ x_in = torch.randn((1, 1, num_frames * frame_size))
+
+ x_out = adashape(x_in, features)
+
+ features = features[0].detach().numpy()
+ x_in = x_in.flatten().detach().numpy()
+ x_out = x_out.flatten().detach().numpy()
+
+ features.tofile(prefix + '_features.f32')
+ x_in.tofile(prefix + '_x_in.f32')
+ x_out.tofile(prefix + '_x_out.f32')
+
+def create_feature_net_testvector(prefix, model, num_frames):
+ num_features = model.num_features
+ num_subframes = 4 * num_frames
+
+ input_features = torch.randn((1, num_subframes, num_features))
+ periods = torch.randint(32, 300, (1, num_subframes))
+ numbits = model.numbits_range[0] + torch.rand((1, num_frames, 2)) * (model.numbits_range[1] - model.numbits_range[0])
+
+
+ pembed = model.pitch_embedding(periods)
+ nembed = torch.repeat_interleave(model.numbits_embedding(numbits).flatten(2), 4, dim=1)
+ full_features = torch.cat((input_features, pembed, nembed), dim=-1)
+
+ cf = model.feature_net(full_features)
+
+ input_features.float().numpy().tofile(prefix + "_in_features.f32")
+ periods.numpy().astype(np.int32).tofile(prefix + "_periods.s32")
+ numbits.float().numpy().tofile(prefix + "_numbits.f32")
+ full_features.detach().numpy().tofile(prefix + "_full_features.f32")
+ cf.detach().numpy().tofile(prefix + "_out_features.f32")
+
+
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+ os.makedirs(args.output_folder, exist_ok=True)
+
+ lace_checkpoint = torch.load(os.path.join(args.checkpoint_path, "lace_checkpoint.pth"), map_location='cpu')
+ nolace_checkpoint = torch.load(os.path.join(args.checkpoint_path, "nolace_checkpoint.pth"), map_location='cpu')
+
+ lace = model_dict['lace'](**lace_checkpoint['setup']['model']['kwargs'])
+ nolace = model_dict['nolace'](**nolace_checkpoint['setup']['model']['kwargs'])
+
+ lace.load_state_dict(lace_checkpoint['state_dict'])
+ nolace.load_state_dict(nolace_checkpoint['state_dict'])
+
+ if args.debug:
+ endoscopy.init(args.output_folder)
+
+ # lace af1, 1 input channel, 1 output channel
+ create_adaconv_testvector(os.path.join(args.output_folder, "lace_af1"), lace.af1, 5, debug=args.debug)
+
+ # nolace af1, 1 input channel, 2 output channels
+ create_adaconv_testvector(os.path.join(args.output_folder, "nolace_af1"), nolace.af1, 5, debug=args.debug)
+
+ # nolace af4, 2 input channel, 1 output channels
+ create_adaconv_testvector(os.path.join(args.output_folder, "nolace_af4"), nolace.af4, 5, debug=args.debug)
+
+ # nolace af2, 2 input channel, 2 output channels
+ create_adaconv_testvector(os.path.join(args.output_folder, "nolace_af2"), nolace.af2, 5, debug=args.debug)
+
+ # lace cf1
+ create_adacomb_testvector(os.path.join(args.output_folder, "lace_cf1"), lace.cf1, 5, debug=args.debug)
+
+ # nolace tdshape1
+ create_adashape_testvector(os.path.join(args.output_folder, "nolace_tdshape1"), nolace.tdshape1, 5)
+
+ # lace feature net
+ create_feature_net_testvector(os.path.join(args.output_folder, 'lace'), lace, 5)
+
+ if args.debug:
+ endoscopy.close()
diff --git a/dnn/torch/osce/data/__init__.py b/dnn/torch/osce/data/__init__.py
new file mode 100644
index 00000000..8df4d56a
--- /dev/null
+++ b/dnn/torch/osce/data/__init__.py
@@ -0,0 +1,2 @@
+from .silk_enhancement_set import SilkEnhancementSet
+from .lpcnet_vocoding_dataset import LPCNetVocodingDataset \ No newline at end of file
diff --git a/dnn/torch/osce/data/lpcnet_vocoding_dataset.py b/dnn/torch/osce/data/lpcnet_vocoding_dataset.py
new file mode 100644
index 00000000..36c8c724
--- /dev/null
+++ b/dnn/torch/osce/data/lpcnet_vocoding_dataset.py
@@ -0,0 +1,225 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+""" Dataset for LPCNet training """
+import os
+
+import yaml
+import torch
+import numpy as np
+from torch.utils.data import Dataset
+
+
+scale = 255.0/32768.0
+scale_1 = 32768.0/255.0
+def ulaw2lin(u):
+ u = u - 128
+ s = np.sign(u)
+ u = np.abs(u)
+ return s*scale_1*(np.exp(u/128.*np.log(256))-1)
+
+
+def lin2ulaw(x):
+ s = np.sign(x)
+ x = np.abs(x)
+ u = (s*(128*np.log(1+scale*x)/np.log(256)))
+ u = np.clip(128 + np.round(u), 0, 255)
+ return u
+
+
+def run_lpc(signal, lpcs, frame_length=160):
+ num_frames, lpc_order = lpcs.shape
+
+ prediction = np.concatenate(
+ [- np.convolve(signal[i * frame_length : (i + 1) * frame_length + lpc_order - 1], lpcs[i], mode='valid') for i in range(num_frames)]
+ )
+ error = signal[lpc_order :] - prediction
+
+ return prediction, error
+
+class LPCNetVocodingDataset(Dataset):
+ def __init__(self,
+ path_to_dataset,
+ features=['cepstrum', 'periods', 'pitch_corr'],
+ target='signal',
+ frames_per_sample=100,
+ feature_history=0,
+ feature_lookahead=0,
+ lpc_gamma=1):
+
+ super().__init__()
+
+ # load dataset info
+ self.path_to_dataset = path_to_dataset
+ with open(os.path.join(path_to_dataset, 'info.yml'), 'r') as f:
+ dataset = yaml.load(f, yaml.FullLoader)
+
+ # dataset version
+ self.version = dataset['version']
+ if self.version == 1:
+ self.getitem = self.getitem_v1
+ elif self.version == 2:
+ self.getitem = self.getitem_v2
+ else:
+ raise ValueError(f"dataset version {self.version} unknown")
+
+ # features
+ self.feature_history = feature_history
+ self.feature_lookahead = feature_lookahead
+ self.frame_offset = 2 + self.feature_history
+ self.frames_per_sample = frames_per_sample
+ self.input_features = features
+ self.feature_frame_layout = dataset['feature_frame_layout']
+ self.lpc_gamma = lpc_gamma
+
+ # load feature file
+ self.feature_file = os.path.join(path_to_dataset, dataset['feature_file'])
+ self.features = np.memmap(self.feature_file, dtype=dataset['feature_dtype'])
+ self.feature_frame_length = dataset['feature_frame_length']
+
+ assert len(self.features) % self.feature_frame_length == 0
+ self.features = self.features.reshape((-1, self.feature_frame_length))
+
+ # derive number of samples is dataset
+ self.dataset_length = (len(self.features) - self.frame_offset - self.feature_lookahead - 1 - 2) // self.frames_per_sample
+
+ # signals
+ self.frame_length = dataset['frame_length']
+ self.signal_frame_layout = dataset['signal_frame_layout']
+ self.target = target
+
+ # load signals
+ self.signal_file = os.path.join(path_to_dataset, dataset['signal_file'])
+ self.signals = np.memmap(self.signal_file, dtype=dataset['signal_dtype'])
+ self.signal_frame_length = dataset['signal_frame_length']
+ self.signals = self.signals.reshape((-1, self.signal_frame_length))
+ assert len(self.signals) == len(self.features) * self.frame_length
+
+
+ def __getitem__(self, index):
+ return self.getitem(index)
+
+ def getitem_v2(self, index):
+ sample = dict()
+
+ # extract features
+ frame_start = self.frame_offset + index * self.frames_per_sample - self.feature_history
+ frame_stop = self.frame_offset + (index + 1) * self.frames_per_sample + self.feature_lookahead
+
+ for feature in self.input_features:
+ feature_start, feature_stop = self.feature_frame_layout[feature]
+ sample[feature] = self.features[frame_start : frame_stop, feature_start : feature_stop]
+
+ # convert periods
+ if 'periods' in self.input_features:
+ sample['periods'] = (0.1 + 50 * sample['periods'] + 100).astype('int16')
+
+ signal_start = (self.frame_offset + index * self.frames_per_sample) * self.frame_length
+ signal_stop = (self.frame_offset + (index + 1) * self.frames_per_sample) * self.frame_length
+
+ # last_signal and signal are always expected to be there
+ sample['last_signal'] = self.signals[signal_start : signal_stop, self.signal_frame_layout['last_signal']]
+ sample['signal'] = self.signals[signal_start : signal_stop, self.signal_frame_layout['signal']]
+
+ # calculate prediction and error if lpc coefficients present and prediction not given
+ if 'lpc' in self.feature_frame_layout and 'prediction' not in self.signal_frame_layout:
+ # lpc coefficients with one frame lookahead
+ # frame positions (start one frame early for past excitation)
+ frame_start = self.frame_offset + self.frames_per_sample * index - 1
+ frame_stop = self.frame_offset + self.frames_per_sample * (index + 1)
+
+ # feature positions
+ lpc_start, lpc_stop = self.feature_frame_layout['lpc']
+ lpc_order = lpc_stop - lpc_start
+ lpcs = self.features[frame_start : frame_stop, lpc_start : lpc_stop]
+
+ # LPC weighting
+ lpc_order = lpc_stop - lpc_start
+ weights = np.array([self.lpc_gamma ** (i + 1) for i in range(lpc_order)])
+ lpcs = lpcs * weights
+
+ # signal position (lpc_order samples as history)
+ signal_start = frame_start * self.frame_length - lpc_order + 1
+ signal_stop = frame_stop * self.frame_length + 1
+ noisy_signal = self.signals[signal_start : signal_stop, self.signal_frame_layout['last_signal']]
+ clean_signal = self.signals[signal_start - 1 : signal_stop - 1, self.signal_frame_layout['signal']]
+
+ noisy_prediction, noisy_error = run_lpc(noisy_signal, lpcs, frame_length=self.frame_length)
+
+ # extract signals
+ offset = self.frame_length
+ sample['prediction'] = noisy_prediction[offset : offset + self.frame_length * self.frames_per_sample]
+ sample['last_error'] = noisy_error[offset - 1 : offset - 1 + self.frame_length * self.frames_per_sample]
+ # calculate error between real signal and noisy prediction
+
+
+ sample['error'] = sample['signal'] - sample['prediction']
+
+
+ # concatenate features
+ feature_keys = [key for key in self.input_features if not key.startswith("periods")]
+ features = torch.concat([torch.FloatTensor(sample[key]) for key in feature_keys], dim=-1)
+ target = torch.FloatTensor(sample[self.target]) / 2**15
+ periods = torch.LongTensor(sample['periods'])
+
+ return {'features' : features, 'periods' : periods, 'target' : target}
+
+ def getitem_v1(self, index):
+ sample = dict()
+
+ # extract features
+ frame_start = self.frame_offset + index * self.frames_per_sample - self.feature_history
+ frame_stop = self.frame_offset + (index + 1) * self.frames_per_sample + self.feature_lookahead
+
+ for feature in self.input_features:
+ feature_start, feature_stop = self.feature_frame_layout[feature]
+ sample[feature] = self.features[frame_start : frame_stop, feature_start : feature_stop]
+
+ # convert periods
+ if 'periods' in self.input_features:
+ sample['periods'] = (0.1 + 50 * sample['periods'] + 100).astype('int16')
+
+ signal_start = (self.frame_offset + index * self.frames_per_sample) * self.frame_length
+ signal_stop = (self.frame_offset + (index + 1) * self.frames_per_sample) * self.frame_length
+
+ # last_signal and signal are always expected to be there
+ for signal_name, index in self.signal_frame_layout.items():
+ sample[signal_name] = self.signals[signal_start : signal_stop, index]
+
+ # concatenate features
+ feature_keys = [key for key in self.input_features if not key.startswith("periods")]
+ features = torch.concat([torch.FloatTensor(sample[key]) for key in feature_keys], dim=-1)
+ signals = torch.cat([torch.LongTensor(sample[key]).unsqueeze(-1) for key in self.input_signals], dim=-1)
+ target = torch.LongTensor(sample[self.target])
+ periods = torch.LongTensor(sample['periods'])
+
+ return {'features' : features, 'periods' : periods, 'signals' : signals, 'target' : target}
+
+ def __len__(self):
+ return self.dataset_length
diff --git a/dnn/torch/osce/data/silk_conversion_set.py b/dnn/torch/osce/data/silk_conversion_set.py
new file mode 100644
index 00000000..8f646756
--- /dev/null
+++ b/dnn/torch/osce/data/silk_conversion_set.py
@@ -0,0 +1,132 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+
+from torch.utils.data import Dataset
+import numpy as np
+
+from utils.silk_features import silk_feature_factory
+from utils.pitch import hangover, calculate_acorr_window
+
+
+class SilkEnhancementSet(Dataset):
+ def __init__(self,
+ path,
+ frames_per_sample=100,
+ no_pitch_value=9,
+ acorr_radius=2,
+ pitch_hangover=8,
+ num_bands_clean_spec=64,
+ num_bands_noisy_spec=18,
+ noisy_spec_scale='opus',
+ noisy_apply_dct=True,
+ add_offset=False,
+ add_double_lag_acorr=False
+ ):
+
+ assert frames_per_sample % 4 == 0
+
+ self.frame_size = 80
+ self.frames_per_sample = frames_per_sample
+ self.no_pitch_value = no_pitch_value
+ self.acorr_radius = acorr_radius
+ self.pitch_hangover = pitch_hangover
+ self.num_bands_clean_spec = num_bands_clean_spec
+ self.num_bands_noisy_spec = num_bands_noisy_spec
+ self.noisy_spec_scale = noisy_spec_scale
+ self.add_double_lag_acorr = add_double_lag_acorr
+
+ self.lpcs = np.fromfile(os.path.join(path, 'features_lpc.f32'), dtype=np.float32).reshape(-1, 16)
+ self.ltps = np.fromfile(os.path.join(path, 'features_ltp.f32'), dtype=np.float32).reshape(-1, 5)
+ self.periods = np.fromfile(os.path.join(path, 'features_period.s16'), dtype=np.int16)
+ self.gains = np.fromfile(os.path.join(path, 'features_gain.f32'), dtype=np.float32)
+ self.num_bits = np.fromfile(os.path.join(path, 'features_num_bits.s32'), dtype=np.int32)
+ self.num_bits_smooth = np.fromfile(os.path.join(path, 'features_num_bits_smooth.f32'), dtype=np.float32)
+ self.offsets = np.fromfile(os.path.join(path, 'features_offset.f32'), dtype=np.float32)
+ self.lpcnet_features = np.from_file(os.path.join(path, 'features_lpcnet.f32'), dtype=np.float32).reshape(-1, 36)
+
+ self.coded_signal = np.fromfile(os.path.join(path, 'coded.s16'), dtype=np.int16)
+
+ self.create_features = silk_feature_factory(no_pitch_value,
+ acorr_radius,
+ pitch_hangover,
+ num_bands_clean_spec,
+ num_bands_noisy_spec,
+ noisy_spec_scale,
+ noisy_apply_dct,
+ add_offset,
+ add_double_lag_acorr)
+
+ self.history_len = 700 if add_double_lag_acorr else 350
+ # discard some frames to have enough signal history
+ self.skip_frames = 4 * ((self.history_len + 319) // 320 + 2)
+
+ num_frames = self.clean_signal.shape[0] // 80 - self.skip_frames
+
+ self.len = num_frames // frames_per_sample
+
+ def __len__(self):
+ return self.len
+
+ def __getitem__(self, index):
+
+ frame_start = self.frames_per_sample * index + self.skip_frames
+ frame_stop = frame_start + self.frames_per_sample
+
+ signal_start = frame_start * self.frame_size - self.skip
+ signal_stop = frame_stop * self.frame_size - self.skip
+
+ coded_signal = self.coded_signal[signal_start : signal_stop].astype(np.float32) / 2**15
+
+ coded_signal_history = self.coded_signal[signal_start - self.history_len : signal_start].astype(np.float32) / 2**15
+
+ features, periods = self.create_features(
+ coded_signal,
+ coded_signal_history,
+ self.lpcs[frame_start : frame_stop],
+ self.gains[frame_start : frame_stop],
+ self.ltps[frame_start : frame_stop],
+ self.periods[frame_start : frame_stop],
+ self.offsets[frame_start : frame_stop]
+ )
+
+ lpcnet_features = self.lpcnet_features[frame_start // 2 : frame_stop // 2, :20]
+
+ num_bits = np.repeat(self.num_bits[frame_start // 4 : frame_stop // 4], 4).astype(np.float32).reshape(-1, 1)
+ num_bits_smooth = np.repeat(self.num_bits_smooth[frame_start // 4 : frame_stop // 4], 4).astype(np.float32).reshape(-1, 1)
+
+ numbits = np.concatenate((num_bits, num_bits_smooth), axis=-1)
+
+ return {
+ 'silk_features' : features,
+ 'periods' : periods.astype(np.int64),
+ 'numbits' : numbits.astype(np.float32),
+ 'lpcnet_features' : lpcnet_features
+ }
diff --git a/dnn/torch/osce/data/silk_enhancement_set.py b/dnn/torch/osce/data/silk_enhancement_set.py
new file mode 100644
index 00000000..fd18c4de
--- /dev/null
+++ b/dnn/torch/osce/data/silk_enhancement_set.py
@@ -0,0 +1,140 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+
+from torch.utils.data import Dataset
+import numpy as np
+
+from utils.silk_features import silk_feature_factory
+from utils.pitch import hangover, calculate_acorr_window
+
+
+class SilkEnhancementSet(Dataset):
+ def __init__(self,
+ path,
+ frames_per_sample=100,
+ no_pitch_value=256,
+ preemph=0.85,
+ skip=91,
+ acorr_radius=2,
+ pitch_hangover=8,
+ num_bands_clean_spec=64,
+ num_bands_noisy_spec=18,
+ noisy_spec_scale='opus',
+ noisy_apply_dct=True,
+ add_double_lag_acorr=False,
+ ):
+
+ assert frames_per_sample % 4 == 0
+
+ self.frame_size = 80
+ self.frames_per_sample = frames_per_sample
+ self.no_pitch_value = no_pitch_value
+ self.preemph = preemph
+ self.skip = skip
+ self.acorr_radius = acorr_radius
+ self.pitch_hangover = pitch_hangover
+ self.num_bands_clean_spec = num_bands_clean_spec
+ self.num_bands_noisy_spec = num_bands_noisy_spec
+ self.noisy_spec_scale = noisy_spec_scale
+ self.add_double_lag_acorr = add_double_lag_acorr
+
+ self.lpcs = np.fromfile(os.path.join(path, 'features_lpc.f32'), dtype=np.float32).reshape(-1, 16)
+ self.ltps = np.fromfile(os.path.join(path, 'features_ltp.f32'), dtype=np.float32).reshape(-1, 5)
+ self.periods = np.fromfile(os.path.join(path, 'features_period.s16'), dtype=np.int16)
+ self.gains = np.fromfile(os.path.join(path, 'features_gain.f32'), dtype=np.float32)
+ self.num_bits = np.fromfile(os.path.join(path, 'features_num_bits.s32'), dtype=np.int32)
+ self.num_bits_smooth = np.fromfile(os.path.join(path, 'features_num_bits_smooth.f32'), dtype=np.float32)
+
+ self.clean_signal_hp = np.fromfile(os.path.join(path, 'clean_hp.s16'), dtype=np.int16)
+ self.clean_signal = np.fromfile(os.path.join(path, 'clean.s16'), dtype=np.int16)
+ self.coded_signal = np.fromfile(os.path.join(path, 'coded.s16'), dtype=np.int16)
+
+ self.create_features = silk_feature_factory(no_pitch_value,
+ acorr_radius,
+ pitch_hangover,
+ num_bands_clean_spec,
+ num_bands_noisy_spec,
+ noisy_spec_scale,
+ noisy_apply_dct,
+ add_double_lag_acorr)
+
+ self.history_len = 700 if add_double_lag_acorr else 350
+ # discard some frames to have enough signal history
+ self.skip_frames = 4 * ((skip + self.history_len + 319) // 320 + 2)
+
+ num_frames = self.clean_signal_hp.shape[0] // 80 - self.skip_frames
+
+ self.len = num_frames // frames_per_sample
+
+ def __len__(self):
+ return self.len
+
+ def __getitem__(self, index):
+
+ frame_start = self.frames_per_sample * index + self.skip_frames
+ frame_stop = frame_start + self.frames_per_sample
+
+ signal_start = frame_start * self.frame_size - self.skip
+ signal_stop = frame_stop * self.frame_size - self.skip
+
+ clean_signal_hp = self.clean_signal_hp[signal_start : signal_stop].astype(np.float32) / 2**15
+ clean_signal = self.clean_signal[signal_start : signal_stop].astype(np.float32) / 2**15
+ coded_signal = self.coded_signal[signal_start : signal_stop].astype(np.float32) / 2**15
+
+ coded_signal_history = self.coded_signal[signal_start - self.history_len : signal_start].astype(np.float32) / 2**15
+
+ features, periods = self.create_features(
+ coded_signal,
+ coded_signal_history,
+ self.lpcs[frame_start : frame_stop],
+ self.gains[frame_start : frame_stop],
+ self.ltps[frame_start : frame_stop],
+ self.periods[frame_start : frame_stop]
+ )
+
+ if self.preemph > 0:
+ clean_signal[1:] -= self.preemph * clean_signal[: -1]
+ clean_signal_hp[1:] -= self.preemph * clean_signal_hp[: -1]
+ coded_signal[1:] -= self.preemph * coded_signal[: -1]
+
+ num_bits = np.repeat(self.num_bits[frame_start // 4 : frame_stop // 4], 4).astype(np.float32).reshape(-1, 1)
+ num_bits_smooth = np.repeat(self.num_bits_smooth[frame_start // 4 : frame_stop // 4], 4).astype(np.float32).reshape(-1, 1)
+
+ numbits = np.concatenate((num_bits, num_bits_smooth), axis=-1)
+
+ return {
+ 'features' : features,
+ 'periods' : periods.astype(np.int64),
+ 'target_orig' : clean_signal.astype(np.float32),
+ 'target' : clean_signal_hp.astype(np.float32),
+ 'signals' : coded_signal.reshape(-1, 1).astype(np.float32),
+ 'numbits' : numbits.astype(np.float32)
+ }
diff --git a/dnn/torch/osce/engine/engine.py b/dnn/torch/osce/engine/engine.py
new file mode 100644
index 00000000..0762c898
--- /dev/null
+++ b/dnn/torch/osce/engine/engine.py
@@ -0,0 +1,103 @@
+import torch
+from tqdm import tqdm
+import sys
+
+def train_one_epoch(model, criterion, optimizer, dataloader, device, scheduler, log_interval=10):
+
+ model.to(device)
+ model.train()
+
+ running_loss = 0
+ previous_running_loss = 0
+
+
+ with tqdm(dataloader, unit='batch', file=sys.stdout) as tepoch:
+
+ for i, batch in enumerate(tepoch):
+
+ # set gradients to zero
+ optimizer.zero_grad()
+
+
+ # push batch to device
+ for key in batch:
+ batch[key] = batch[key].to(device)
+
+ target = batch['target']
+
+ # calculate model output
+ output = model(batch['signals'].permute(0, 2, 1), batch['features'], batch['periods'], batch['numbits'])
+
+ # calculate loss
+ if isinstance(output, list):
+ loss = torch.zeros(1, device=device)
+ for y in output:
+ loss = loss + criterion(target, y.squeeze(1))
+ loss = loss / len(output)
+ else:
+ loss = criterion(target, output.squeeze(1))
+
+ # calculate gradients
+ loss.backward()
+
+ # update weights
+ optimizer.step()
+
+ # update learning rate
+ scheduler.step()
+
+ # sparsification
+ if hasattr(model, 'sparsifier'):
+ model.sparsifier()
+
+ # update running loss
+ running_loss += float(loss.cpu())
+
+ # update status bar
+ if i % log_interval == 0:
+ tepoch.set_postfix(running_loss=f"{running_loss/(i + 1):8.7f}", current_loss=f"{(running_loss - previous_running_loss)/log_interval:8.7f}")
+ previous_running_loss = running_loss
+
+
+ running_loss /= len(dataloader)
+
+ return running_loss
+
+def evaluate(model, criterion, dataloader, device, log_interval=10):
+
+ model.to(device)
+ model.eval()
+
+ running_loss = 0
+ previous_running_loss = 0
+
+
+ with torch.no_grad():
+ with tqdm(dataloader, unit='batch', file=sys.stdout) as tepoch:
+
+ for i, batch in enumerate(tepoch):
+
+ # push batch to device
+ for key in batch:
+ batch[key] = batch[key].to(device)
+
+ target = batch['target']
+
+ # calculate model output
+ output = model(batch['signals'].permute(0, 2, 1), batch['features'], batch['periods'], batch['numbits'])
+
+ # calculate loss
+ loss = criterion(target, output.squeeze(1))
+
+ # update running loss
+ running_loss += float(loss.cpu())
+
+ # update status bar
+ if i % log_interval == 0:
+ tepoch.set_postfix(running_loss=f"{running_loss/(i + 1):8.7f}", current_loss=f"{(running_loss - previous_running_loss)/log_interval:8.7f}")
+ previous_running_loss = running_loss
+
+
+ running_loss /= len(dataloader)
+
+ return running_loss \ No newline at end of file
diff --git a/dnn/torch/osce/engine/vocoder_engine.py b/dnn/torch/osce/engine/vocoder_engine.py
new file mode 100644
index 00000000..9eee49e4
--- /dev/null
+++ b/dnn/torch/osce/engine/vocoder_engine.py
@@ -0,0 +1,101 @@
+import torch
+from tqdm import tqdm
+import sys
+
+def train_one_epoch(model, criterion, optimizer, dataloader, device, scheduler, log_interval=10):
+
+ model.to(device)
+ model.train()
+
+ running_loss = 0
+ previous_running_loss = 0
+
+
+ with tqdm(dataloader, unit='batch', file=sys.stdout) as tepoch:
+
+ for i, batch in enumerate(tepoch):
+
+ # set gradients to zero
+ optimizer.zero_grad()
+
+
+ # push batch to device
+ for key in batch:
+ batch[key] = batch[key].to(device)
+
+ target = batch['target']
+
+ # calculate model output
+ output = model(batch['features'], batch['periods'])
+
+ # calculate loss
+ if isinstance(output, list):
+ loss = torch.zeros(1, device=device)
+ for y in output:
+ loss = loss + criterion(target, y.squeeze(1))
+ loss = loss / len(output)
+ else:
+ loss = criterion(target, output.squeeze(1))
+
+ # calculate gradients
+ loss.backward()
+
+ # update weights
+ optimizer.step()
+
+ # update learning rate
+ scheduler.step()
+
+ # update running loss
+ running_loss += float(loss.cpu())
+
+ # update status bar
+ if i % log_interval == 0:
+ tepoch.set_postfix(running_loss=f"{running_loss/(i + 1):8.7f}", current_loss=f"{(running_loss - previous_running_loss)/log_interval:8.7f}")
+ previous_running_loss = running_loss
+
+
+ running_loss /= len(dataloader)
+
+ return running_loss
+
+def evaluate(model, criterion, dataloader, device, log_interval=10):
+
+ model.to(device)
+ model.eval()
+
+ running_loss = 0
+ previous_running_loss = 0
+
+
+ with torch.no_grad():
+ with tqdm(dataloader, unit='batch', file=sys.stdout) as tepoch:
+
+ for i, batch in enumerate(tepoch):
+
+
+
+ # push batch to device
+ for key in batch:
+ batch[key] = batch[key].to(device)
+
+ target = batch['target']
+
+ # calculate model output
+ output = model(batch['features'], batch['periods'])
+
+ # calculate loss
+ loss = criterion(target, output.squeeze(1))
+
+ # update running loss
+ running_loss += float(loss.cpu())
+
+ # update status bar
+ if i % log_interval == 0:
+ tepoch.set_postfix(running_loss=f"{running_loss/(i + 1):8.7f}", current_loss=f"{(running_loss - previous_running_loss)/log_interval:8.7f}")
+ previous_running_loss = running_loss
+
+
+ running_loss /= len(dataloader)
+
+ return running_loss \ No newline at end of file
diff --git a/dnn/torch/osce/export_model_weights.py b/dnn/torch/osce/export_model_weights.py
new file mode 100644
index 00000000..a504a61c
--- /dev/null
+++ b/dnn/torch/osce/export_model_weights.py
@@ -0,0 +1,174 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+import sys
+
+import hashlib
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../weight-exchange'))
+
+import torch
+import wexchange.torch
+from wexchange.torch import dump_torch_weights
+from models import model_dict
+
+from utils.layers.limited_adaptive_comb1d import LimitedAdaptiveComb1d
+from utils.layers.limited_adaptive_conv1d import LimitedAdaptiveConv1d
+from utils.layers.td_shaper import TDShaper
+from utils.misc import remove_all_weight_norm
+from wexchange.torch import dump_torch_weights
+
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('checkpoint', type=str, help='LACE or NoLACE model checkpoint')
+parser.add_argument('output_dir', type=str, help='output folder')
+parser.add_argument('--quantize', action="store_true", help='quantization according to schedule')
+
+sparse_default=False
+schedules = {
+ 'nolace': [
+ ('pitch_embedding', dict()),
+ ('feature_net.conv1', dict()),
+ ('feature_net.conv2', dict(quantize=True, scale=None, sparse=sparse_default)),
+ ('feature_net.tconv', dict(quantize=True, scale=None, sparse=sparse_default)),
+ ('feature_net.gru', dict(quantize=True, scale=None, recurrent_scale=None, input_sparse=sparse_default, recurrent_sparse=sparse_default)),
+ ('cf1', dict(quantize=True, scale=None)),
+ ('cf2', dict(quantize=True, scale=None)),
+ ('af1', dict(quantize=True, scale=None)),
+ ('tdshape1', dict(quantize=True, scale=None)),
+ ('tdshape2', dict(quantize=True, scale=None)),
+ ('tdshape3', dict(quantize=True, scale=None)),
+ ('af2', dict(quantize=True, scale=None)),
+ ('af3', dict(quantize=True, scale=None)),
+ ('af4', dict(quantize=True, scale=None)),
+ ('post_cf1', dict(quantize=True, scale=None, sparse=sparse_default)),
+ ('post_cf2', dict(quantize=True, scale=None, sparse=sparse_default)),
+ ('post_af1', dict(quantize=True, scale=None, sparse=sparse_default)),
+ ('post_af2', dict(quantize=True, scale=None, sparse=sparse_default)),
+ ('post_af3', dict(quantize=True, scale=None, sparse=sparse_default))
+ ],
+ 'lace' : [
+ ('pitch_embedding', dict()),
+ ('feature_net.conv1', dict()),
+ ('feature_net.conv2', dict(quantize=True, scale=None, sparse=sparse_default)),
+ ('feature_net.tconv', dict(quantize=True, scale=None, sparse=sparse_default)),
+ ('feature_net.gru', dict(quantize=True, scale=None, recurrent_scale=None, input_sparse=sparse_default, recurrent_sparse=sparse_default)),
+ ('cf1', dict(quantize=True, scale=None)),
+ ('cf2', dict(quantize=True, scale=None)),
+ ('af1', dict(quantize=True, scale=None))
+ ]
+}
+
+
+# auxiliary functions
+def sha1(filename):
+ BUF_SIZE = 65536
+ sha1 = hashlib.sha1()
+
+ with open(filename, 'rb') as f:
+ while True:
+ data = f.read(BUF_SIZE)
+ if not data:
+ break
+ sha1.update(data)
+
+ return sha1.hexdigest()
+
+def osce_dump_generic(writer, name, module):
+ if isinstance(module, torch.nn.Linear) or isinstance(module, torch.nn.Conv1d) \
+ or isinstance(module, torch.nn.ConvTranspose1d) or isinstance(module, torch.nn.Embedding) \
+ or isinstance(module, LimitedAdaptiveConv1d) or isinstance(module, LimitedAdaptiveComb1d) \
+ or isinstance(module, TDShaper) or isinstance(module, torch.nn.GRU):
+ dump_torch_weights(writer, module, name=name, verbose=True)
+ else:
+ for child_name, child in module.named_children():
+ osce_dump_generic(writer, (name + "_" + child_name).replace("feature_net", "fnet"), child)
+
+
+def export_name(name):
+ name = name.replace('.', '_')
+ name = name.replace('feature_net', 'fnet')
+ return name
+
+def osce_scheduled_dump(writer, prefix, model, schedule):
+ if not prefix.endswith('_'):
+ prefix += '_'
+
+ for name, kwargs in schedule:
+ dump_torch_weights(writer, model.get_submodule(name), prefix + export_name(name), **kwargs, verbose=True)
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+ checkpoint_path = args.checkpoint
+ outdir = args.output_dir
+ os.makedirs(outdir, exist_ok=True)
+
+ # dump message
+ message = f"Auto generated from checkpoint {os.path.basename(checkpoint_path)} (sha1: {sha1(checkpoint_path)})"
+
+ # create model and load weights
+ checkpoint = torch.load(checkpoint_path, map_location='cpu')
+ model = model_dict[checkpoint['setup']['model']['name']](*checkpoint['setup']['model']['args'], **checkpoint['setup']['model']['kwargs'])
+ model.load_state_dict(checkpoint['state_dict'])
+ remove_all_weight_norm(model, verbose=True)
+
+ # CWriter
+ model_name = checkpoint['setup']['model']['name']
+ cwriter = wexchange.c_export.CWriter(os.path.join(outdir, model_name + "_data"), message=message, model_struct_name=model_name.upper() + 'Layers', add_typedef=True)
+
+ # Add custom includes and global parameters
+ cwriter.header.write(f'''
+#define {model_name.upper()}_PREEMPH {model.preemph}f
+#define {model_name.upper()}_FRAME_SIZE {model.FRAME_SIZE}
+#define {model_name.upper()}_OVERLAP_SIZE 40
+#define {model_name.upper()}_NUM_FEATURES {model.num_features}
+#define {model_name.upper()}_PITCH_MAX {model.pitch_max}
+#define {model_name.upper()}_PITCH_EMBEDDING_DIM {model.pitch_embedding_dim}
+#define {model_name.upper()}_NUMBITS_RANGE_LOW {model.numbits_range[0]}
+#define {model_name.upper()}_NUMBITS_RANGE_HIGH {model.numbits_range[1]}
+#define {model_name.upper()}_NUMBITS_EMBEDDING_DIM {model.numbits_embedding_dim}
+#define {model_name.upper()}_COND_DIM {model.cond_dim}
+#define {model_name.upper()}_HIDDEN_FEATURE_DIM {model.hidden_feature_dim}
+''')
+
+ for i, s in enumerate(model.numbits_embedding.scale_factors):
+ cwriter.header.write(f"#define {model_name.upper()}_NUMBITS_SCALE_{i} {float(s.detach().cpu())}f\n")
+
+ # dump layers
+ if model_name in schedules and args.quantize:
+ osce_scheduled_dump(cwriter, model_name, model, schedules[model_name])
+ else:
+ osce_dump_generic(cwriter, model_name, model)
+
+ cwriter.close()
diff --git a/dnn/torch/osce/losses/stft_loss.py b/dnn/torch/osce/losses/stft_loss.py
new file mode 100644
index 00000000..4c164cb6
--- /dev/null
+++ b/dnn/torch/osce/losses/stft_loss.py
@@ -0,0 +1,277 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+"""STFT-based Loss modules."""
+
+import torch
+import torch.nn.functional as F
+from torch import nn
+import numpy as np
+import torchaudio
+
+
+def get_window(win_name, win_length, *args, **kwargs):
+ window_dict = {
+ 'bartlett_window' : torch.bartlett_window,
+ 'blackman_window' : torch.blackman_window,
+ 'hamming_window' : torch.hamming_window,
+ 'hann_window' : torch.hann_window,
+ 'kaiser_window' : torch.kaiser_window
+ }
+
+ if not win_name in window_dict:
+ raise ValueError()
+
+ return window_dict[win_name](win_length, *args, **kwargs)
+
+
+def stft(x, fft_size, hop_size, win_length, window):
+ """Perform STFT and convert to magnitude spectrogram.
+ Args:
+ x (Tensor): Input signal tensor (B, T).
+ fft_size (int): FFT size.
+ hop_size (int): Hop size.
+ win_length (int): Window length.
+ window (str): Window function type.
+ Returns:
+ Tensor: Magnitude spectrogram (B, #frames, fft_size // 2 + 1).
+ """
+
+ win = get_window(window, win_length).to(x.device)
+ x_stft = torch.stft(x, fft_size, hop_size, win_length, win, return_complex=True)
+
+
+ return torch.clamp(torch.abs(x_stft), min=1e-7)
+
+def spectral_convergence_loss(Y_true, Y_pred):
+ dims=list(range(1, len(Y_pred.shape)))
+ return torch.mean(torch.norm(torch.abs(Y_true) - torch.abs(Y_pred), p="fro", dim=dims) / (torch.norm(Y_pred, p="fro", dim=dims) + 1e-6))
+
+
+def log_magnitude_loss(Y_true, Y_pred):
+ Y_true_log_abs = torch.log(torch.abs(Y_true) + 1e-15)
+ Y_pred_log_abs = torch.log(torch.abs(Y_pred) + 1e-15)
+
+ return torch.mean(torch.abs(Y_true_log_abs - Y_pred_log_abs))
+
+def spectral_xcorr_loss(Y_true, Y_pred):
+ Y_true = Y_true.abs()
+ Y_pred = Y_pred.abs()
+ dims=list(range(1, len(Y_pred.shape)))
+ xcorr = torch.sum(Y_true * Y_pred, dim=dims) / torch.sqrt(torch.sum(Y_true ** 2, dim=dims) * torch.sum(Y_pred ** 2, dim=dims) + 1e-9)
+
+ return 1 - xcorr.mean()
+
+
+
+class MRLogMelLoss(nn.Module):
+ def __init__(self,
+ fft_sizes=[512, 256, 128, 64],
+ overlap=0.5,
+ fs=16000,
+ n_mels=18
+ ):
+
+ self.fft_sizes = fft_sizes
+ self.overlap = overlap
+ self.fs = fs
+ self.n_mels = n_mels
+
+ super().__init__()
+
+ self.mel_specs = []
+ for fft_size in fft_sizes:
+ hop_size = int(round(fft_size * (1 - self.overlap)))
+
+ n_mels = self.n_mels
+ if fft_size < 128:
+ n_mels //= 2
+
+ self.mel_specs.append(torchaudio.transforms.MelSpectrogram(fs, fft_size, hop_length=hop_size, n_mels=n_mels))
+
+ for i, mel_spec in enumerate(self.mel_specs):
+ self.add_module(f'mel_spec_{i+1}', mel_spec)
+
+ def forward(self, y_true, y_pred):
+
+ loss = torch.zeros(1, device=y_true.device)
+
+ for mel_spec in self.mel_specs:
+ Y_true = mel_spec(y_true)
+ Y_pred = mel_spec(y_pred)
+ loss = loss + log_magnitude_loss(Y_true, Y_pred)
+
+ loss = loss / len(self.mel_specs)
+
+ return loss
+
+def create_weight_matrix(num_bins, bins_per_band=10):
+ m = torch.zeros((num_bins, num_bins), dtype=torch.float32)
+
+ r0 = bins_per_band // 2
+ r1 = bins_per_band - r0
+
+ for i in range(num_bins):
+ i0 = max(i - r0, 0)
+ j0 = min(i + r1, num_bins)
+
+ m[i, i0: j0] += 1
+
+ if i < r0:
+ m[i, :r0 - i] += 1
+
+ if i > num_bins - r1:
+ m[i, num_bins - r1 - i:] += 1
+
+ return m / bins_per_band
+
+def weighted_spectral_convergence(Y_true, Y_pred, w):
+
+ # calculate sfm based weights
+ logY = torch.log(torch.abs(Y_true) + 1e-9)
+ Y = torch.abs(Y_true)
+
+ avg_logY = torch.matmul(logY.transpose(1, 2), w)
+ avg_Y = torch.matmul(Y.transpose(1, 2), w)
+
+ sfm = torch.exp(avg_logY) / (avg_Y + 1e-9)
+
+ weight = (torch.relu(1 - sfm) ** .5).transpose(1, 2)
+
+ loss = torch.mean(
+ torch.mean(weight * torch.abs(torch.abs(Y_true) - torch.abs(Y_pred)), dim=[1, 2])
+ / (torch.mean( weight * torch.abs(Y_true), dim=[1, 2]) + 1e-9)
+ )
+
+ return loss
+
+def gen_filterbank(N, Fs=16000):
+ in_freq = (np.arange(N+1, dtype='float32')/N*Fs/2)[None,:]
+ out_freq = (np.arange(N, dtype='float32')/N*Fs/2)[:,None]
+ #ERB from B.C.J Moore, An Introduction to the Psychology of Hearing, 5th Ed., page 73.
+ ERB_N = 24.7 + .108*in_freq
+ delta = np.abs(in_freq-out_freq)/ERB_N
+ center = (delta<.5).astype('float32')
+ R = -12*center*delta**2 + (1-center)*(3-12*delta)
+ RE = 10.**(R/10.)
+ norm = np.sum(RE, axis=1)
+ RE = RE/norm[:, np.newaxis]
+ return torch.from_numpy(RE)
+
+def smooth_log_mag(Y_true, Y_pred, filterbank):
+ Y_true_smooth = torch.matmul(filterbank, torch.abs(Y_true))
+ Y_pred_smooth = torch.matmul(filterbank, torch.abs(Y_pred))
+
+ loss = torch.abs(
+ torch.log(Y_true_smooth + 1e-9) - torch.log(Y_pred_smooth + 1e-9)
+ )
+
+ loss = loss.mean()
+
+ return loss
+
+class MRSTFTLoss(nn.Module):
+ def __init__(self,
+ fft_sizes=[2048, 1024, 512, 256, 128, 64],
+ overlap=0.5,
+ window='hann_window',
+ fs=16000,
+ log_mag_weight=1,
+ sc_weight=0,
+ wsc_weight=0,
+ smooth_log_mag_weight=0,
+ sxcorr_weight=0):
+ super().__init__()
+
+ self.fft_sizes = fft_sizes
+ self.overlap = overlap
+ self.window = window
+ self.log_mag_weight = log_mag_weight
+ self.sc_weight = sc_weight
+ self.wsc_weight = wsc_weight
+ self.smooth_log_mag_weight = smooth_log_mag_weight
+ self.sxcorr_weight = sxcorr_weight
+ self.fs = fs
+
+ # weights for SFM weighted spectral convergence loss
+ self.wsc_weights = torch.nn.ParameterDict()
+ for fft_size in fft_sizes:
+ width = min(11, int(1000 * fft_size / self.fs + .5))
+ width += width % 2
+ self.wsc_weights[str(fft_size)] = torch.nn.Parameter(
+ create_weight_matrix(fft_size // 2 + 1, width),
+ requires_grad=False
+ )
+
+ # filterbanks for smooth log magnitude loss
+ self.filterbanks = torch.nn.ParameterDict()
+ for fft_size in fft_sizes:
+ self.filterbanks[str(fft_size)] = torch.nn.Parameter(
+ gen_filterbank(fft_size//2),
+ requires_grad=False
+ )
+
+
+ def __call__(self, y_true, y_pred):
+
+
+ lm_loss = torch.zeros(1, device=y_true.device)
+ sc_loss = torch.zeros(1, device=y_true.device)
+ wsc_loss = torch.zeros(1, device=y_true.device)
+ slm_loss = torch.zeros(1, device=y_true.device)
+ sxcorr_loss = torch.zeros(1, device=y_true.device)
+
+ for fft_size in self.fft_sizes:
+ hop_size = int(round(fft_size * (1 - self.overlap)))
+ win_size = fft_size
+
+ Y_true = stft(y_true, fft_size, hop_size, win_size, self.window)
+ Y_pred = stft(y_pred, fft_size, hop_size, win_size, self.window)
+
+ if self.log_mag_weight > 0:
+ lm_loss = lm_loss + log_magnitude_loss(Y_true, Y_pred)
+
+ if self.sc_weight > 0:
+ sc_loss = sc_loss + spectral_convergence_loss(Y_true, Y_pred)
+
+ if self.wsc_weight > 0:
+ wsc_loss = wsc_loss + weighted_spectral_convergence(Y_true, Y_pred, self.wsc_weights[str(fft_size)])
+
+ if self.smooth_log_mag_weight > 0:
+ slm_loss = slm_loss + smooth_log_mag(Y_true, Y_pred, self.filterbanks[str(fft_size)])
+
+ if self.sxcorr_weight > 0:
+ sxcorr_loss = sxcorr_loss + spectral_xcorr_loss(Y_true, Y_pred)
+
+
+ total_loss = (self.log_mag_weight * lm_loss + self.sc_weight * sc_loss
+ + self.wsc_weight * wsc_loss + self.smooth_log_mag_weight * slm_loss
+ + self.sxcorr_weight * sxcorr_loss) / len(self.fft_sizes)
+
+ return total_loss \ No newline at end of file
diff --git a/dnn/torch/osce/make_default_setup.py b/dnn/torch/osce/make_default_setup.py
new file mode 100644
index 00000000..d7365fff
--- /dev/null
+++ b/dnn/torch/osce/make_default_setup.py
@@ -0,0 +1,93 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import sys
+import argparse
+
+import yaml
+
+from utils.templates import setup_dict
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('name', type=str, help='name of default setup file')
+parser.add_argument('--model', choices=['lace', 'nolace', 'lavoce'], help='model name', default='lace')
+parser.add_argument('--adversarial', action='store_true', help='setup for adversarial training')
+parser.add_argument('--path2dataset', type=str, help='dataset path', default=None)
+
+args = parser.parse_args()
+
+key = args.model + "_adv" if args.adversarial else args.model
+
+try:
+ setup = setup_dict[key]
+except KeyError:
+ print("setup not found, adversarial training possibly not specified for model")
+ sys.exit(1)
+
+# update dataset if given
+if type(args.path2dataset) != type(None):
+ setup['dataset'] = args.path2dataset
+
+name = args.name
+if not name.endswith('.yml'):
+ name += '.yml'
+
+if __name__ == '__main__':
+ with open(name, 'w') as f:
+ f.write(yaml.dump(setup)) \ No newline at end of file
diff --git a/dnn/torch/osce/models/__init__.py b/dnn/torch/osce/models/__init__.py
new file mode 100644
index 00000000..859db033
--- /dev/null
+++ b/dnn/torch/osce/models/__init__.py
@@ -0,0 +1,42 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+from .lace import LACE
+from .no_lace import NoLACE
+from .lavoce import LaVoce
+from .lavoce_400 import LaVoce400
+from .fd_discriminator import TFDMultiResolutionDiscriminator as FDMResDisc
+
+model_dict = {
+ 'lace': LACE,
+ 'nolace': NoLACE,
+ 'lavoce': LaVoce,
+ 'lavoce400': LaVoce400,
+ 'fdmresdisc': FDMResDisc,
+}
diff --git a/dnn/torch/osce/models/fd_discriminator.py b/dnn/torch/osce/models/fd_discriminator.py
new file mode 100644
index 00000000..22948624
--- /dev/null
+++ b/dnn/torch/osce/models/fd_discriminator.py
@@ -0,0 +1,974 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import math as m
+import copy
+
+import torch
+import torch.nn.functional as F
+from torch import nn
+from torch.nn.utils import weight_norm, spectral_norm
+import torchaudio
+
+from utils.spec import gen_filterbank
+
+# auxiliary functions
+
+def remove_all_weight_norms(module):
+ for m in module.modules():
+ if hasattr(m, 'weight_v'):
+ nn.utils.remove_weight_norm(m)
+
+
+def create_smoothing_kernel(h, w, gamma=1.5):
+
+ ch = h / 2 - 0.5
+ cw = w / 2 - 0.5
+
+ sh = gamma * ch
+ sw = gamma * cw
+
+ vx = ((torch.arange(h) - ch) / sh) ** 2
+ vy = ((torch.arange(w) - cw) / sw) ** 2
+ vals = vx.view(-1, 1) + vy.view(1, -1)
+ kernel = torch.exp(- vals)
+ kernel = kernel / kernel.sum()
+
+ return kernel
+
+
+def create_kernel(h, w, sh, sw):
+ # proto kernel gives disjoint partition of 1
+ proto_kernel = torch.ones((sh, sw))
+
+ # create smoothing kernel eta
+ h_eta, w_eta = h - sh + 1, w - sw + 1
+ assert h_eta > 0 and w_eta > 0
+ eta = create_smoothing_kernel(h_eta, w_eta).view(1, 1, h_eta, w_eta)
+
+ kernel0 = F.pad(proto_kernel, [w_eta - 1, w_eta - 1, h_eta - 1, h_eta - 1]).unsqueeze(0).unsqueeze(0)
+ kernel = F.conv2d(kernel0, eta)
+
+ return kernel
+
+# positional embeddings
+class FrequencyPositionalEmbedding(nn.Module):
+ def __init__(self):
+
+ super().__init__()
+
+ def forward(self, x):
+
+ N = x.size(2)
+ args = torch.arange(0, N, dtype=x.dtype, device=x.device) * torch.pi * 2 / N
+ cos = torch.cos(args).reshape(1, 1, -1, 1)
+ sin = torch.sin(args).reshape(1, 1, -1, 1)
+ zeros = torch.zeros_like(x[:, 0:1, :, :])
+
+ y = torch.cat((x, zeros + sin, zeros + cos), dim=1)
+
+ return y
+
+
+class PositionalEmbedding2D(nn.Module):
+ def __init__(self, d=5):
+
+ super().__init__()
+
+ self.d = d
+
+ def forward(self, x):
+
+ N = x.size(2)
+ M = x.size(3)
+
+ h_args = torch.arange(0, N, dtype=x.dtype, device=x.device).reshape(1, 1, -1, 1)
+ w_args = torch.arange(0, M, dtype=x.dtype, device=x.device).reshape(1, 1, 1, -1)
+ coeffs = (10000 ** (-2 * torch.arange(0, self.d, dtype=x.dtype, device=x.device) / self.d)).reshape(1, -1, 1, 1)
+
+ h_sin = torch.sin(coeffs * h_args)
+ h_cos = torch.sin(coeffs * h_args)
+ w_sin = torch.sin(coeffs * w_args)
+ w_cos = torch.sin(coeffs * w_args)
+
+ zeros = torch.zeros_like(x[:, 0:1, :, :])
+
+ y = torch.cat((x, zeros + h_sin, zeros + h_cos, zeros + w_sin, zeros + w_cos), dim=1)
+
+ return y
+
+
+# spectral discriminator base class
+class SpecDiscriminatorBase(nn.Module):
+ RECEPTIVE_FIELD_MAX_WIDTH=10000
+ def __init__(self,
+ layers,
+ resolution,
+ fs=16000,
+ freq_roi=[50, 7000],
+ noise_gain=1e-3,
+ fmap_start_index=0
+ ):
+ super().__init__()
+
+
+ self.layers = nn.ModuleList(layers)
+ self.resolution = resolution
+ self.fs = fs
+ self.noise_gain = noise_gain
+ self.fmap_start_index = fmap_start_index
+
+ if fmap_start_index >= len(layers):
+ raise ValueError(f'fmap_start_index is larger than number of layers')
+
+ # filter bank for noise shaping
+ n_fft = resolution[0]
+
+ self.filterbank = nn.Parameter(
+ gen_filterbank(n_fft // 2, fs, keep_size=True),
+ requires_grad=False
+ )
+
+ # roi bins
+ f_step = fs / n_fft
+ self.start_bin = int(m.ceil(freq_roi[0] / f_step - 0.01))
+ self.stop_bin = min(int(m.floor(freq_roi[1] / f_step + 0.01)), n_fft//2 + 1)
+
+ self.init_weights()
+
+ # determine receptive field size, offsets and strides
+
+ hw = 1000
+ while True:
+ x = torch.zeros((1, hw, hw))
+ with torch.no_grad():
+ y = self.run_layer_stack(x)[-1]
+
+ pos0 = [y.size(-2) // 2, y.size(-1) // 2]
+ pos1 = [t + 1 for t in pos0]
+
+ hs0, ws0 = self._receptive_field((hw, hw), pos0)
+ hs1, ws1 = self._receptive_field((hw, hw), pos1)
+
+ h0 = hs0[1] - hs0[0] + 1
+ h1 = hs1[1] - hs1[0] + 1
+ w0 = ws0[1] - ws0[0] + 1
+ w1 = ws1[1] - ws1[0] + 1
+
+ if h0 != h1 or w0 != w1:
+ hw = 2 * hw
+ else:
+
+ # strides
+ sh = hs1[0] - hs0[0]
+ sw = ws1[0] - ws0[0]
+
+ if sh == 0 or sw == 0: continue
+
+ # offsets
+ oh = hs0[0] - sh * pos0[0]
+ ow = ws0[0] - sw * pos0[1]
+
+ # overlap factor
+ overlap = w0 / sw + h0 / sh
+
+ #print(f"{w0=} {h0=} {sw=} {sh=} {overlap=}")
+ self.receptive_field_params = {'width': [sw, ow, w0], 'height': [sh, oh, h0], 'overlap': overlap}
+
+ break
+
+ if hw > self.RECEPTIVE_FIELD_MAX_WIDTH:
+ print("warning: exceeded max size while trying to determine receptive field")
+
+ # create transposed convolutional kernel
+ #self.tconv_kernel = nn.Parameter(create_kernel(h0, w0, sw, sw), requires_grad=False)
+
+ def run_layer_stack(self, spec):
+
+ output = []
+
+ x = spec.unsqueeze(1)
+
+ for layer in self.layers:
+ x = layer(x)
+ output.append(x)
+
+ return output
+
+ def forward(self, x):
+ """ returns array with feature maps and final score at index -1 """
+
+ output = []
+
+ x = self.spectrogram(x)
+
+ output = self.run_layer_stack(x)
+
+ return output[self.fmap_start_index:]
+
+ def receptive_field(self, output_pos):
+
+ if self.receptive_field_params is not None:
+ s, o, h = self.receptive_field_params['height']
+ h_min = output_pos[0] * s + o + self.start_bin
+ h_max = h_min + h
+ h_min = max(h_min, self.start_bin)
+ h_max = min(h_max, self.stop_bin)
+
+ s, o, w = self.receptive_field_params['width']
+ w_min = output_pos[1] * s + o
+ w_max = w_min + w
+
+ return (h_min, h_max), (w_min, w_max)
+
+ else:
+ return None, None
+
+
+ def _receptive_field(self, input_dims, output_pos):
+ """ determines receptive field probabilistically via autograd (slow) """
+
+ x = torch.randn((1,) + input_dims, requires_grad=True)
+
+ # run input through layers
+ y = self.run_layer_stack(x)[-1]
+ b, c, h, w = y.shape
+
+ if output_pos[0] >= h or output_pos[1] >= w:
+ raise ValueError("position out of range")
+
+ mask = torch.zeros((b, c, h, w))
+ mask[0, 0, output_pos[0], output_pos[1]] = 1
+
+ (mask * y).sum().backward()
+
+ hs, ws = torch.nonzero(x.grad[0], as_tuple=True)
+
+ h_min, h_max = hs.min().item(), hs.max().item()
+ w_min, w_max = ws.min().item(), ws.max().item()
+
+ return [h_min, h_max], [w_min, w_max]
+
+
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d) or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+
+ def spectrogram(self, x):
+ n_fft, hop_length, win_length = self.resolution
+ x = x.squeeze(1)
+ window = getattr(torch, 'hann_window')(win_length).to(x.device)
+
+ x = torch.stft(x, n_fft=n_fft, hop_length=hop_length, win_length=win_length,\
+ window=window, return_complex=True) #[B, F, T]
+ x = torch.abs(x)
+
+ # noise floor following spectral envelope
+ smoothed_x = torch.matmul(self.filterbank, x)
+ noise = torch.randn_like(x) * smoothed_x * self.noise_gain
+ x = x + noise
+
+ # frequency ROI
+ x = x[:, self.start_bin : self.stop_bin + 1, ...]
+
+ return torchaudio.functional.amplitude_to_DB(x,db_multiplier=0.0, multiplier=20,amin=1e-05,top_db=80)#torch.sqrt(x)
+
+ def grad_map(self, x):
+ self.zero_grad()
+
+ n_fft, hop_length, win_length = self.resolution
+
+ window = getattr(torch, 'hann_window')(win_length).to(x.device)
+
+ y = torch.stft(x.squeeze(1), n_fft=n_fft, hop_length=hop_length, win_length=win_length,
+ window=window, return_complex=True) #[B, F, T]
+ y = torch.abs(y)
+
+ specgram = torchaudio.functional.amplitude_to_DB(y,db_multiplier=0.0, multiplier=20,amin=1e-05,top_db=80)
+
+ specgram.requires_grad = True
+ specgram.retain_grad()
+
+ if specgram.grad is not None:
+ specgram.grad.zero_()
+
+ y = specgram[:, self.start_bin : self.stop_bin + 1, ...]
+
+ scores = self.run_layer_stack(y)[-1]
+
+ loss = torch.mean((1 - scores) ** 2)
+ loss.backward()
+
+ return specgram.data[0], torch.abs(specgram.grad)[0]
+
+ def relevance_map(self, x):
+
+ n_fft, hop_length, win_length = self.resolution
+ y = x.view(-1)
+ window = getattr(torch, 'hann_window')(win_length).to(x.device)
+
+ y = torch.stft(y, n_fft=n_fft, hop_length=hop_length, win_length=win_length,\
+ window=window, return_complex=True) #[B, F, T]
+ y = torch.abs(y)
+
+ specgram = torchaudio.functional.amplitude_to_DB(y,db_multiplier=0.0, multiplier=20,amin=1e-05,top_db=80)
+
+
+ scores = self.forward(x)[-1]
+
+ sh, _, h = self.receptive_field_params['height']
+ sw, _, w = self.receptive_field_params['width']
+ kernel = create_kernel(h, w, sh, sw).float().to(scores.device)
+ with torch.no_grad():
+ pad_w = (w + sw - 1) // sw
+ pad_h = (h + sh - 1) // sh
+ padded_scores = F.pad(scores, (pad_w, pad_w, pad_h, pad_h), mode='replicate')
+ # CAVE: padding should be derived from offsets
+ rv = F.conv_transpose2d(padded_scores, kernel, bias=None, stride=(sh, sw), padding=(h//2, w//2))
+ rv = rv[..., pad_h * sh : - pad_h * sh, pad_w * sw : -pad_w * sw]
+
+ relevance = torch.zeros_like(specgram)
+ relevance[..., self.start_bin : self.start_bin + rv.size(-2), : rv.size(-1)] = rv
+
+
+ return specgram, relevance
+
+
+ def lrp(self, x, eps=1e-9, label='both', threshold=0.5, low=None, high=None, verbose=False):
+ """ layer-wise relevance propagation (https://git.tu-berlin.de/gmontavon/lrp-tutorial) """
+
+ # ToDo: this code is highly unsafe as it assumes that layers are nn.Sequential with suitable activations
+
+ def newconv2d(layer,g):
+
+ new_layer = nn.Conv2d(layer.in_channels,
+ layer.out_channels,
+ layer.kernel_size,
+ stride=layer.stride,
+ padding=layer.padding,
+ dilation=layer.dilation,
+ groups=layer.groups)
+
+ try: new_layer.weight = nn.Parameter(g(layer.weight.data.clone()))
+ except AttributeError: pass
+
+ try: new_layer.bias = nn.Parameter(g(layer.bias.data.clone()))
+ except AttributeError: pass
+
+ return new_layer
+
+ bounds = {
+ 64: [-85.82449722290039, 2.1755014657974243],
+ 128: [-84.49211349487305, 3.5078893899917607],
+ 256: [-80.33127822875977, 7.6687201976776125],
+ 512: [-73.79328079223633, 14.20672025680542],
+ 1024: [-67.59239501953125, 20.40760498046875],
+ 2048: [-62.31902580261231, 25.680974197387698],
+ }
+
+ nfft = self.resolution[0]
+ if low is None: low = bounds[nfft][0]
+ if high is None: high = bounds[nfft][1]
+
+ remove_all_weight_norms(self)
+
+ for p in self.parameters():
+ if p.grad is not None:
+ p.grad.zero_()
+
+ num_layers = len(self.layers)
+ X = self.spectrogram(x). detach()
+
+
+ # forward pass
+ A = [X.unsqueeze(1)] + [None] * len(self.layers)
+
+ for i in range(num_layers - 1):
+ A[i + 1] = self.layers[i](A[i])
+
+ # initial relevance is last layer without activation
+ r = A[-2]
+ last_layer_rs = [r]
+ layer = self.layers[-1]
+ for sublayer in list(layer)[:-1]:
+ r = sublayer(r)
+ last_layer_rs.append(r)
+
+
+ mask = torch.zeros_like(r)
+ mask.requires_grad_(False)
+ if verbose:
+ print(r.min(), r.max())
+ if label in {'both', 'fake'}:
+ mask[r < -threshold] = 1
+ if label in {'both', 'real'}:
+ mask[r > threshold] = 1
+ r = r * mask
+
+ # backward pass
+ R = [None] * num_layers + [r]
+
+ for l in range(1, num_layers)[::-1]:
+ A[l] = (A[l]).data.requires_grad_(True)
+
+ layer = nn.Sequential(*(list(self.layers[l])[:-1]))
+ z = layer(A[l]) + eps
+ s = (R[l+1] / z).data
+ (z*s).sum().backward()
+ c = A[l].grad
+ R[l] = (A[l] * c).data
+
+ # first layer
+ A[0] = (A[0].data).requires_grad_(True)
+
+ Xl = (torch.zeros_like(A[0].data) + low).requires_grad_(True)
+ Xh = (torch.zeros_like(A[0].data) + high).requires_grad_(True)
+
+ if len(list(self.layers)) > 2:
+ # unsafe way to check for embedding layer
+ embed = list(self.layers[0])[0]
+ conv = list(self.layers[0])[1]
+
+ layer = nn.Sequential(embed, conv)
+ layerl = nn.Sequential(embed, newconv2d(conv, lambda p: p.clamp(min=0)))
+ layerh = nn.Sequential(embed, newconv2d(conv, lambda p: p.clamp(max=0)))
+
+ else:
+ layer = list(self.layers[0])[0]
+ layerl = newconv2d(layer, lambda p: p.clamp(min=0))
+ layerh = newconv2d(layer, lambda p: p.clamp(max=0))
+
+
+ z = layer(A[0])
+ z -= layerl(Xl) + layerh(Xh)
+ s = (R[1] / z).data
+ (z * s).sum().backward()
+ c, cp, cm = A[0].grad, Xl.grad, Xh.grad
+
+ R[0] = (A[0] * c + Xl * cp + Xh * cm)
+ #R[0] = (A[0] * c).data
+
+ return X, R[0].mean(dim=1)
+
+
+
+
+
+
+
+
+
+
+def create_3x3_conv_plan(num_layers : int,
+ f_stretch : int,
+ f_down : int,
+ t_stretch : int,
+ t_down : int
+ ):
+
+
+ """ creates a stride, dilation, padding plan for a 2d conv network
+
+ Args:
+ num_layers (int): number of layers
+ f_stretch (int): log_2 of stretching factor along frequency axis
+ f_down (int): log_2 of downsampling factor along frequency axis
+ t_stretch (int): log_2 of stretching factor along time axis
+ t_down (int): log_2 of downsampling factor along time axis
+
+ Returns:
+ list(list(tuple)): list containing entries [(stride_t, stride_f), (dilation_t, dilation_f), (padding_t, padding_f)]
+ """
+
+ assert num_layers > 0 and t_stretch >= 0 and t_down >= 0 and f_stretch >= 0 and f_down >= 0
+ assert f_stretch < num_layers and t_stretch < num_layers
+
+ def process_dimension(n_layers, stretch, down):
+
+ stack_layers = n_layers - 1
+
+ stride_layers = min(min(down, stretch) , stack_layers)
+ dilation_layers = max(min(stack_layers - stride_layers - 1, stretch - stride_layers), 0)
+ final_stride = 2 ** (max(down - stride_layers, 0))
+
+ final_dilation = 1
+ if stride_layers < stack_layers and stretch - stride_layers - dilation_layers > 0:
+ final_dilation = 2
+
+ strides, dilations, paddings = [], [], []
+ processed_layers = 0
+ current_dilation = 1
+
+ for _ in range(stride_layers):
+ # increase receptive field and downsample via stride = 2
+ strides.append(2)
+ dilations.append(1)
+ paddings.append(1)
+ processed_layers += 1
+
+ if processed_layers < stack_layers:
+ strides.append(1)
+ dilations.append(1)
+ paddings.append(1)
+ processed_layers += 1
+
+ for _ in range(dilation_layers):
+ # increase receptive field via dilation = 2
+ strides.append(1)
+ current_dilation *= 2
+ dilations.append(current_dilation)
+ paddings.append(current_dilation)
+ processed_layers += 1
+
+ while processed_layers < n_layers - 1:
+ # fill up with std layers
+ strides.append(1)
+ dilations.append(current_dilation)
+ paddings.append(current_dilation)
+ processed_layers += 1
+
+ # final layer
+ strides.append(final_stride)
+ current_dilation * final_dilation
+ dilations.append(current_dilation)
+ paddings.append(current_dilation)
+ processed_layers += 1
+
+ assert processed_layers == n_layers
+
+ return strides, dilations, paddings
+
+ t_strides, t_dilations, t_paddings = process_dimension(num_layers, t_stretch, t_down)
+ f_strides, f_dilations, f_paddings = process_dimension(num_layers, f_stretch, f_down)
+
+ plan = []
+
+ for i in range(num_layers):
+ plan.append([
+ (f_strides[i], t_strides[i]),
+ (f_dilations[i], t_dilations[i]),
+ (f_paddings[i], t_paddings[i]),
+ ])
+
+ return plan
+
+
+class DiscriminatorExperimental(SpecDiscriminatorBase):
+
+ def __init__(self,
+ resolution,
+ fs=16000,
+ freq_roi=[50, 7400],
+ noise_gain=0,
+ num_channels=16,
+ max_channels=512,
+ num_layers=5,
+ use_spectral_norm=False):
+
+ norm_f = weight_norm if use_spectral_norm == False else spectral_norm
+
+ self.num_channels = num_channels
+ self.num_channels_max = max_channels
+ self.num_layers = num_layers
+
+ layers = []
+ stride = (2, 1)
+ padding= (1, 1)
+ in_channels = 1 + 2
+ out_channels = self.num_channels
+ for _ in range(self.num_layers):
+ layers.append(
+ nn.Sequential(
+ FrequencyPositionalEmbedding(),
+ norm_f(nn.Conv2d(in_channels, out_channels, (3, 3), stride=stride, padding=padding)),
+ nn.ReLU(inplace=True)
+ )
+ )
+ in_channels = out_channels + 2
+ out_channels = min(2 * out_channels, self.num_channels_max)
+
+ layers.append(
+ nn.Sequential(
+ FrequencyPositionalEmbedding(),
+ norm_f(nn.Conv2d(in_channels, 1, (3, 3), padding=padding)),
+ nn.Sigmoid()
+ )
+ )
+
+ super().__init__(layers=layers, resolution=resolution, fs=fs, freq_roi=freq_roi, noise_gain=noise_gain)
+
+ # bias biases
+ bias_val = 0.1
+ with torch.no_grad():
+ for name, weight in self.named_parameters():
+ if 'bias' in name:
+ weight = weight + bias_val
+
+
+configs = {
+ 'f_down': {
+ 'stretch' : {
+ 64 : (0, 0),
+ 128: (1, 0),
+ 256: (2, 0),
+ 512: (3, 0),
+ 1024: (4, 0),
+ 2048: (5, 0)
+ },
+ 'down' : {
+ 64 : (0, 0),
+ 128: (1, 0),
+ 256: (2, 0),
+ 512: (3, 0),
+ 1024: (4, 0),
+ 2048: (5, 0)
+ }
+ },
+ 'ft_down': {
+ 'stretch' : {
+ 64 : (0, 4),
+ 128: (1, 3),
+ 256: (2, 2),
+ 512: (3, 1),
+ 1024: (4, 0),
+ 2048: (5, 0)
+ },
+ 'down' : {
+ 64 : (0, 4),
+ 128: (1, 3),
+ 256: (2, 2),
+ 512: (3, 1),
+ 1024: (4, 0),
+ 2048: (5, 0)
+ }
+ },
+ 'dilated': {
+ 'stretch' : {
+ 64 : (0, 4),
+ 128: (1, 3),
+ 256: (2, 2),
+ 512: (3, 1),
+ 1024: (4, 0),
+ 2048: (5, 0)
+ },
+ 'down' : {
+ 64 : (0, 0),
+ 128: (0, 0),
+ 256: (0, 0),
+ 512: (0, 0),
+ 1024: (0, 0),
+ 2048: (0, 0)
+ }
+ },
+ 'mixed': {
+ 'stretch' : {
+ 64 : (0, 4),
+ 128: (1, 3),
+ 256: (2, 2),
+ 512: (3, 1),
+ 1024: (4, 0),
+ 2048: (5, 0)
+ },
+ 'down' : {
+ 64 : (0, 0),
+ 128: (1, 0),
+ 256: (2, 0),
+ 512: (3, 0),
+ 1024: (4, 0),
+ 2048: (5, 0)
+ }
+ },
+}
+
+
+class DiscriminatorMagFree(SpecDiscriminatorBase):
+
+ def __init__(self,
+ resolution,
+ fs=16000,
+ freq_roi=[50, 7400],
+ noise_gain=0,
+ num_channels=16,
+ max_channels=256,
+ num_layers=5,
+ use_spectral_norm=False,
+ design=None):
+
+ if design is None:
+ raise ValueError('error: arch required in DiscriminatorMagFree')
+
+ norm_f = weight_norm if use_spectral_norm == False else spectral_norm
+
+ stretch = configs[design]['stretch'][resolution[0]]
+ down = configs[design]['down'][resolution[0]]
+
+ self.num_channels = num_channels
+ self.num_channels_max = max_channels
+ self.num_layers = num_layers
+ self.stretch = stretch
+ self.down = down
+
+ layers = []
+ plan = create_3x3_conv_plan(num_layers + 1, stretch[0], down[0], stretch[1], down[1])
+ in_channels = 1 + 2
+ out_channels = self.num_channels
+ for i in range(self.num_layers):
+ layers.append(
+ nn.Sequential(
+ FrequencyPositionalEmbedding(),
+ norm_f(nn.Conv2d(in_channels, out_channels, (3, 3), stride=plan[i][0], dilation=plan[i][1], padding=plan[i][2])),
+ nn.ReLU(inplace=True)
+ )
+ )
+ in_channels = out_channels + 2
+ # product over strides
+ channel_factor = plan[i][0][0] * plan[i][0][1]
+ out_channels = min(channel_factor * out_channels, self.num_channels_max)
+
+ layers.append(
+ nn.Sequential(
+ FrequencyPositionalEmbedding(),
+ norm_f(nn.Conv2d(in_channels, 1, (3, 3), stride=plan[-1][0], dilation=plan[-1][1], padding=plan[-1][2])),
+ nn.Sigmoid()
+ )
+ )
+
+
+
+ # for layer in layers:
+ # print(layer)
+
+ # print("end\n\n")
+
+ super().__init__(layers=layers, resolution=resolution, fs=fs, freq_roi=freq_roi, noise_gain=noise_gain)
+
+ # bias biases
+ bias_val = 0.1
+ with torch.no_grad():
+ for name, weight in self.named_parameters():
+ if 'bias' in name:
+ weight = weight + bias_val
+
+class DiscriminatorMagFreqPosition(SpecDiscriminatorBase):
+
+ def __init__(self,
+ resolution,
+ fs=16000,
+ freq_roi=[50, 7400],
+ noise_gain=0,
+ num_channels=16,
+ max_channels=512,
+ num_layers=5,
+ use_spectral_norm=False):
+
+ norm_f = weight_norm if use_spectral_norm == False else spectral_norm
+
+ self.num_channels = num_channels
+ self.num_channels_max = max_channels
+ self.num_layers = num_layers
+
+ layers = []
+ stride = (2, 1)
+ padding= (1, 1)
+ in_channels = 1 + 2
+ out_channels = self.num_channels
+ for _ in range(self.num_layers):
+ layers.append(
+ nn.Sequential(
+ FrequencyPositionalEmbedding(),
+ norm_f(nn.Conv2d(in_channels, out_channels, (3, 3), stride=stride, padding=padding)),
+ nn.LeakyReLU(0.2, inplace=True)
+ )
+ )
+ in_channels = out_channels + 2
+ out_channels = min(2 * out_channels, self.num_channels_max)
+
+ layers.append(
+ nn.Sequential(
+ FrequencyPositionalEmbedding(),
+ norm_f(nn.Conv2d(in_channels, 1, (3, 3), padding=padding))
+ )
+ )
+
+ super().__init__(layers=layers, resolution=resolution, fs=fs, freq_roi=freq_roi, noise_gain=noise_gain)
+
+
+
+class DiscriminatorMag2dPositional(SpecDiscriminatorBase):
+
+ def __init__(self,
+ resolution,
+ fs=16000,
+ freq_roi=[50, 7400],
+ noise_gain=0,
+ num_channels=16,
+ max_channels=512,
+ num_layers=5,
+ d=5,
+ use_spectral_norm=False):
+
+ norm_f = weight_norm if use_spectral_norm == False else spectral_norm
+ self.resolution = resolution
+ self.num_channels = num_channels
+ self.num_channels_max = max_channels
+ self.num_layers = num_layers
+ self.d = d
+ embedding_dim = 4 * d
+
+
+ layers = []
+ stride = (2, 2)
+ padding= (1, 1)
+ in_channels = 1 + embedding_dim
+ out_channels = self.num_channels
+ for _ in range(self.num_layers):
+ layers.append(
+ nn.Sequential(
+ PositionalEmbedding2D(d),
+ norm_f(nn.Conv2d(in_channels, out_channels, (3, 3), stride=stride, padding=padding)),
+ nn.LeakyReLU(0.2, inplace=True)
+ )
+ )
+ in_channels = out_channels + embedding_dim
+ out_channels = min(2 * out_channels, self.num_channels_max)
+
+
+ layers.append(
+ nn.Sequential(
+ PositionalEmbedding2D(),
+ norm_f(nn.Conv2d(in_channels, 1, (3, 3), padding=padding))
+ )
+ )
+
+ super().__init__(layers=layers, resolution=resolution, fs=fs, freq_roi=freq_roi, noise_gain=noise_gain)
+
+
+
+class DiscriminatorMag(SpecDiscriminatorBase):
+ def __init__(self,
+ resolution,
+ fs=16000,
+ freq_roi=[50, 7400],
+ noise_gain=0,
+ num_channels=32,
+ num_layers=5,
+ use_spectral_norm=False):
+
+ norm_f = weight_norm if use_spectral_norm == False else spectral_norm
+
+ self.num_channels = num_channels
+ self.num_layers = num_layers
+
+ layers = []
+ stride = (1, 1)
+ padding= (1, 1)
+ in_channels = 1
+ out_channels = self.num_channels
+ for _ in range(self.num_layers):
+ layers.append(
+ nn.Sequential(
+ norm_f(nn.Conv2d(in_channels, out_channels, (3, 3), stride=stride, padding=padding)),
+ nn.LeakyReLU(0.2, inplace=True)
+ )
+ )
+ in_channels = out_channels
+
+ layers.append(norm_f(nn.Conv2d(in_channels, 1, (3, 3), padding=padding)))
+
+ super().__init__(layers=layers, resolution=resolution, fs=fs, freq_roi=freq_roi, noise_gain=noise_gain)
+
+
+discriminators = {
+ 'mag': DiscriminatorMag,
+ 'freqpos': DiscriminatorMagFreqPosition,
+ '2dpos': DiscriminatorMag2dPositional,
+ 'experimental': DiscriminatorExperimental,
+ 'free': DiscriminatorMagFree
+}
+
+class TFDMultiResolutionDiscriminator(torch.nn.Module):
+ def __init__(self,
+ fft_sizes_16k=[64, 128, 256, 512, 1024, 2048],
+ architecture='mag',
+ fs=16000,
+ freq_roi=[50, 7400],
+ noise_gain=0,
+ use_spectral_norm=False,
+ **kwargs):
+
+ super().__init__()
+
+
+ fft_sizes = [int(round(fft_size_16k * fs / 16000)) for fft_size_16k in fft_sizes_16k]
+
+ resolutions = [[n_fft, n_fft // 4, n_fft] for n_fft in fft_sizes]
+
+
+ Disc = discriminators[architecture]
+
+ discs = [Disc(resolutions[i], fs=fs, freq_roi=freq_roi, noise_gain=noise_gain, use_spectral_norm=use_spectral_norm, **kwargs) for i in range(len(resolutions))]
+
+ self.discriminators = nn.ModuleList(discs)
+
+ def forward(self, y):
+ outputs = []
+
+ for disc in self.discriminators:
+ outputs.append(disc(y))
+
+ return outputs
+
+
+class FWGAN_disc_wrapper(nn.Module):
+ def __init__(self, disc):
+ super().__init__()
+
+ self.disc = disc
+
+ def forward(self, y, y_hat):
+
+ out_real = self.disc(y)
+ out_fake = self.disc(y_hat)
+
+ y_d_rs = []
+ y_d_gs = []
+ fmap_rs = []
+ fmap_gs = []
+
+ for y_real, y_fake in zip(out_real, out_fake):
+ y_d_rs.append(y_real[-1])
+ y_d_gs.append(y_fake[-1])
+ fmap_rs.append(y_real[:-1])
+ fmap_gs.append(y_fake[:-1])
+
+ return y_d_rs, y_d_gs, fmap_rs, fmap_gs
diff --git a/dnn/torch/osce/models/lace.py b/dnn/torch/osce/models/lace.py
new file mode 100644
index 00000000..51d65c3e
--- /dev/null
+++ b/dnn/torch/osce/models/lace.py
@@ -0,0 +1,190 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+import numpy as np
+
+from utils.layers.limited_adaptive_comb1d import LimitedAdaptiveComb1d
+from utils.layers.limited_adaptive_conv1d import LimitedAdaptiveConv1d
+
+from models.nns_base import NNSBase
+from models.silk_feature_net_pl import SilkFeatureNetPL
+from models.silk_feature_net import SilkFeatureNet
+from .scale_embedding import ScaleEmbedding
+
+import sys
+sys.path.append('../dnntools')
+
+from dnntools.sparsification import create_sparsifier
+
+
+class LACE(NNSBase):
+ """ Linear-Adaptive Coding Enhancer """
+ FRAME_SIZE=80
+
+ def __init__(self,
+ num_features=47,
+ pitch_embedding_dim=64,
+ cond_dim=256,
+ pitch_max=257,
+ kernel_size=15,
+ preemph=0.85,
+ skip=91,
+ comb_gain_limit_db=-6,
+ global_gain_limits_db=[-6, 6],
+ conv_gain_limits_db=[-6, 6],
+ numbits_range=[50, 650],
+ numbits_embedding_dim=8,
+ hidden_feature_dim=64,
+ partial_lookahead=True,
+ norm_p=2,
+ softquant=False,
+ sparsify=False,
+ sparsification_schedule=[10000, 30000, 100],
+ sparsification_density=0.5,
+ apply_weight_norm=False):
+
+ super().__init__(skip=skip, preemph=preemph)
+
+
+ self.num_features = num_features
+ self.cond_dim = cond_dim
+ self.pitch_max = pitch_max
+ self.pitch_embedding_dim = pitch_embedding_dim
+ self.kernel_size = kernel_size
+ self.preemph = preemph
+ self.skip = skip
+ self.numbits_range = numbits_range
+ self.numbits_embedding_dim = numbits_embedding_dim
+ self.hidden_feature_dim = hidden_feature_dim
+ self.partial_lookahead = partial_lookahead
+
+ # pitch embedding
+ self.pitch_embedding = nn.Embedding(pitch_max + 1, pitch_embedding_dim)
+
+ # numbits embedding
+ self.numbits_embedding = ScaleEmbedding(numbits_embedding_dim, *numbits_range, logscale=True)
+
+ # feature net
+ if partial_lookahead:
+ self.feature_net = SilkFeatureNetPL(num_features + pitch_embedding_dim + 2 * numbits_embedding_dim, cond_dim, hidden_feature_dim, softquant=softquant, sparsify=sparsify, sparsification_density=sparsification_density, apply_weight_norm=apply_weight_norm)
+ else:
+ self.feature_net = SilkFeatureNet(num_features + pitch_embedding_dim + 2 * numbits_embedding_dim, cond_dim)
+
+ # comb filters
+ left_pad = self.kernel_size // 2
+ right_pad = self.kernel_size - 1 - left_pad
+ self.cf1 = LimitedAdaptiveComb1d(self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, overlap_size=40, use_bias=False, padding=[left_pad, right_pad], max_lag=pitch_max + 1, gain_limit_db=comb_gain_limit_db, global_gain_limits_db=global_gain_limits_db, norm_p=norm_p, softquant=softquant, apply_weight_norm=apply_weight_norm)
+ self.cf2 = LimitedAdaptiveComb1d(self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, overlap_size=40, use_bias=False, padding=[left_pad, right_pad], max_lag=pitch_max + 1, gain_limit_db=comb_gain_limit_db, global_gain_limits_db=global_gain_limits_db, norm_p=norm_p, softquant=softquant, apply_weight_norm=apply_weight_norm)
+
+ # spectral shaping
+ self.af1 = LimitedAdaptiveConv1d(1, 1, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p, softquant=softquant, apply_weight_norm=apply_weight_norm)
+
+ if sparsify:
+ self.sparsifier = create_sparsifier(self, *sparsification_schedule)
+
+ def flop_count(self, rate=16000, verbose=False):
+
+ frame_rate = rate / self.FRAME_SIZE
+
+ # feature net
+ feature_net_flops = self.feature_net.flop_count(frame_rate)
+ comb_flops = self.cf1.flop_count(rate) + self.cf2.flop_count(rate)
+ af_flops = self.af1.flop_count(rate)
+
+ if verbose:
+ print(f"feature net: {feature_net_flops / 1e6} MFLOPS")
+ print(f"comb filters: {comb_flops / 1e6} MFLOPS")
+ print(f"adaptive conv: {af_flops / 1e6} MFLOPS")
+
+ return feature_net_flops + comb_flops + af_flops
+
+ def forward(self, x, features, periods, numbits, debug=False):
+
+ periods = periods.squeeze(-1)
+ pitch_embedding = self.pitch_embedding(periods)
+ numbits_embedding = self.numbits_embedding(numbits).flatten(2)
+
+ full_features = torch.cat((features, pitch_embedding, numbits_embedding), dim=-1)
+ cf = self.feature_net(full_features)
+
+ y = self.cf1(x, cf, periods, debug=debug)
+
+ y = self.cf2(y, cf, periods, debug=debug)
+
+ y = self.af1(y, cf, debug=debug)
+
+ return y
+
+ def get_impulse_responses(self, features, periods, numbits):
+ """ generates impoulse responses on frame centers (input without batch dimension) """
+
+ num_frames = features.size(0)
+ batch_size = 32
+ max_len = 2 * (self.pitch_max + self.kernel_size) + 10
+
+ # spread out some pulses
+ x = np.zeros((batch_size, 1, num_frames * self.FRAME_SIZE))
+ for b in range(batch_size):
+ x[b, :, self.FRAME_SIZE // 2 + b * self.FRAME_SIZE :: batch_size * self.FRAME_SIZE] = 1
+
+ # prepare input
+ x = torch.from_numpy(x).float().to(features.device)
+ features = torch.repeat_interleave(features.unsqueeze(0), batch_size, 0)
+ periods = torch.repeat_interleave(periods.unsqueeze(0), batch_size, 0)
+ numbits = torch.repeat_interleave(numbits.unsqueeze(0), batch_size, 0)
+
+ # run network
+ with torch.no_grad():
+ periods = periods.squeeze(-1)
+ pitch_embedding = self.pitch_embedding(periods)
+ numbits_embedding = self.numbits_embedding(numbits).flatten(2)
+ full_features = torch.cat((features, pitch_embedding, numbits_embedding), dim=-1)
+ cf = self.feature_net(full_features)
+ y = self.cf1(x, cf, periods, debug=False)
+ y = self.cf2(y, cf, periods, debug=False)
+ y = self.af1(y, cf, debug=False)
+
+ # collect responses
+ y = y.detach().squeeze().cpu().numpy()
+ cut_frames = (max_len + self.FRAME_SIZE - 1) // self.FRAME_SIZE
+ num_responses = num_frames - cut_frames
+ responses = np.zeros((num_responses, max_len))
+
+ for i in range(num_responses):
+ b = i % batch_size
+ start = self.FRAME_SIZE // 2 + i * self.FRAME_SIZE
+ stop = start + max_len
+
+ responses[i, :] = y[b, start:stop]
+
+ return responses
diff --git a/dnn/torch/osce/models/lavoce.py b/dnn/torch/osce/models/lavoce.py
new file mode 100644
index 00000000..fcfdc8bf
--- /dev/null
+++ b/dnn/torch/osce/models/lavoce.py
@@ -0,0 +1,274 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+import numpy as np
+
+from utils.layers.limited_adaptive_comb1d import LimitedAdaptiveComb1d
+from utils.layers.limited_adaptive_conv1d import LimitedAdaptiveConv1d
+from utils.layers.td_shaper import TDShaper
+from utils.layers.noise_shaper import NoiseShaper
+from utils.complexity import _conv1d_flop_count
+from utils.endoscopy import write_data
+
+from models.nns_base import NNSBase
+from models.lpcnet_feature_net import LPCNetFeatureNet
+from .scale_embedding import ScaleEmbedding
+
+def print_channels(y, prefix="", name="", rate=16000):
+ num_channels = y.size(1)
+ for i in range(num_channels):
+ channel_name = f"{prefix}_c{i:02d}"
+ if len(name) > 0: channel_name += "_" + name
+ ch = y[0,i,:].detach().cpu().numpy()
+ ch = ((2**14) * ch / np.max(ch)).astype(np.int16)
+ write_data(channel_name, ch, rate)
+
+
+
+class LaVoce(nn.Module):
+ """ Linear-Adaptive VOCodEr """
+ FEATURE_FRAME_SIZE=160
+ FRAME_SIZE=80
+
+ def __init__(self,
+ num_features=20,
+ pitch_embedding_dim=64,
+ cond_dim=256,
+ pitch_max=300,
+ kernel_size=15,
+ preemph=0.85,
+ comb_gain_limit_db=-6,
+ global_gain_limits_db=[-6, 6],
+ conv_gain_limits_db=[-6, 6],
+ norm_p=2,
+ avg_pool_k=4,
+ pulses=False,
+ innovate1=True,
+ innovate2=False,
+ innovate3=False,
+ ftrans_k=2):
+
+ super().__init__()
+
+
+ self.num_features = num_features
+ self.cond_dim = cond_dim
+ self.pitch_max = pitch_max
+ self.pitch_embedding_dim = pitch_embedding_dim
+ self.kernel_size = kernel_size
+ self.preemph = preemph
+ self.pulses = pulses
+ self.ftrans_k = ftrans_k
+
+ assert self.FEATURE_FRAME_SIZE % self.FRAME_SIZE == 0
+ self.upsamp_factor = self.FEATURE_FRAME_SIZE // self.FRAME_SIZE
+
+ # pitch embedding
+ self.pitch_embedding = nn.Embedding(pitch_max + 1, pitch_embedding_dim)
+
+ # feature net
+ self.feature_net = LPCNetFeatureNet(num_features + pitch_embedding_dim, cond_dim, self.upsamp_factor)
+
+ # noise shaper
+ self.noise_shaper = NoiseShaper(cond_dim, self.FRAME_SIZE)
+
+ # comb filters
+ left_pad = self.kernel_size // 2
+ right_pad = self.kernel_size - 1 - left_pad
+ self.cf1 = LimitedAdaptiveComb1d(self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, overlap_size=40, use_bias=False, padding=[left_pad, right_pad], max_lag=pitch_max + 1, gain_limit_db=comb_gain_limit_db, global_gain_limits_db=global_gain_limits_db, norm_p=norm_p)
+ self.cf2 = LimitedAdaptiveComb1d(self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, overlap_size=40, use_bias=False, padding=[left_pad, right_pad], max_lag=pitch_max + 1, gain_limit_db=comb_gain_limit_db, global_gain_limits_db=global_gain_limits_db, norm_p=norm_p)
+
+
+ self.af_prescale = LimitedAdaptiveConv1d(2, 1, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+ self.af_mix = LimitedAdaptiveConv1d(2, 2, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+
+ # spectral shaping
+ self.af1 = LimitedAdaptiveConv1d(1, 2, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+
+ # non-linear transforms
+ self.tdshape1 = TDShaper(cond_dim, frame_size=self.FRAME_SIZE, avg_pool_k=avg_pool_k, innovate=innovate1)
+ self.tdshape2 = TDShaper(cond_dim, frame_size=self.FRAME_SIZE, avg_pool_k=avg_pool_k, innovate=innovate2)
+ self.tdshape3 = TDShaper(cond_dim, frame_size=self.FRAME_SIZE, avg_pool_k=avg_pool_k, innovate=innovate3)
+
+ # combinators
+ self.af2 = LimitedAdaptiveConv1d(2, 2, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+ self.af3 = LimitedAdaptiveConv1d(2, 1, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+ self.af4 = LimitedAdaptiveConv1d(2, 1, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+
+ # feature transforms
+ self.post_cf1 = nn.Conv1d(cond_dim, cond_dim, ftrans_k)
+ self.post_cf2 = nn.Conv1d(cond_dim, cond_dim, ftrans_k)
+ self.post_af1 = nn.Conv1d(cond_dim, cond_dim, ftrans_k)
+ self.post_af2 = nn.Conv1d(cond_dim, cond_dim, ftrans_k)
+ self.post_af3 = nn.Conv1d(cond_dim, cond_dim, ftrans_k)
+
+
+ def create_phase_signals(self, periods):
+
+ batch_size = periods.size(0)
+ progression = torch.arange(1, self.FRAME_SIZE + 1, dtype=periods.dtype, device=periods.device).view((1, -1))
+ progression = torch.repeat_interleave(progression, batch_size, 0)
+
+ phase0 = torch.zeros(batch_size, dtype=periods.dtype, device=periods.device).unsqueeze(-1)
+ chunks = []
+ for sframe in range(periods.size(1)):
+ f = (2.0 * torch.pi / periods[:, sframe]).unsqueeze(-1)
+
+ if self.pulses:
+ alpha = torch.cos(f).view(batch_size, 1, 1)
+ chunk_sin = torch.sin(f * progression + phase0).view(batch_size, 1, self.FRAME_SIZE)
+ pulse_a = torch.relu(chunk_sin - alpha) / (1 - alpha)
+ pulse_b = torch.relu(-chunk_sin - alpha) / (1 - alpha)
+
+ chunk = torch.cat((pulse_a, pulse_b), dim = 1)
+ else:
+ chunk_sin = torch.sin(f * progression + phase0).view(batch_size, 1, self.FRAME_SIZE)
+ chunk_cos = torch.cos(f * progression + phase0).view(batch_size, 1, self.FRAME_SIZE)
+
+ chunk = torch.cat((chunk_sin, chunk_cos), dim = 1)
+
+ phase0 = phase0 + self.FRAME_SIZE * f
+
+ chunks.append(chunk)
+
+ phase_signals = torch.cat(chunks, dim=-1)
+
+ return phase_signals
+
+ def flop_count(self, rate=16000, verbose=False):
+
+ frame_rate = rate / self.FRAME_SIZE
+
+ # feature net
+ feature_net_flops = self.feature_net.flop_count(frame_rate)
+ comb_flops = self.cf1.flop_count(rate) + self.cf2.flop_count(rate)
+ af_flops = self.af1.flop_count(rate) + self.af2.flop_count(rate) + self.af3.flop_count(rate) + self.af4.flop_count(rate) + self.af_prescale.flop_count(rate) + self.af_mix.flop_count(rate)
+ feature_flops = (_conv1d_flop_count(self.post_cf1, frame_rate) + _conv1d_flop_count(self.post_cf2, frame_rate)
+ + _conv1d_flop_count(self.post_af1, frame_rate) + _conv1d_flop_count(self.post_af2, frame_rate) + _conv1d_flop_count(self.post_af3, frame_rate))
+
+ if verbose:
+ print(f"feature net: {feature_net_flops / 1e6} MFLOPS")
+ print(f"comb filters: {comb_flops / 1e6} MFLOPS")
+ print(f"adaptive conv: {af_flops / 1e6} MFLOPS")
+ print(f"feature transforms: {feature_flops / 1e6} MFLOPS")
+
+ return feature_net_flops + comb_flops + af_flops + feature_flops
+
+ def feature_transform(self, f, layer):
+ f = f.permute(0, 2, 1)
+ f = F.pad(f, [self.ftrans_k - 1, 0])
+ f = torch.tanh(layer(f))
+ return f.permute(0, 2, 1)
+
+ def forward(self, features, periods, debug=False):
+
+ periods = periods.squeeze(-1)
+ pitch_embedding = self.pitch_embedding(periods)
+
+ full_features = torch.cat((features, pitch_embedding), dim=-1)
+ cf = self.feature_net(full_features)
+
+ # upsample periods
+ periods = torch.repeat_interleave(periods, self.upsamp_factor, 1)
+
+ # pre-net
+ ref_phase = torch.tanh(self.create_phase_signals(periods))
+ if debug: print_channels(ref_phase, prefix="lavoce_01", name="pulse")
+ x = self.af_prescale(ref_phase, cf)
+ noise = self.noise_shaper(cf)
+ if debug: print_channels(torch.cat((x, noise), dim=1), prefix="lavoce_02", name="inputs")
+ y = self.af_mix(torch.cat((x, noise), dim=1), cf)
+ if debug: print_channels(y, prefix="lavoce_03", name="postselect1")
+
+ # temporal shaping + innovating
+ y1 = y[:, 0:1, :]
+ y2 = self.tdshape1(y[:, 1:2, :], cf)
+ if debug: print_channels(y2, prefix="lavoce_04", name="postshape1")
+ y = torch.cat((y1, y2), dim=1)
+ y = self.af2(y, cf, debug=debug)
+ if debug: print_channels(y, prefix="lavoce_05", name="postselect2")
+ cf = self.feature_transform(cf, self.post_af2)
+
+ y1 = y[:, 0:1, :]
+ y2 = self.tdshape2(y[:, 1:2, :], cf)
+ if debug: print_channels(y2, prefix="lavoce_06", name="postshape2")
+ y = torch.cat((y1, y2), dim=1)
+ y = self.af3(y, cf, debug=debug)
+ if debug: print_channels(y, prefix="lavoce_07", name="postmix1")
+ cf = self.feature_transform(cf, self.post_af3)
+
+ # spectral shaping
+ y = self.cf1(y, cf, periods, debug=debug)
+ if debug: print_channels(y, prefix="lavoce_08", name="postcomb1")
+ cf = self.feature_transform(cf, self.post_cf1)
+
+ y = self.cf2(y, cf, periods, debug=debug)
+ if debug: print_channels(y, prefix="lavoce_09", name="postcomb2")
+ cf = self.feature_transform(cf, self.post_cf2)
+
+ y = self.af1(y, cf, debug=debug)
+ if debug: print_channels(y, prefix="lavoce_10", name="postselect3")
+ cf = self.feature_transform(cf, self.post_af1)
+
+ # final temporal env adjustment
+ y1 = y[:, 0:1, :]
+ y2 = self.tdshape3(y[:, 1:2, :], cf)
+ if debug: print_channels(y2, prefix="lavoce_11", name="postshape3")
+ y = torch.cat((y1, y2), dim=1)
+ y = self.af4(y, cf, debug=debug)
+ if debug: print_channels(y, prefix="lavoce_12", name="postmix2")
+
+ return y
+
+ def process(self, features, periods, debug=False):
+
+ self.eval()
+ device = next(iter(self.parameters())).device
+ with torch.no_grad():
+
+ # run model
+ f = features.unsqueeze(0).to(device)
+ p = periods.unsqueeze(0).to(device)
+
+ y = self.forward(f, p, debug=debug).squeeze()
+
+ # deemphasis
+ if self.preemph > 0:
+ for i in range(len(y) - 1):
+ y[i + 1] += self.preemph * y[i]
+
+ # clip to valid range
+ out = torch.clip((2**15) * y, -2**15, 2**15 - 1).short()
+
+ return out \ No newline at end of file
diff --git a/dnn/torch/osce/models/lavoce_400.py b/dnn/torch/osce/models/lavoce_400.py
new file mode 100644
index 00000000..fe8263be
--- /dev/null
+++ b/dnn/torch/osce/models/lavoce_400.py
@@ -0,0 +1,254 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+import numpy as np
+
+from utils.layers.limited_adaptive_comb1d import LimitedAdaptiveComb1d
+from utils.layers.limited_adaptive_conv1d import LimitedAdaptiveConv1d
+from utils.layers.td_shaper import TDShaper
+from utils.layers.noise_shaper import NoiseShaper
+from utils.complexity import _conv1d_flop_count
+from utils.endoscopy import write_data
+
+from models.nns_base import NNSBase
+from models.lpcnet_feature_net import LPCNetFeatureNet
+from .scale_embedding import ScaleEmbedding
+
+class LaVoce400(nn.Module):
+ """ Linear-Adaptive VOCodEr """
+ FEATURE_FRAME_SIZE=160
+ FRAME_SIZE=40
+
+ def __init__(self,
+ num_features=20,
+ pitch_embedding_dim=64,
+ cond_dim=256,
+ pitch_max=300,
+ kernel_size=15,
+ preemph=0.85,
+ comb_gain_limit_db=-6,
+ global_gain_limits_db=[-6, 6],
+ conv_gain_limits_db=[-6, 6],
+ norm_p=2,
+ avg_pool_k=4,
+ pulses=False):
+
+ super().__init__()
+
+
+ self.num_features = num_features
+ self.cond_dim = cond_dim
+ self.pitch_max = pitch_max
+ self.pitch_embedding_dim = pitch_embedding_dim
+ self.kernel_size = kernel_size
+ self.preemph = preemph
+ self.pulses = pulses
+
+ assert self.FEATURE_FRAME_SIZE % self.FRAME_SIZE == 0
+ self.upsamp_factor = self.FEATURE_FRAME_SIZE // self.FRAME_SIZE
+
+ # pitch embedding
+ self.pitch_embedding = nn.Embedding(pitch_max + 1, pitch_embedding_dim)
+
+ # feature net
+ self.feature_net = LPCNetFeatureNet(num_features + pitch_embedding_dim, cond_dim, self.upsamp_factor)
+
+ # noise shaper
+ self.noise_shaper = NoiseShaper(cond_dim, self.FRAME_SIZE)
+
+ # comb filters
+ left_pad = self.kernel_size // 2
+ right_pad = self.kernel_size - 1 - left_pad
+ self.cf1 = LimitedAdaptiveComb1d(self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, overlap_size=20, use_bias=False, padding=[left_pad, right_pad], max_lag=pitch_max + 1, gain_limit_db=comb_gain_limit_db, global_gain_limits_db=global_gain_limits_db, norm_p=norm_p)
+ self.cf2 = LimitedAdaptiveComb1d(self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, overlap_size=20, use_bias=False, padding=[left_pad, right_pad], max_lag=pitch_max + 1, gain_limit_db=comb_gain_limit_db, global_gain_limits_db=global_gain_limits_db, norm_p=norm_p)
+
+
+ self.af_prescale = LimitedAdaptiveConv1d(2, 1, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+ self.af_mix = LimitedAdaptiveConv1d(2, 2, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+
+ # spectral shaping
+ self.af1 = LimitedAdaptiveConv1d(1, 2, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+
+ # non-linear transforms
+ self.tdshape1 = TDShaper(cond_dim, frame_size=self.FRAME_SIZE, avg_pool_k=avg_pool_k, innovate=True)
+ self.tdshape2 = TDShaper(cond_dim, frame_size=self.FRAME_SIZE, avg_pool_k=avg_pool_k)
+ self.tdshape3 = TDShaper(cond_dim, frame_size=self.FRAME_SIZE, avg_pool_k=avg_pool_k)
+
+ # combinators
+ self.af2 = LimitedAdaptiveConv1d(2, 2, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+ self.af3 = LimitedAdaptiveConv1d(2, 1, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+ self.af4 = LimitedAdaptiveConv1d(2, 1, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+
+ # feature transforms
+ self.post_cf1 = nn.Conv1d(cond_dim, cond_dim, 2)
+ self.post_cf2 = nn.Conv1d(cond_dim, cond_dim, 2)
+ self.post_af1 = nn.Conv1d(cond_dim, cond_dim, 2)
+ self.post_af2 = nn.Conv1d(cond_dim, cond_dim, 2)
+ self.post_af3 = nn.Conv1d(cond_dim, cond_dim, 2)
+
+
+ def create_phase_signals(self, periods):
+
+ batch_size = periods.size(0)
+ progression = torch.arange(1, self.FRAME_SIZE + 1, dtype=periods.dtype, device=periods.device).view((1, -1))
+ progression = torch.repeat_interleave(progression, batch_size, 0)
+
+ phase0 = torch.zeros(batch_size, dtype=periods.dtype, device=periods.device).unsqueeze(-1)
+ chunks = []
+ for sframe in range(periods.size(1)):
+ f = (2.0 * torch.pi / periods[:, sframe]).unsqueeze(-1)
+
+ if self.pulses:
+ alpha = torch.cos(f).view(batch_size, 1, 1)
+ chunk_sin = torch.sin(f * progression + phase0).view(batch_size, 1, self.FRAME_SIZE)
+ pulse_a = torch.relu(chunk_sin - alpha) / (1 - alpha)
+ pulse_b = torch.relu(-chunk_sin - alpha) / (1 - alpha)
+
+ chunk = torch.cat((pulse_a, pulse_b), dim = 1)
+ else:
+ chunk_sin = torch.sin(f * progression + phase0).view(batch_size, 1, self.FRAME_SIZE)
+ chunk_cos = torch.cos(f * progression + phase0).view(batch_size, 1, self.FRAME_SIZE)
+
+ chunk = torch.cat((chunk_sin, chunk_cos), dim = 1)
+
+ phase0 = phase0 + self.FRAME_SIZE * f
+
+ chunks.append(chunk)
+
+ phase_signals = torch.cat(chunks, dim=-1)
+
+ return phase_signals
+
+ def flop_count(self, rate=16000, verbose=False):
+
+ frame_rate = rate / self.FRAME_SIZE
+
+ # feature net
+ feature_net_flops = self.feature_net.flop_count(frame_rate)
+ comb_flops = self.cf1.flop_count(rate) + self.cf2.flop_count(rate)
+ af_flops = self.af1.flop_count(rate) + self.af2.flop_count(rate) + self.af3.flop_count(rate) + self.af4.flop_count(rate) + self.af_prescale.flop_count(rate) + self.af_mix.flop_count(rate)
+ feature_flops = (_conv1d_flop_count(self.post_cf1, frame_rate) + _conv1d_flop_count(self.post_cf2, frame_rate)
+ + _conv1d_flop_count(self.post_af1, frame_rate) + _conv1d_flop_count(self.post_af2, frame_rate) + _conv1d_flop_count(self.post_af3, frame_rate))
+
+ if verbose:
+ print(f"feature net: {feature_net_flops / 1e6} MFLOPS")
+ print(f"comb filters: {comb_flops / 1e6} MFLOPS")
+ print(f"adaptive conv: {af_flops / 1e6} MFLOPS")
+ print(f"feature transforms: {feature_flops / 1e6} MFLOPS")
+
+ return feature_net_flops + comb_flops + af_flops + feature_flops
+
+ def feature_transform(self, f, layer):
+ f = f.permute(0, 2, 1)
+ f = F.pad(f, [1, 0])
+ f = torch.tanh(layer(f))
+ return f.permute(0, 2, 1)
+
+ def forward(self, features, periods, debug=False):
+
+ periods = periods.squeeze(-1)
+ pitch_embedding = self.pitch_embedding(periods)
+
+ full_features = torch.cat((features, pitch_embedding), dim=-1)
+ cf = self.feature_net(full_features)
+
+ # upsample periods
+ periods = torch.repeat_interleave(periods, self.upsamp_factor, 1)
+
+ # pre-net
+ ref_phase = torch.tanh(self.create_phase_signals(periods))
+ x = self.af_prescale(ref_phase, cf)
+ noise = self.noise_shaper(cf)
+ y = self.af_mix(torch.cat((x, noise), dim=1), cf)
+
+ if debug:
+ ch0 = y[0,0,:].detach().cpu().numpy()
+ ch1 = y[0,1,:].detach().cpu().numpy()
+ ch0 = (2**15 * ch0 / np.max(ch0)).astype(np.int16)
+ ch1 = (2**15 * ch1 / np.max(ch1)).astype(np.int16)
+ write_data('prior_channel0', ch0, 16000)
+ write_data('prior_channel1', ch1, 16000)
+
+ # temporal shaping + innovating
+ y1 = y[:, 0:1, :]
+ y2 = self.tdshape1(y[:, 1:2, :], cf)
+ y = torch.cat((y1, y2), dim=1)
+ y = self.af2(y, cf, debug=debug)
+ cf = self.feature_transform(cf, self.post_af2)
+
+ y1 = y[:, 0:1, :]
+ y2 = self.tdshape2(y[:, 1:2, :], cf)
+ y = torch.cat((y1, y2), dim=1)
+ y = self.af3(y, cf, debug=debug)
+ cf = self.feature_transform(cf, self.post_af3)
+
+ # spectral shaping
+ y = self.cf1(y, cf, periods, debug=debug)
+ cf = self.feature_transform(cf, self.post_cf1)
+
+ y = self.cf2(y, cf, periods, debug=debug)
+ cf = self.feature_transform(cf, self.post_cf2)
+
+ y = self.af1(y, cf, debug=debug)
+ cf = self.feature_transform(cf, self.post_af1)
+
+ # final temporal env adjustment
+ y1 = y[:, 0:1, :]
+ y2 = self.tdshape3(y[:, 1:2, :], cf)
+ y = torch.cat((y1, y2), dim=1)
+ y = self.af4(y, cf, debug=debug)
+
+ return y
+
+ def process(self, features, periods, debug=False):
+
+ self.eval()
+ device = next(iter(self.parameters())).device
+ with torch.no_grad():
+
+ # run model
+ f = features.unsqueeze(0).to(device)
+ p = periods.unsqueeze(0).to(device)
+
+ y = self.forward(f, p, debug=debug).squeeze()
+
+ # deemphasis
+ if self.preemph > 0:
+ for i in range(len(y) - 1):
+ y[i + 1] += self.preemph * y[i]
+
+ # clip to valid range
+ out = torch.clip((2**15) * y, -2**15, 2**15 - 1).short()
+
+ return out \ No newline at end of file
diff --git a/dnn/torch/osce/models/lpcnet_feature_net.py b/dnn/torch/osce/models/lpcnet_feature_net.py
new file mode 100644
index 00000000..b637d748
--- /dev/null
+++ b/dnn/torch/osce/models/lpcnet_feature_net.py
@@ -0,0 +1,91 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+from utils.complexity import _conv1d_flop_count
+
+class LPCNetFeatureNet(nn.Module):
+
+ def __init__(self,
+ feature_dim=84,
+ num_channels=256,
+ upsamp_factor=2,
+ lookahead=True):
+
+ super().__init__()
+
+ self.feature_dim = feature_dim
+ self.num_channels = num_channels
+ self.upsamp_factor = upsamp_factor
+ self.lookahead = lookahead
+
+ self.conv1 = nn.Conv1d(feature_dim, num_channels, 3)
+ self.conv2 = nn.Conv1d(num_channels, num_channels, 3)
+
+ self.gru = nn.GRU(num_channels, num_channels, batch_first=True)
+
+ self.tconv = nn.ConvTranspose1d(num_channels, num_channels, upsamp_factor, upsamp_factor)
+
+ def flop_count(self, rate=100):
+ count = 0
+ for conv in self.conv1, self.conv2, self.tconv:
+ count += _conv1d_flop_count(conv, rate)
+
+ count += 2 * (3 * self.gru.input_size * self.gru.hidden_size + 3 * self.gru.hidden_size * self.gru.hidden_size) * rate
+
+ return count
+
+
+ def forward(self, features, state=None):
+ """ features shape: (batch_size, num_frames, feature_dim) """
+
+ batch_size = features.size(0)
+
+ if state is None:
+ state = torch.zeros((1, batch_size, self.num_channels), device=features.device)
+
+
+ features = features.permute(0, 2, 1)
+ if self.lookahead:
+ c = torch.tanh(self.conv1(F.pad(features, [1, 1])))
+ c = torch.tanh(self.conv2(F.pad(c, [2, 0])))
+ else:
+ c = torch.tanh(self.conv1(F.pad(features, [2, 0])))
+ c = torch.tanh(self.conv2(F.pad(c, [2, 0])))
+
+ c = torch.tanh(self.tconv(c))
+
+ c = c.permute(0, 2, 1)
+
+ c, _ = self.gru(c, state)
+
+ return c \ No newline at end of file
diff --git a/dnn/torch/osce/models/nns_base.py b/dnn/torch/osce/models/nns_base.py
new file mode 100644
index 00000000..6e667b96
--- /dev/null
+++ b/dnn/torch/osce/models/nns_base.py
@@ -0,0 +1,69 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+class NNSBase(nn.Module):
+
+ def __init__(self, skip=91, preemph=0.85):
+ super().__init__()
+
+ self.skip = skip
+ self.preemph = preemph
+
+ def process(self, sig, features, periods, numbits, debug=False):
+
+ self.eval()
+ has_numbits = 'numbits' in self.forward.__code__.co_varnames
+ device = next(iter(self.parameters())).device
+ with torch.no_grad():
+
+ # run model
+ x = sig.view(1, 1, -1).to(device)
+ f = features.unsqueeze(0).to(device)
+ p = periods.unsqueeze(0).to(device)
+ n = numbits.unsqueeze(0).to(device)
+
+ if has_numbits:
+ y = self.forward(x, f, p, n, debug=debug).squeeze()
+ else:
+ y = self.forward(x, f, p, debug=debug).squeeze()
+
+ # deemphasis
+ if self.preemph > 0:
+ for i in range(len(y) - 1):
+ y[i + 1] += self.preemph * y[i]
+
+ # delay compensation
+ y = torch.cat((y[self.skip:], torch.zeros(self.skip, dtype=y.dtype, device=y.device)))
+ out = torch.clip((2**15) * y, -2**15, 2**15 - 1).short()
+
+ return out \ No newline at end of file
diff --git a/dnn/torch/osce/models/no_lace.py b/dnn/torch/osce/models/no_lace.py
new file mode 100644
index 00000000..78c3a301
--- /dev/null
+++ b/dnn/torch/osce/models/no_lace.py
@@ -0,0 +1,218 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import numbers
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+from torch.nn.utils import weight_norm
+
+
+import numpy as np
+
+from utils.layers.limited_adaptive_comb1d import LimitedAdaptiveComb1d
+from utils.layers.limited_adaptive_conv1d import LimitedAdaptiveConv1d
+from utils.layers.td_shaper import TDShaper
+from utils.complexity import _conv1d_flop_count
+
+from models.nns_base import NNSBase
+from models.silk_feature_net_pl import SilkFeatureNetPL
+from models.silk_feature_net import SilkFeatureNet
+from .scale_embedding import ScaleEmbedding
+
+import sys
+sys.path.append('../dnntools')
+from dnntools.quantization import soft_quant
+from dnntools.sparsification import create_sparsifier, mark_for_sparsification
+
+class NoLACE(NNSBase):
+ """ Non-Linear Adaptive Coding Enhancer """
+ FRAME_SIZE=80
+
+ def __init__(self,
+ num_features=47,
+ pitch_embedding_dim=64,
+ cond_dim=256,
+ pitch_max=257,
+ kernel_size=15,
+ preemph=0.85,
+ skip=91,
+ comb_gain_limit_db=-6,
+ global_gain_limits_db=[-6, 6],
+ conv_gain_limits_db=[-6, 6],
+ numbits_range=[50, 650],
+ numbits_embedding_dim=8,
+ hidden_feature_dim=64,
+ partial_lookahead=True,
+ norm_p=2,
+ avg_pool_k=4,
+ pool_after=False,
+ softquant=False,
+ sparsify=False,
+ sparsification_schedule=[100, 1000, 100],
+ sparsification_density=0.5,
+ apply_weight_norm=False):
+
+ super().__init__(skip=skip, preemph=preemph)
+
+ self.num_features = num_features
+ self.cond_dim = cond_dim
+ self.pitch_max = pitch_max
+ self.pitch_embedding_dim = pitch_embedding_dim
+ self.kernel_size = kernel_size
+ self.preemph = preemph
+ self.skip = skip
+ self.numbits_range = numbits_range
+ self.numbits_embedding_dim = numbits_embedding_dim
+ self.hidden_feature_dim = hidden_feature_dim
+ self.partial_lookahead = partial_lookahead
+
+ if isinstance(sparsification_density, numbers.Number):
+ sparsification_density = 10 * [sparsification_density]
+
+ norm = weight_norm if apply_weight_norm else lambda x, name=None: x
+
+ # pitch embedding
+ self.pitch_embedding = nn.Embedding(pitch_max + 1, pitch_embedding_dim)
+
+ # numbits embedding
+ self.numbits_embedding = ScaleEmbedding(numbits_embedding_dim, *numbits_range, logscale=True)
+
+ # feature net
+ if partial_lookahead:
+ self.feature_net = SilkFeatureNetPL(num_features + pitch_embedding_dim + 2 * numbits_embedding_dim, cond_dim, hidden_feature_dim, softquant=softquant, sparsify=sparsify, sparsification_density=sparsification_density, apply_weight_norm=apply_weight_norm)
+ else:
+ self.feature_net = SilkFeatureNet(num_features + pitch_embedding_dim + 2 * numbits_embedding_dim, cond_dim)
+
+ # comb filters
+ left_pad = self.kernel_size // 2
+ right_pad = self.kernel_size - 1 - left_pad
+ self.cf1 = LimitedAdaptiveComb1d(self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, overlap_size=40, padding=[left_pad, right_pad], max_lag=pitch_max + 1, gain_limit_db=comb_gain_limit_db, global_gain_limits_db=global_gain_limits_db, norm_p=norm_p, softquant=softquant, apply_weight_norm=apply_weight_norm)
+ self.cf2 = LimitedAdaptiveComb1d(self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, overlap_size=40, padding=[left_pad, right_pad], max_lag=pitch_max + 1, gain_limit_db=comb_gain_limit_db, global_gain_limits_db=global_gain_limits_db, norm_p=norm_p, softquant=softquant, apply_weight_norm=apply_weight_norm)
+
+ # spectral shaping
+ self.af1 = LimitedAdaptiveConv1d(1, 2, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p, softquant=softquant, apply_weight_norm=apply_weight_norm)
+
+ # non-linear transforms
+ self.tdshape1 = TDShaper(cond_dim, frame_size=self.FRAME_SIZE, avg_pool_k=avg_pool_k, pool_after=pool_after, softquant=softquant, apply_weight_norm=apply_weight_norm)
+ self.tdshape2 = TDShaper(cond_dim, frame_size=self.FRAME_SIZE, avg_pool_k=avg_pool_k, pool_after=pool_after, softquant=softquant, apply_weight_norm=apply_weight_norm)
+ self.tdshape3 = TDShaper(cond_dim, frame_size=self.FRAME_SIZE, avg_pool_k=avg_pool_k, pool_after=pool_after, softquant=softquant, apply_weight_norm=apply_weight_norm)
+
+ # combinators
+ self.af2 = LimitedAdaptiveConv1d(2, 2, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p, softquant=softquant, apply_weight_norm=apply_weight_norm)
+ self.af3 = LimitedAdaptiveConv1d(2, 2, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p, softquant=softquant, apply_weight_norm=apply_weight_norm)
+ self.af4 = LimitedAdaptiveConv1d(2, 1, self.kernel_size, cond_dim, frame_size=self.FRAME_SIZE, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p, softquant=softquant, apply_weight_norm=apply_weight_norm)
+
+ # feature transforms
+ self.post_cf1 = norm(nn.Conv1d(cond_dim, cond_dim, 2))
+ self.post_cf2 = norm(nn.Conv1d(cond_dim, cond_dim, 2))
+ self.post_af1 = norm(nn.Conv1d(cond_dim, cond_dim, 2))
+ self.post_af2 = norm(nn.Conv1d(cond_dim, cond_dim, 2))
+ self.post_af3 = norm(nn.Conv1d(cond_dim, cond_dim, 2))
+
+ if softquant:
+ self.post_cf1 = soft_quant(self.post_cf1)
+ self.post_cf2 = soft_quant(self.post_cf2)
+ self.post_af1 = soft_quant(self.post_af1)
+ self.post_af2 = soft_quant(self.post_af2)
+ self.post_af3 = soft_quant(self.post_af3)
+
+
+ if sparsify:
+ mark_for_sparsification(self.post_cf1, (sparsification_density[4], [8, 4]))
+ mark_for_sparsification(self.post_cf2, (sparsification_density[5], [8, 4]))
+ mark_for_sparsification(self.post_af1, (sparsification_density[6], [8, 4]))
+ mark_for_sparsification(self.post_af2, (sparsification_density[7], [8, 4]))
+ mark_for_sparsification(self.post_af3, (sparsification_density[8], [8, 4]))
+
+ self.sparsifier = create_sparsifier(self, *sparsification_schedule)
+
+ def flop_count(self, rate=16000, verbose=False):
+
+ frame_rate = rate / self.FRAME_SIZE
+
+ # feature net
+ feature_net_flops = self.feature_net.flop_count(frame_rate)
+ comb_flops = self.cf1.flop_count(rate) + self.cf2.flop_count(rate)
+ af_flops = self.af1.flop_count(rate) + self.af2.flop_count(rate) + self.af3.flop_count(rate) + self.af4.flop_count(rate)
+ shape_flops = self.tdshape1.flop_count(rate) + self.tdshape2.flop_count(rate) + self.tdshape3.flop_count(rate)
+ feature_flops = (_conv1d_flop_count(self.post_cf1, frame_rate) + _conv1d_flop_count(self.post_cf2, frame_rate)
+ + _conv1d_flop_count(self.post_af1, frame_rate) + _conv1d_flop_count(self.post_af2, frame_rate) + _conv1d_flop_count(self.post_af3, frame_rate))
+
+ if verbose:
+ print(f"feature net: {feature_net_flops / 1e6} MFLOPS")
+ print(f"comb filters: {comb_flops / 1e6} MFLOPS")
+ print(f"adaptive conv: {af_flops / 1e6} MFLOPS")
+ print(f"feature transforms: {feature_flops / 1e6} MFLOPS")
+
+ return feature_net_flops + comb_flops + af_flops + feature_flops + shape_flops
+
+ def feature_transform(self, f, layer):
+ f0 = f.permute(0, 2, 1)
+ f = F.pad(f0, [1, 0])
+ f = torch.tanh(layer(f))
+ return f.permute(0, 2, 1)
+
+ def forward(self, x, features, periods, numbits, debug=False):
+
+ periods = periods.squeeze(-1)
+ pitch_embedding = self.pitch_embedding(periods)
+ numbits_embedding = self.numbits_embedding(numbits).flatten(2)
+
+ full_features = torch.cat((features, pitch_embedding, numbits_embedding), dim=-1)
+ cf = self.feature_net(full_features)
+
+ y = self.cf1(x, cf, periods, debug=debug)
+ cf = self.feature_transform(cf, self.post_cf1)
+
+ y = self.cf2(y, cf, periods, debug=debug)
+ cf = self.feature_transform(cf, self.post_cf2)
+
+ y = self.af1(y, cf, debug=debug)
+ cf = self.feature_transform(cf, self.post_af1)
+
+ y1 = y[:, 0:1, :]
+ y2 = self.tdshape1(y[:, 1:2, :], cf)
+ y = torch.cat((y1, y2), dim=1)
+ y = self.af2(y, cf, debug=debug)
+ cf = self.feature_transform(cf, self.post_af2)
+
+ y1 = y[:, 0:1, :]
+ y2 = self.tdshape2(y[:, 1:2, :], cf)
+ y = torch.cat((y1, y2), dim=1)
+ y = self.af3(y, cf, debug=debug)
+ cf = self.feature_transform(cf, self.post_af3)
+
+ y1 = y[:, 0:1, :]
+ y2 = self.tdshape3(y[:, 1:2, :], cf)
+ y = torch.cat((y1, y2), dim=1)
+ y = self.af4(y, cf, debug=debug)
+
+ return y
diff --git a/dnn/torch/osce/models/scale_embedding.py b/dnn/torch/osce/models/scale_embedding.py
new file mode 100644
index 00000000..58695302
--- /dev/null
+++ b/dnn/torch/osce/models/scale_embedding.py
@@ -0,0 +1,68 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import math as m
+import torch
+from torch import nn
+
+
+class ScaleEmbedding(nn.Module):
+ def __init__(self,
+ dim,
+ min_val,
+ max_val,
+ logscale=False):
+
+ super().__init__()
+
+ if min_val >= max_val:
+ raise ValueError('min_val must be smaller than max_val')
+
+ if min_val <= 0 and logscale:
+ raise ValueError('min_val must be positive when logscale is true')
+
+ self.dim = dim
+ self.logscale = logscale
+ self.min_val = min_val
+ self.max_val = max_val
+
+ if logscale:
+ self.min_val = m.log(self.min_val)
+ self.max_val = m.log(self.max_val)
+
+
+ self.offset = (self.min_val + self.max_val) / 2
+ self.scale_factors = nn.Parameter(
+ torch.arange(1, dim+1, dtype=torch.float32) * torch.pi / (self.max_val - self.min_val)
+ )
+
+ def forward(self, x):
+ if self.logscale: x = torch.log(x)
+ x = torch.clip(x, self.min_val, self.max_val) - self.offset
+ return torch.sin(x.unsqueeze(-1) * self.scale_factors - 0.5)
diff --git a/dnn/torch/osce/models/shape_up_48.py b/dnn/torch/osce/models/shape_up_48.py
new file mode 100644
index 00000000..6e717f0c
--- /dev/null
+++ b/dnn/torch/osce/models/shape_up_48.py
@@ -0,0 +1,179 @@
+
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+import numpy as np
+
+from utils.layers.silk_upsampler import SilkUpsampler
+from utils.layers.limited_adaptive_conv1d import LimitedAdaptiveConv1d
+from utils.layers.td_shaper import TDShaper
+from utils.layers.deemph import Deemph
+from utils.misc import freeze_model
+
+from models.nns_base import NNSBase
+from models.silk_feature_net_pl import SilkFeatureNetPL
+from models.silk_feature_net import SilkFeatureNet
+from .scale_embedding import ScaleEmbedding
+
+
+
+class ShapeUp48(NNSBase):
+ FRAME_SIZE16k=80
+
+ def __init__(self,
+ num_features=47,
+ pitch_embedding_dim=64,
+ cond_dim=256,
+ pitch_max=257,
+ kernel_size=15,
+ preemph=0.85,
+ skip=288,
+ conv_gain_limits_db=[-6, 6],
+ numbits_range=[50, 650],
+ numbits_embedding_dim=8,
+ hidden_feature_dim=64,
+ partial_lookahead=True,
+ norm_p=2,
+ target_fs=48000,
+ noise_amplitude=0,
+ prenet=None,
+ avg_pool_k=4):
+
+ super().__init__(skip=skip, preemph=preemph)
+
+
+ self.num_features = num_features
+ self.cond_dim = cond_dim
+ self.pitch_max = pitch_max
+ self.pitch_embedding_dim = pitch_embedding_dim
+ self.kernel_size = kernel_size
+ self.preemph = preemph
+ self.skip = skip
+ self.numbits_range = numbits_range
+ self.numbits_embedding_dim = numbits_embedding_dim
+ self.hidden_feature_dim = hidden_feature_dim
+ self.partial_lookahead = partial_lookahead
+ self.frame_size48 = int(self.FRAME_SIZE16k * target_fs / 16000 + .1)
+ self.frame_size32 = self.FRAME_SIZE16k * 2
+ self.noise_amplitude = noise_amplitude
+ self.prenet = prenet
+
+ # freeze prenet if given
+ if prenet is not None:
+ freeze_model(self.prenet)
+ try:
+ self.deemph = Deemph(prenet.preemph)
+ except:
+ print("[warning] prenet model is expected to have preemph attribute")
+ self.deemph = Deemph(0)
+
+
+
+ # upsampler
+ self.upsampler = SilkUpsampler()
+
+ # pitch embedding
+ self.pitch_embedding = nn.Embedding(pitch_max + 1, pitch_embedding_dim)
+
+ # numbits embedding
+ self.numbits_embedding = ScaleEmbedding(numbits_embedding_dim, *numbits_range, logscale=True)
+
+ # feature net
+ if partial_lookahead:
+ self.feature_net = SilkFeatureNetPL(num_features + pitch_embedding_dim + 2 * numbits_embedding_dim, cond_dim, hidden_feature_dim)
+ else:
+ self.feature_net = SilkFeatureNet(num_features + pitch_embedding_dim + 2 * numbits_embedding_dim, cond_dim)
+
+ # non-linear transforms
+ self.tdshape1 = TDShaper(cond_dim, frame_size=self.frame_size32, avg_pool_k=avg_pool_k)
+ self.tdshape2 = TDShaper(cond_dim, frame_size=self.frame_size48, avg_pool_k=avg_pool_k)
+
+ # spectral shaping
+ self.af_noise = LimitedAdaptiveConv1d(1, 1, self.kernel_size, cond_dim, frame_size=self.frame_size32, overlap_size=self.frame_size32//2, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=[-30, 0], norm_p=norm_p)
+ self.af1 = LimitedAdaptiveConv1d(1, 2, self.kernel_size, cond_dim, frame_size=self.frame_size32, overlap_size=self.frame_size32//2, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+ self.af2 = LimitedAdaptiveConv1d(3, 2, self.kernel_size, cond_dim, frame_size=self.frame_size32, overlap_size=self.frame_size32//2, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+ self.af3 = LimitedAdaptiveConv1d(2, 1, self.kernel_size, cond_dim, frame_size=self.frame_size48, overlap_size=self.frame_size48//2, use_bias=False, padding=[self.kernel_size - 1, 0], gain_limits_db=conv_gain_limits_db, norm_p=norm_p)
+
+
+ def flop_count(self, rate=16000, verbose=False):
+
+ frame_rate = rate / self.FRAME_SIZE16k
+
+ # feature net
+ feature_net_flops = self.feature_net.flop_count(frame_rate)
+ af_flops = self.af1.flop_count(rate) + self.af2.flop_count(2 * rate) + self.af3.flop_count(3 * rate)
+
+ if verbose:
+ print(f"feature net: {feature_net_flops / 1e6} MFLOPS")
+ print(f"adaptive conv: {af_flops / 1e6} MFLOPS")
+
+ return feature_net_flops + af_flops
+
+ def forward(self, x, features, periods, numbits, debug=False):
+
+ if self.prenet is not None:
+ with torch.no_grad():
+ x = self.prenet(x, features, periods, numbits)
+ x = self.deemph(x)
+
+
+
+ periods = periods.squeeze(-1)
+ pitch_embedding = self.pitch_embedding(periods)
+ numbits_embedding = self.numbits_embedding(numbits).flatten(2)
+
+ full_features = torch.cat((features, pitch_embedding, numbits_embedding), dim=-1)
+ cf = self.feature_net(full_features)
+
+ y32 = self.upsampler.hq_2x_up(x)
+
+ noise = self.noise_amplitude * torch.randn_like(y32)
+ noise = self.af_noise(noise, cf)
+
+ y32 = self.af1(y32, cf, debug=debug)
+
+ y32_1 = y32[:, 0:1, :]
+ y32_2 = self.tdshape1(y32[:, 1:2, :], cf)
+ y32 = torch.cat((y32_1, y32_2, noise), dim=1)
+
+ y32 = self.af2(y32, cf, debug=debug)
+
+ y48 = self.upsampler.interpolate_3_2(y32)
+
+ y48_1 = y48[:, 0:1, :]
+ y48_2 = self.tdshape2(y48[:, 1:2, :], cf)
+ y48 = torch.cat((y48_1, y48_2), dim=1)
+
+ y48 = self.af3(y48, cf, debug=debug)
+
+ return y48
diff --git a/dnn/torch/osce/models/silk_feature_net.py b/dnn/torch/osce/models/silk_feature_net.py
new file mode 100644
index 00000000..ed22f52e
--- /dev/null
+++ b/dnn/torch/osce/models/silk_feature_net.py
@@ -0,0 +1,86 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+from utils.complexity import _conv1d_flop_count
+
+class SilkFeatureNet(nn.Module):
+
+ def __init__(self,
+ feature_dim=47,
+ num_channels=256,
+ lookahead=False):
+
+ super(SilkFeatureNet, self).__init__()
+
+ self.feature_dim = feature_dim
+ self.num_channels = num_channels
+ self.lookahead = lookahead
+
+ self.conv1 = nn.Conv1d(feature_dim, num_channels, 3)
+ self.conv2 = nn.Conv1d(num_channels, num_channels, 3, dilation=2)
+
+ self.gru = nn.GRU(num_channels, num_channels, batch_first=True)
+
+ def flop_count(self, rate=200):
+ count = 0
+ for conv in self.conv1, self.conv2:
+ count += _conv1d_flop_count(conv, rate)
+
+ count += 2 * (3 * self.gru.input_size * self.gru.hidden_size + 3 * self.gru.hidden_size * self.gru.hidden_size) * rate
+
+ return count
+
+
+ def forward(self, features, state=None):
+ """ features shape: (batch_size, num_frames, feature_dim) """
+
+ batch_size = features.size(0)
+
+ if state is None:
+ state = torch.zeros((1, batch_size, self.num_channels), device=features.device)
+
+
+ features = features.permute(0, 2, 1)
+ if self.lookahead:
+ c = torch.tanh(self.conv1(F.pad(features, [1, 1])))
+ c = torch.tanh(self.conv2(F.pad(c, [2, 2])))
+ else:
+ c = torch.tanh(self.conv1(F.pad(features, [2, 0])))
+ c = torch.tanh(self.conv2(F.pad(c, [4, 0])))
+
+ c = c.permute(0, 2, 1)
+
+ c, _ = self.gru(c, state)
+
+ return c \ No newline at end of file
diff --git a/dnn/torch/osce/models/silk_feature_net_pl.py b/dnn/torch/osce/models/silk_feature_net_pl.py
new file mode 100644
index 00000000..e6a536fe
--- /dev/null
+++ b/dnn/torch/osce/models/silk_feature_net_pl.py
@@ -0,0 +1,127 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+import sys
+sys.path.append('../dnntools')
+import numbers
+
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+from torch.nn.utils import weight_norm
+
+from utils.complexity import _conv1d_flop_count
+
+from dnntools.quantization.softquant import soft_quant
+from dnntools.sparsification import mark_for_sparsification
+
+class SilkFeatureNetPL(nn.Module):
+ """ feature net with partial lookahead """
+ def __init__(self,
+ feature_dim=47,
+ num_channels=256,
+ hidden_feature_dim=64,
+ softquant=False,
+ sparsify=True,
+ sparsification_density=0.5,
+ apply_weight_norm=False):
+
+ super(SilkFeatureNetPL, self).__init__()
+
+ if isinstance(sparsification_density, numbers.Number):
+ sparsification_density = 4 * [sparsification_density]
+
+ self.feature_dim = feature_dim
+ self.num_channels = num_channels
+ self.hidden_feature_dim = hidden_feature_dim
+
+ norm = weight_norm if apply_weight_norm else lambda x, name=None: x
+
+ self.conv1 = norm(nn.Conv1d(feature_dim, self.hidden_feature_dim, 1))
+ self.conv2 = norm(nn.Conv1d(4 * self.hidden_feature_dim, num_channels, 2))
+ self.tconv = norm(nn.ConvTranspose1d(num_channels, num_channels, 4, 4))
+ self.gru = norm(norm(nn.GRU(num_channels, num_channels, batch_first=True), name='weight_hh_l0'), name='weight_ih_l0')
+
+ if softquant:
+ self.conv2 = soft_quant(self.conv2)
+ self.tconv = soft_quant(self.tconv)
+ self.gru = soft_quant(self.gru, names=['weight_hh_l0', 'weight_ih_l0'])
+
+
+ if sparsify:
+ mark_for_sparsification(self.conv2, (sparsification_density[0], [8, 4]))
+ mark_for_sparsification(self.tconv, (sparsification_density[1], [8, 4]))
+ mark_for_sparsification(
+ self.gru,
+ {
+ 'W_ir' : (sparsification_density[2], [8, 4], False),
+ 'W_iz' : (sparsification_density[2], [8, 4], False),
+ 'W_in' : (sparsification_density[2], [8, 4], False),
+ 'W_hr' : (sparsification_density[3], [8, 4], True),
+ 'W_hz' : (sparsification_density[3], [8, 4], True),
+ 'W_hn' : (sparsification_density[3], [8, 4], True),
+ }
+ )
+
+
+ def flop_count(self, rate=200):
+ count = 0
+ for conv in self.conv1, self.conv2, self.tconv:
+ count += _conv1d_flop_count(conv, rate)
+
+ count += 2 * (3 * self.gru.input_size * self.gru.hidden_size + 3 * self.gru.hidden_size * self.gru.hidden_size) * rate
+
+ return count
+
+
+ def forward(self, features, state=None):
+ """ features shape: (batch_size, num_frames, feature_dim) """
+
+ batch_size = features.size(0)
+ num_frames = features.size(1)
+
+ if state is None:
+ state = torch.zeros((1, batch_size, self.num_channels), device=features.device)
+
+ features = features.permute(0, 2, 1)
+ # dimensionality reduction
+ c = torch.tanh(self.conv1(features))
+
+ # frame accumulation
+ c = c.permute(0, 2, 1)
+ c = c.reshape(batch_size, num_frames // 4, -1).permute(0, 2, 1)
+ c = torch.tanh(self.conv2(F.pad(c, [1, 0])))
+
+ # upsampling
+ c = torch.tanh(self.tconv(c))
+ c = c.permute(0, 2, 1)
+
+ c, _ = self.gru(c, state)
+
+ return c \ No newline at end of file
diff --git a/dnn/torch/osce/requirements.txt b/dnn/torch/osce/requirements.txt
new file mode 100644
index 00000000..4ca566e5
--- /dev/null
+++ b/dnn/torch/osce/requirements.txt
@@ -0,0 +1,9 @@
+pyyaml==6.0.1
+torch==2.0.1
+numpy==1.25.2
+scipy==1.11.2
+pesq==0.0.4
+gitpython==3.1.36
+matplotlib==3.7.3
+torchaudio==2.0.2
+tqdm==4.66.1
diff --git a/dnn/torch/osce/stndrd/evaluation/create_input_data.sh b/dnn/torch/osce/stndrd/evaluation/create_input_data.sh
new file mode 100644
index 00000000..54bacb88
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/create_input_data.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+
+INPUT="dataset/LibriSpeech"
+OUTPUT="testdata"
+OPUSDEMO="/local/experiments/ietf_enhancement_studies/bin/opus_demo_patched"
+BITRATES=( 6000 7500 ) # 9000 12000 15000 18000 24000 32000 )
+
+
+mkdir -p $OUTPUT
+
+for fn in $(find $INPUT -name "*.wav")
+do
+ name=$(basename ${fn%*.wav})
+ sox $fn -r 16000 -b 16 -e signed-integer ${OUTPUT}/tmp.raw
+ for br in ${BITRATES[@]}
+ do
+ folder=${OUTPUT}/"${name}_${br}.se"
+ echo "creating ${folder}..."
+ mkdir -p $folder
+ cp ${OUTPUT}/tmp.raw ${folder}/clean.s16
+ (cd ${folder} && $OPUSDEMO voip 16000 1 $br clean.s16 noisy.s16)
+ done
+ rm -f ${OUTPUT}/tmp.raw
+done
diff --git a/dnn/torch/osce/stndrd/evaluation/env.rc b/dnn/torch/osce/stndrd/evaluation/env.rc
new file mode 100644
index 00000000..f1266b6f
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/env.rc
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+export PYTHON=/home/ubuntu/opt/miniconda3/envs/torch/bin/python
+export LACE="/local/experiments/ietf_enhancement_studies/checkpoints/lace_checkpoint.pth"
+export NOLACE="/local/experiments/ietf_enhancement_studies/checkpoints/nolace_checkpoint.pth"
+export TESTMODEL="/local/experiments/ietf_enhancement_studies/opus/dnn/torch/osce/test_model.py"
+export OPUSDEMO="/local/experiments/ietf_enhancement_studies/bin/opus_demo_patched" \ No newline at end of file
diff --git a/dnn/torch/osce/stndrd/evaluation/evaluate.py b/dnn/torch/osce/stndrd/evaluation/evaluate.py
new file mode 100644
index 00000000..54700dbe
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/evaluate.py
@@ -0,0 +1,113 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+
+
+from scipy.io import wavfile
+from pesq import pesq
+import numpy as np
+from moc import compare
+from moc2 import compare as compare2
+#from warpq import compute_WAPRQ as warpq
+from lace_loss_metric import compare as laceloss_compare
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('folder', type=str, help='folder with processed items')
+parser.add_argument('metric', type=str, choices=['pesq', 'moc', 'moc2', 'laceloss'], help='metric to be used for evaluation')
+
+
+def get_bitrates(folder):
+ with open(os.path.join(folder, 'bitrates.txt')) as f:
+ x = f.read()
+
+ bitrates = [int(y) for y in x.rstrip('\n').split()]
+
+ return bitrates
+
+def get_itemlist(folder):
+ with open(os.path.join(folder, 'items.txt')) as f:
+ lines = f.readlines()
+
+ items = [x.split()[0] for x in lines]
+
+ return items
+
+
+def process_item(folder, item, bitrate, metric):
+ fs, x_clean = wavfile.read(os.path.join(folder, 'clean', f"{item}_{bitrate}_clean.wav"))
+ fs, x_opus = wavfile.read(os.path.join(folder, 'opus', f"{item}_{bitrate}_opus.wav"))
+ fs, x_lace = wavfile.read(os.path.join(folder, 'lace', f"{item}_{bitrate}_lace.wav"))
+ fs, x_nolace = wavfile.read(os.path.join(folder, 'nolace', f"{item}_{bitrate}_nolace.wav"))
+
+ x_clean = x_clean.astype(np.float32) / 2**15
+ x_opus = x_opus.astype(np.float32) / 2**15
+ x_lace = x_lace.astype(np.float32) / 2**15
+ x_nolace = x_nolace.astype(np.float32) / 2**15
+
+ if metric == 'pesq':
+ result = [pesq(fs, x_clean, x_opus), pesq(fs, x_clean, x_lace), pesq(fs, x_clean, x_nolace)]
+ elif metric =='moc':
+ result = [compare(x_clean, x_opus), compare(x_clean, x_lace), compare(x_clean, x_nolace)]
+ elif metric =='moc2':
+ result = [compare2(x_clean, x_opus), compare2(x_clean, x_lace), compare2(x_clean, x_nolace)]
+ # elif metric == 'warpq':
+ # result = [warpq(x_clean, x_opus), warpq(x_clean, x_lace), warpq(x_clean, x_nolace)]
+ elif metric == 'laceloss':
+ result = [laceloss_compare(x_clean, x_opus), laceloss_compare(x_clean, x_lace), laceloss_compare(x_clean, x_nolace)]
+ else:
+ raise ValueError(f'unknown metric {metric}')
+
+ return result
+
+def process_bitrate(folder, items, bitrate, metric):
+ results = np.zeros((len(items), 3))
+
+ for i, item in enumerate(items):
+ results[i, :] = np.array(process_item(folder, item, bitrate, metric))
+
+ return results
+
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+ items = get_itemlist(args.folder)
+ bitrates = get_bitrates(args.folder)
+
+ results = dict()
+ for br in bitrates:
+ print(f"processing bitrate {br}...")
+ results[br] = process_bitrate(args.folder, items, br, args.metric)
+
+ np.save(os.path.join(args.folder, f'results_{args.metric}.npy'), results)
+
+ print("Done.")
diff --git a/dnn/torch/osce/stndrd/evaluation/lace_loss_metric.py b/dnn/torch/osce/stndrd/evaluation/lace_loss_metric.py
new file mode 100644
index 00000000..b0790585
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/lace_loss_metric.py
@@ -0,0 +1,330 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+"""STFT-based Loss modules."""
+
+import torch
+import torch.nn.functional as F
+from torch import nn
+import numpy as np
+import torchaudio
+
+
+def get_window(win_name, win_length, *args, **kwargs):
+ window_dict = {
+ 'bartlett_window' : torch.bartlett_window,
+ 'blackman_window' : torch.blackman_window,
+ 'hamming_window' : torch.hamming_window,
+ 'hann_window' : torch.hann_window,
+ 'kaiser_window' : torch.kaiser_window
+ }
+
+ if not win_name in window_dict:
+ raise ValueError()
+
+ return window_dict[win_name](win_length, *args, **kwargs)
+
+
+def stft(x, fft_size, hop_size, win_length, window):
+ """Perform STFT and convert to magnitude spectrogram.
+ Args:
+ x (Tensor): Input signal tensor (B, T).
+ fft_size (int): FFT size.
+ hop_size (int): Hop size.
+ win_length (int): Window length.
+ window (str): Window function type.
+ Returns:
+ Tensor: Magnitude spectrogram (B, #frames, fft_size // 2 + 1).
+ """
+
+ win = get_window(window, win_length).to(x.device)
+ x_stft = torch.stft(x, fft_size, hop_size, win_length, win, return_complex=True)
+
+
+ return torch.clamp(torch.abs(x_stft), min=1e-7)
+
+def spectral_convergence_loss(Y_true, Y_pred):
+ dims=list(range(1, len(Y_pred.shape)))
+ return torch.mean(torch.norm(torch.abs(Y_true) - torch.abs(Y_pred), p="fro", dim=dims) / (torch.norm(Y_pred, p="fro", dim=dims) + 1e-6))
+
+
+def log_magnitude_loss(Y_true, Y_pred):
+ Y_true_log_abs = torch.log(torch.abs(Y_true) + 1e-15)
+ Y_pred_log_abs = torch.log(torch.abs(Y_pred) + 1e-15)
+
+ return torch.mean(torch.abs(Y_true_log_abs - Y_pred_log_abs))
+
+def spectral_xcorr_loss(Y_true, Y_pred):
+ Y_true = Y_true.abs()
+ Y_pred = Y_pred.abs()
+ dims=list(range(1, len(Y_pred.shape)))
+ xcorr = torch.sum(Y_true * Y_pred, dim=dims) / torch.sqrt(torch.sum(Y_true ** 2, dim=dims) * torch.sum(Y_pred ** 2, dim=dims) + 1e-9)
+
+ return 1 - xcorr.mean()
+
+
+
+class MRLogMelLoss(nn.Module):
+ def __init__(self,
+ fft_sizes=[512, 256, 128, 64],
+ overlap=0.5,
+ fs=16000,
+ n_mels=18
+ ):
+
+ self.fft_sizes = fft_sizes
+ self.overlap = overlap
+ self.fs = fs
+ self.n_mels = n_mels
+
+ super().__init__()
+
+ self.mel_specs = []
+ for fft_size in fft_sizes:
+ hop_size = int(round(fft_size * (1 - self.overlap)))
+
+ n_mels = self.n_mels
+ if fft_size < 128:
+ n_mels //= 2
+
+ self.mel_specs.append(torchaudio.transforms.MelSpectrogram(fs, fft_size, hop_length=hop_size, n_mels=n_mels))
+
+ for i, mel_spec in enumerate(self.mel_specs):
+ self.add_module(f'mel_spec_{i+1}', mel_spec)
+
+ def forward(self, y_true, y_pred):
+
+ loss = torch.zeros(1, device=y_true.device)
+
+ for mel_spec in self.mel_specs:
+ Y_true = mel_spec(y_true)
+ Y_pred = mel_spec(y_pred)
+ loss = loss + log_magnitude_loss(Y_true, Y_pred)
+
+ loss = loss / len(self.mel_specs)
+
+ return loss
+
+def create_weight_matrix(num_bins, bins_per_band=10):
+ m = torch.zeros((num_bins, num_bins), dtype=torch.float32)
+
+ r0 = bins_per_band // 2
+ r1 = bins_per_band - r0
+
+ for i in range(num_bins):
+ i0 = max(i - r0, 0)
+ j0 = min(i + r1, num_bins)
+
+ m[i, i0: j0] += 1
+
+ if i < r0:
+ m[i, :r0 - i] += 1
+
+ if i > num_bins - r1:
+ m[i, num_bins - r1 - i:] += 1
+
+ return m / bins_per_band
+
+def weighted_spectral_convergence(Y_true, Y_pred, w):
+
+ # calculate sfm based weights
+ logY = torch.log(torch.abs(Y_true) + 1e-9)
+ Y = torch.abs(Y_true)
+
+ avg_logY = torch.matmul(logY.transpose(1, 2), w)
+ avg_Y = torch.matmul(Y.transpose(1, 2), w)
+
+ sfm = torch.exp(avg_logY) / (avg_Y + 1e-9)
+
+ weight = (torch.relu(1 - sfm) ** .5).transpose(1, 2)
+
+ loss = torch.mean(
+ torch.mean(weight * torch.abs(torch.abs(Y_true) - torch.abs(Y_pred)), dim=[1, 2])
+ / (torch.mean( weight * torch.abs(Y_true), dim=[1, 2]) + 1e-9)
+ )
+
+ return loss
+
+def gen_filterbank(N, Fs=16000):
+ in_freq = (np.arange(N+1, dtype='float32')/N*Fs/2)[None,:]
+ out_freq = (np.arange(N, dtype='float32')/N*Fs/2)[:,None]
+ #ERB from B.C.J Moore, An Introduction to the Psychology of Hearing, 5th Ed., page 73.
+ ERB_N = 24.7 + .108*in_freq
+ delta = np.abs(in_freq-out_freq)/ERB_N
+ center = (delta<.5).astype('float32')
+ R = -12*center*delta**2 + (1-center)*(3-12*delta)
+ RE = 10.**(R/10.)
+ norm = np.sum(RE, axis=1)
+ RE = RE/norm[:, np.newaxis]
+ return torch.from_numpy(RE)
+
+def smooth_log_mag(Y_true, Y_pred, filterbank):
+ Y_true_smooth = torch.matmul(filterbank, torch.abs(Y_true))
+ Y_pred_smooth = torch.matmul(filterbank, torch.abs(Y_pred))
+
+ loss = torch.abs(
+ torch.log(Y_true_smooth + 1e-9) - torch.log(Y_pred_smooth + 1e-9)
+ )
+
+ loss = loss.mean()
+
+ return loss
+
+class MRSTFTLoss(nn.Module):
+ def __init__(self,
+ fft_sizes=[2048, 1024, 512, 256, 128, 64],
+ overlap=0.5,
+ window='hann_window',
+ fs=16000,
+ log_mag_weight=0,
+ sc_weight=0,
+ wsc_weight=0,
+ smooth_log_mag_weight=2,
+ sxcorr_weight=1):
+ super().__init__()
+
+ self.fft_sizes = fft_sizes
+ self.overlap = overlap
+ self.window = window
+ self.log_mag_weight = log_mag_weight
+ self.sc_weight = sc_weight
+ self.wsc_weight = wsc_weight
+ self.smooth_log_mag_weight = smooth_log_mag_weight
+ self.sxcorr_weight = sxcorr_weight
+ self.fs = fs
+
+ # weights for SFM weighted spectral convergence loss
+ self.wsc_weights = torch.nn.ParameterDict()
+ for fft_size in fft_sizes:
+ width = min(11, int(1000 * fft_size / self.fs + .5))
+ width += width % 2
+ self.wsc_weights[str(fft_size)] = torch.nn.Parameter(
+ create_weight_matrix(fft_size // 2 + 1, width),
+ requires_grad=False
+ )
+
+ # filterbanks for smooth log magnitude loss
+ self.filterbanks = torch.nn.ParameterDict()
+ for fft_size in fft_sizes:
+ self.filterbanks[str(fft_size)] = torch.nn.Parameter(
+ gen_filterbank(fft_size//2),
+ requires_grad=False
+ )
+
+
+ def __call__(self, y_true, y_pred):
+
+
+ lm_loss = torch.zeros(1, device=y_true.device)
+ sc_loss = torch.zeros(1, device=y_true.device)
+ wsc_loss = torch.zeros(1, device=y_true.device)
+ slm_loss = torch.zeros(1, device=y_true.device)
+ sxcorr_loss = torch.zeros(1, device=y_true.device)
+
+ for fft_size in self.fft_sizes:
+ hop_size = int(round(fft_size * (1 - self.overlap)))
+ win_size = fft_size
+
+ Y_true = stft(y_true, fft_size, hop_size, win_size, self.window)
+ Y_pred = stft(y_pred, fft_size, hop_size, win_size, self.window)
+
+ if self.log_mag_weight > 0:
+ lm_loss = lm_loss + log_magnitude_loss(Y_true, Y_pred)
+
+ if self.sc_weight > 0:
+ sc_loss = sc_loss + spectral_convergence_loss(Y_true, Y_pred)
+
+ if self.wsc_weight > 0:
+ wsc_loss = wsc_loss + weighted_spectral_convergence(Y_true, Y_pred, self.wsc_weights[str(fft_size)])
+
+ if self.smooth_log_mag_weight > 0:
+ slm_loss = slm_loss + smooth_log_mag(Y_true, Y_pred, self.filterbanks[str(fft_size)])
+
+ if self.sxcorr_weight > 0:
+ sxcorr_loss = sxcorr_loss + spectral_xcorr_loss(Y_true, Y_pred)
+
+
+ total_loss = (self.log_mag_weight * lm_loss + self.sc_weight * sc_loss
+ + self.wsc_weight * wsc_loss + self.smooth_log_mag_weight * slm_loss
+ + self.sxcorr_weight * sxcorr_loss) / len(self.fft_sizes)
+
+ return total_loss
+
+
+def td_l2_norm(y_true, y_pred):
+ dims = list(range(1, len(y_true.shape)))
+
+ loss = torch.mean((y_true - y_pred) ** 2, dim=dims) / (torch.mean(y_pred ** 2, dim=dims) ** .5 + 1e-6)
+
+ return loss.mean()
+
+
+class LaceLoss(nn.Module):
+ def __init__(self):
+ super().__init__()
+
+
+ self.stftloss = MRSTFTLoss(log_mag_weight=0, sc_weight=0, wsc_weight=0, smooth_log_mag_weight=2, sxcorr_weight=1)
+
+
+ def forward(self, x, y):
+ specloss = self.stftloss(x, y)
+ phaseloss = td_l2_norm(x, y)
+ total_loss = (specloss + 10 * phaseloss) / 13
+
+ return total_loss
+
+ def compare(self, x_ref, x_deg):
+ # trim items to same size
+ n = min(len(x_ref), len(x_deg))
+ x_ref = x_ref[:n].copy()
+ x_deg = x_deg[:n].copy()
+
+ # pre-emphasis
+ x_ref[1:] -= 0.85 * x_ref[:-1]
+ x_deg[1:] -= 0.85 * x_deg[:-1]
+
+ device = next(iter(self.parameters())).device
+
+ x = torch.from_numpy(x_ref).to(device)
+ y = torch.from_numpy(x_deg).to(device)
+
+ with torch.no_grad():
+ dist = 10 * self.forward(x, y)
+
+ return dist.cpu().numpy().item()
+
+
+lace_loss = LaceLoss()
+device = 'cuda' if torch.cuda.is_available() else 'cpu'
+lace_loss.to(device)
+
+def compare(x, y):
+
+ return lace_loss.compare(x, y)
diff --git a/dnn/torch/osce/stndrd/evaluation/make_boxplots.py b/dnn/torch/osce/stndrd/evaluation/make_boxplots.py
new file mode 100644
index 00000000..f7ea778a
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/make_boxplots.py
@@ -0,0 +1,116 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+
+import numpy as np
+import matplotlib.pyplot as plt
+from prettytable import PrettyTable
+from matplotlib.patches import Patch
+
+parser = argparse.ArgumentParser()
+parser.add_argument('folder', type=str, help='path to folder with pre-calculated metrics')
+parser.add_argument('--metric', choices=['pesq', 'moc', 'warpq', 'nomad', 'laceloss', 'all'], default='all', help='default: all')
+parser.add_argument('--output', type=str, default=None, help='alternative output folder, default: folder')
+
+def load_data(folder):
+ data = dict()
+
+ if os.path.isfile(os.path.join(folder, 'results_moc.npy')):
+ data['moc'] = np.load(os.path.join(folder, 'results_moc.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_moc2.npy')):
+ data['moc2'] = np.load(os.path.join(folder, 'results_moc2.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_pesq.npy')):
+ data['pesq'] = np.load(os.path.join(folder, 'results_pesq.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_warpq.npy')):
+ data['warpq'] = np.load(os.path.join(folder, 'results_warpq.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_nomad.npy')):
+ data['nomad'] = np.load(os.path.join(folder, 'results_nomad.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_laceloss.npy')):
+ data['laceloss'] = np.load(os.path.join(folder, 'results_laceloss.npy'), allow_pickle=True).item()
+
+ return data
+
+def plot_data(filename, data, title=None):
+ compare_dict = dict()
+ for br in data.keys():
+ compare_dict[f'Opus {br/1000:.1f} kb/s'] = data[br][:, 0]
+ compare_dict[f'LACE {br/1000:.1f} kb/s'] = data[br][:, 1]
+ compare_dict[f'NoLACE {br/1000:.1f} kb/s'] = data[br][:, 2]
+
+ plt.rcParams.update({
+ "text.usetex": True,
+ "font.family": "Helvetica",
+ "font.size": 32
+ })
+
+ black = '#000000'
+ red = '#ff5745'
+ blue = '#007dbc'
+ colors = [black, red, blue]
+ legend_elements = [Patch(facecolor=colors[0], label='Opus SILK'),
+ Patch(facecolor=colors[1], label='LACE'),
+ Patch(facecolor=colors[2], label='NoLACE')]
+
+ fig, ax = plt.subplots()
+ fig.set_size_inches(40, 20)
+ bplot = ax.boxplot(compare_dict.values(), showfliers=False, notch=True, patch_artist=True)
+
+ for i, patch in enumerate(bplot['boxes']):
+ patch.set_facecolor(colors[i%3])
+
+ ax.set_xticklabels(compare_dict.keys(), rotation=290)
+
+ if title is not None:
+ ax.set_title(title)
+
+ ax.legend(handles=legend_elements)
+
+ fig.savefig(filename, bbox_inches='tight')
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+ data = load_data(args.folder)
+
+
+ metrics = list(data.keys()) if args.metric == 'all' else [args.metric]
+ folder = args.folder if args.output is None else args.output
+ os.makedirs(folder, exist_ok=True)
+
+ for metric in metrics:
+ print(f"Plotting data for {metric} metric...")
+ plot_data(os.path.join(folder, f"boxplot_{metric}.png"), data[metric], title=metric.upper())
+
+ print("Done.") \ No newline at end of file
diff --git a/dnn/torch/osce/stndrd/evaluation/make_boxplots_moctest.py b/dnn/torch/osce/stndrd/evaluation/make_boxplots_moctest.py
new file mode 100644
index 00000000..ca65aba9
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/make_boxplots_moctest.py
@@ -0,0 +1,109 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+
+import numpy as np
+import matplotlib.pyplot as plt
+from prettytable import PrettyTable
+from matplotlib.patches import Patch
+
+parser = argparse.ArgumentParser()
+parser.add_argument('folder', type=str, help='path to folder with pre-calculated metrics')
+parser.add_argument('--metric', choices=['pesq', 'moc', 'warpq', 'nomad', 'laceloss', 'all'], default='all', help='default: all')
+parser.add_argument('--output', type=str, default=None, help='alternative output folder, default: folder')
+
+def load_data(folder):
+ data = dict()
+
+ if os.path.isfile(os.path.join(folder, 'results_moc.npy')):
+ data['moc'] = np.load(os.path.join(folder, 'results_moc.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_pesq.npy')):
+ data['pesq'] = np.load(os.path.join(folder, 'results_pesq.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_warpq.npy')):
+ data['warpq'] = np.load(os.path.join(folder, 'results_warpq.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_nomad.npy')):
+ data['nomad'] = np.load(os.path.join(folder, 'results_nomad.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_laceloss.npy')):
+ data['laceloss'] = np.load(os.path.join(folder, 'results_laceloss.npy'), allow_pickle=True).item()
+
+ return data
+
+def plot_data(filename, data, title=None):
+ compare_dict = dict()
+ for br in data.keys():
+ compare_dict[f'Opus {br/1000:.1f} kb/s'] = data[br][:, 0]
+ compare_dict[f'LACE (MOC only) {br/1000:.1f} kb/s'] = data[br][:, 1]
+ compare_dict[f'LACE (MOC + TD) {br/1000:.1f} kb/s'] = data[br][:, 2]
+
+ plt.rcParams.update({
+ "text.usetex": True,
+ "font.family": "Helvetica",
+ "font.size": 32
+ })
+ colors = ['pink', 'lightblue', 'lightgreen']
+ legend_elements = [Patch(facecolor=colors[0], label='Opus SILK'),
+ Patch(facecolor=colors[1], label='MOC loss only'),
+ Patch(facecolor=colors[2], label='MOC + TD loss')]
+
+ fig, ax = plt.subplots()
+ fig.set_size_inches(40, 20)
+ bplot = ax.boxplot(compare_dict.values(), showfliers=False, notch=True, patch_artist=True)
+
+ for i, patch in enumerate(bplot['boxes']):
+ patch.set_facecolor(colors[i%3])
+
+ ax.set_xticklabels(compare_dict.keys(), rotation=290)
+
+ if title is not None:
+ ax.set_title(title)
+
+ ax.legend(handles=legend_elements)
+
+ fig.savefig(filename, bbox_inches='tight')
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+ data = load_data(args.folder)
+
+
+ metrics = list(data.keys()) if args.metric == 'all' else [args.metric]
+ folder = args.folder if args.output is None else args.output
+ os.makedirs(folder, exist_ok=True)
+
+ for metric in metrics:
+ print(f"Plotting data for {metric} metric...")
+ plot_data(os.path.join(folder, f"boxplot_{metric}.png"), data[metric], title=metric.upper())
+
+ print("Done.") \ No newline at end of file
diff --git a/dnn/torch/osce/stndrd/evaluation/make_tables.py b/dnn/torch/osce/stndrd/evaluation/make_tables.py
new file mode 100644
index 00000000..56080127
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/make_tables.py
@@ -0,0 +1,124 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+
+import numpy as np
+import matplotlib.pyplot as plt
+from prettytable import PrettyTable
+from matplotlib.patches import Patch
+
+parser = argparse.ArgumentParser()
+parser.add_argument('folder', type=str, help='path to folder with pre-calculated metrics')
+parser.add_argument('--metric', choices=['pesq', 'moc', 'warpq', 'nomad', 'laceloss', 'all'], default='all', help='default: all')
+parser.add_argument('--output', type=str, default=None, help='alternative output folder, default: folder')
+
+def load_data(folder):
+ data = dict()
+
+ if os.path.isfile(os.path.join(folder, 'results_moc.npy')):
+ data['moc'] = np.load(os.path.join(folder, 'results_moc.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_moc2.npy')):
+ data['moc2'] = np.load(os.path.join(folder, 'results_moc2.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_pesq.npy')):
+ data['pesq'] = np.load(os.path.join(folder, 'results_pesq.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_warpq.npy')):
+ data['warpq'] = np.load(os.path.join(folder, 'results_warpq.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_nomad.npy')):
+ data['nomad'] = np.load(os.path.join(folder, 'results_nomad.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_laceloss.npy')):
+ data['laceloss'] = np.load(os.path.join(folder, 'results_laceloss.npy'), allow_pickle=True).item()
+
+ return data
+
+def make_table(filename, data, title=None):
+
+ # mean values
+ tbl = PrettyTable()
+ tbl.field_names = ['bitrate (bps)', 'Opus', 'LACE', 'NoLACE']
+ for br in data.keys():
+ opus = data[br][:, 0]
+ lace = data[br][:, 1]
+ nolace = data[br][:, 2]
+ tbl.add_row([br, f"{float(opus.mean()):.3f} ({float(opus.std()):.2f})", f"{float(lace.mean()):.3f} ({float(lace.std()):.2f})", f"{float(nolace.mean()):.3f} ({float(nolace.std()):.2f})"])
+
+ with open(filename + ".txt", "w") as f:
+ f.write(str(tbl))
+
+ with open(filename + ".html", "w") as f:
+ f.write(tbl.get_html_string())
+
+ with open(filename + ".csv", "w") as f:
+ f.write(tbl.get_csv_string())
+
+ print(tbl)
+
+
+def make_diff_table(filename, data, title=None):
+
+ # mean values
+ tbl = PrettyTable()
+ tbl.field_names = ['bitrate (bps)', 'LACE - Opus', 'NoLACE - Opus']
+ for br in data.keys():
+ opus = data[br][:, 0]
+ lace = data[br][:, 1] - opus
+ nolace = data[br][:, 2] - opus
+ tbl.add_row([br, f"{float(lace.mean()):.3f} ({float(lace.std()):.2f})", f"{float(nolace.mean()):.3f} ({float(nolace.std()):.2f})"])
+
+ with open(filename + ".txt", "w") as f:
+ f.write(str(tbl))
+
+ with open(filename + ".html", "w") as f:
+ f.write(tbl.get_html_string())
+
+ with open(filename + ".csv", "w") as f:
+ f.write(tbl.get_csv_string())
+
+ print(tbl)
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+ data = load_data(args.folder)
+
+ metrics = list(data.keys()) if args.metric == 'all' else [args.metric]
+ folder = args.folder if args.output is None else args.output
+ os.makedirs(folder, exist_ok=True)
+
+ for metric in metrics:
+ print(f"Plotting data for {metric} metric...")
+ make_table(os.path.join(folder, f"table_{metric}"), data[metric])
+ make_diff_table(os.path.join(folder, f"table_diff_{metric}"), data[metric])
+
+ print("Done.") \ No newline at end of file
diff --git a/dnn/torch/osce/stndrd/evaluation/make_tables_moctest.py b/dnn/torch/osce/stndrd/evaluation/make_tables_moctest.py
new file mode 100644
index 00000000..37718068
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/make_tables_moctest.py
@@ -0,0 +1,121 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+
+import numpy as np
+import matplotlib.pyplot as plt
+from prettytable import PrettyTable
+from matplotlib.patches import Patch
+
+parser = argparse.ArgumentParser()
+parser.add_argument('folder', type=str, help='path to folder with pre-calculated metrics')
+parser.add_argument('--metric', choices=['pesq', 'moc', 'warpq', 'nomad', 'laceloss', 'all'], default='all', help='default: all')
+parser.add_argument('--output', type=str, default=None, help='alternative output folder, default: folder')
+
+def load_data(folder):
+ data = dict()
+
+ if os.path.isfile(os.path.join(folder, 'results_moc.npy')):
+ data['moc'] = np.load(os.path.join(folder, 'results_moc.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_pesq.npy')):
+ data['pesq'] = np.load(os.path.join(folder, 'results_pesq.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_warpq.npy')):
+ data['warpq'] = np.load(os.path.join(folder, 'results_warpq.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_nomad.npy')):
+ data['nomad'] = np.load(os.path.join(folder, 'results_nomad.npy'), allow_pickle=True).item()
+
+ if os.path.isfile(os.path.join(folder, 'results_laceloss.npy')):
+ data['laceloss'] = np.load(os.path.join(folder, 'results_laceloss.npy'), allow_pickle=True).item()
+
+ return data
+
+def make_table(filename, data, title=None):
+
+ # mean values
+ tbl = PrettyTable()
+ tbl.field_names = ['bitrate (bps)', 'Opus', 'LACE', 'NoLACE']
+ for br in data.keys():
+ opus = data[br][:, 0]
+ lace = data[br][:, 1]
+ nolace = data[br][:, 2]
+ tbl.add_row([br, f"{float(opus.mean()):.3f} ({float(opus.std()):.2f})", f"{float(lace.mean()):.3f} ({float(lace.std()):.2f})", f"{float(nolace.mean()):.3f} ({float(nolace.std()):.2f})"])
+
+ with open(filename + ".txt", "w") as f:
+ f.write(str(tbl))
+
+ with open(filename + ".html", "w") as f:
+ f.write(tbl.get_html_string())
+
+ with open(filename + ".csv", "w") as f:
+ f.write(tbl.get_csv_string())
+
+ print(tbl)
+
+
+def make_diff_table(filename, data, title=None):
+
+ # mean values
+ tbl = PrettyTable()
+ tbl.field_names = ['bitrate (bps)', 'LACE - Opus', 'NoLACE - Opus']
+ for br in data.keys():
+ opus = data[br][:, 0]
+ lace = data[br][:, 1] - opus
+ nolace = data[br][:, 2] - opus
+ tbl.add_row([br, f"{float(lace.mean()):.3f} ({float(lace.std()):.2f})", f"{float(nolace.mean()):.3f} ({float(nolace.std()):.2f})"])
+
+ with open(filename + ".txt", "w") as f:
+ f.write(str(tbl))
+
+ with open(filename + ".html", "w") as f:
+ f.write(tbl.get_html_string())
+
+ with open(filename + ".csv", "w") as f:
+ f.write(tbl.get_csv_string())
+
+ print(tbl)
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+ data = load_data(args.folder)
+
+ metrics = list(data.keys()) if args.metric == 'all' else [args.metric]
+ folder = args.folder if args.output is None else args.output
+ os.makedirs(folder, exist_ok=True)
+
+ for metric in metrics:
+ print(f"Plotting data for {metric} metric...")
+ make_table(os.path.join(folder, f"table_{metric}"), data[metric])
+ make_diff_table(os.path.join(folder, f"table_diff_{metric}"), data[metric])
+
+ print("Done.") \ No newline at end of file
diff --git a/dnn/torch/osce/stndrd/evaluation/moc.py b/dnn/torch/osce/stndrd/evaluation/moc.py
new file mode 100644
index 00000000..bf004de9
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/moc.py
@@ -0,0 +1,182 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import numpy as np
+import scipy.signal
+
+def compute_vad_mask(x, fs, stop_db=-70):
+
+ frame_length = (fs + 49) // 50
+ x = x[: frame_length * (len(x) // frame_length)]
+
+ frames = x.reshape(-1, frame_length)
+ frame_energy = np.sum(frames ** 2, axis=1)
+ frame_energy_smooth = np.convolve(frame_energy, np.ones(5) / 5, mode='same')
+
+ max_threshold = frame_energy.max() * 10 ** (stop_db/20)
+ vactive = np.ones_like(frames)
+ vactive[frame_energy_smooth < max_threshold, :] = 0
+ vactive = vactive.reshape(-1)
+
+ filter = np.sin(np.arange(frame_length) * np.pi / (frame_length - 1))
+ filter = filter / filter.sum()
+
+ mask = np.convolve(vactive, filter, mode='same')
+
+ return x, mask
+
+def convert_mask(mask, num_frames, frame_size=160, hop_size=40):
+ num_samples = frame_size + (num_frames - 1) * hop_size
+ if len(mask) < num_samples:
+ mask = np.concatenate((mask, np.zeros(num_samples - len(mask))), dtype=mask.dtype)
+ else:
+ mask = mask[:num_samples]
+
+ new_mask = np.array([np.mean(mask[i*hop_size : i*hop_size + frame_size]) for i in range(num_frames)])
+
+ return new_mask
+
+def power_spectrum(x, window_size=160, hop_size=40, window='hamming'):
+ num_spectra = (len(x) - window_size - hop_size) // hop_size
+ window = scipy.signal.get_window(window, window_size)
+ N = window_size // 2
+
+ frames = np.concatenate([x[np.newaxis, i * hop_size : i * hop_size + window_size] for i in range(num_spectra)]) * window
+ psd = np.abs(np.fft.fft(frames, axis=1)[:, :N + 1]) ** 2
+
+ return psd
+
+
+def frequency_mask(num_bands, up_factor, down_factor):
+
+ up_mask = np.zeros((num_bands, num_bands))
+ down_mask = np.zeros((num_bands, num_bands))
+
+ for i in range(num_bands):
+ up_mask[i, : i + 1] = up_factor ** np.arange(i, -1, -1)
+ down_mask[i, i :] = down_factor ** np.arange(num_bands - i)
+
+ return down_mask @ up_mask
+
+
+def rect_fb(band_limits, num_bins=None):
+ num_bands = len(band_limits) - 1
+ if num_bins is None:
+ num_bins = band_limits[-1]
+
+ fb = np.zeros((num_bands, num_bins))
+ for i in range(num_bands):
+ fb[i, band_limits[i]:band_limits[i+1]] = 1
+
+ return fb
+
+
+def compare(x, y, apply_vad=False):
+ """ Modified version of opus_compare for 16 kHz mono signals
+
+ Args:
+ x (np.ndarray): reference input signal scaled to [-1, 1]
+ y (np.ndarray): test signal scaled to [-1, 1]
+
+ Returns:
+ float: perceptually weighted error
+ """
+ # filter bank: bark scale with minimum-2-bin bands and cutoff at 7.5 kHz
+ band_limits = [0, 2, 4, 6, 7, 9, 11, 13, 15, 18, 22, 26, 31, 36, 43, 51, 60, 75]
+ num_bands = len(band_limits) - 1
+ fb = rect_fb(band_limits, num_bins=81)
+
+ # trim samples to same size
+ num_samples = min(len(x), len(y))
+ x = x[:num_samples] * 2**15
+ y = y[:num_samples] * 2**15
+
+ psd_x = power_spectrum(x) + 100000
+ psd_y = power_spectrum(y) + 100000
+
+ num_frames = psd_x.shape[0]
+
+ # average band energies
+ be_x = (psd_x @ fb.T) / np.sum(fb, axis=1)
+
+ # frequecy masking
+ f_mask = frequency_mask(num_bands, 0.1, 0.03)
+ mask_x = be_x @ f_mask.T
+
+ # temporal masking
+ for i in range(1, num_frames):
+ mask_x[i, :] += 0.5 * mask_x[i-1, :]
+
+ # apply mask
+ masked_psd_x = psd_x + 0.1 * (mask_x @ fb)
+ masked_psd_y = psd_y + 0.1 * (mask_x @ fb)
+
+ # 2-frame average
+ masked_psd_x = masked_psd_x[1:] + masked_psd_x[:-1]
+ masked_psd_y = masked_psd_y[1:] + masked_psd_y[:-1]
+
+ # distortion metric
+ re = masked_psd_y / masked_psd_x
+ im = np.log(re) ** 2
+ Eb = ((im @ fb.T) / np.sum(fb, axis=1))
+ Ef = np.mean(Eb , axis=1)
+
+ if apply_vad:
+ _, mask = compute_vad_mask(x, 16000)
+ mask = convert_mask(mask, Ef.shape[0])
+ else:
+ mask = np.ones_like(Ef)
+
+ err = np.mean(np.abs(Ef[mask > 1e-6]) ** 3) ** (1/6)
+
+ return float(err)
+
+if __name__ == "__main__":
+ import argparse
+ from scipy.io import wavfile
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('ref', type=str, help='reference wav file')
+ parser.add_argument('deg', type=str, help='degraded wav file')
+ parser.add_argument('--apply-vad', action='store_true')
+ args = parser.parse_args()
+
+
+ fs1, x = wavfile.read(args.ref)
+ fs2, y = wavfile.read(args.deg)
+
+ if max(fs1, fs2) != 16000:
+ raise ValueError('error: encountered sampling frequency diffrent from 16kHz')
+
+ x = x.astype(np.float32) / 2**15
+ y = y.astype(np.float32) / 2**15
+
+ err = compare(x, y, apply_vad=args.apply_vad)
+
+ print(f"MOC: {err}")
diff --git a/dnn/torch/osce/stndrd/evaluation/moc2.py b/dnn/torch/osce/stndrd/evaluation/moc2.py
new file mode 100644
index 00000000..7e155f01
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/moc2.py
@@ -0,0 +1,190 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import numpy as np
+import scipy.signal
+
+def compute_vad_mask(x, fs, stop_db=-70):
+
+ frame_length = (fs + 49) // 50
+ x = x[: frame_length * (len(x) // frame_length)]
+
+ frames = x.reshape(-1, frame_length)
+ frame_energy = np.sum(frames ** 2, axis=1)
+ frame_energy_smooth = np.convolve(frame_energy, np.ones(5) / 5, mode='same')
+
+ max_threshold = frame_energy.max() * 10 ** (stop_db/20)
+ vactive = np.ones_like(frames)
+ vactive[frame_energy_smooth < max_threshold, :] = 0
+ vactive = vactive.reshape(-1)
+
+ filter = np.sin(np.arange(frame_length) * np.pi / (frame_length - 1))
+ filter = filter / filter.sum()
+
+ mask = np.convolve(vactive, filter, mode='same')
+
+ return x, mask
+
+def convert_mask(mask, num_frames, frame_size=160, hop_size=40):
+ num_samples = frame_size + (num_frames - 1) * hop_size
+ if len(mask) < num_samples:
+ mask = np.concatenate((mask, np.zeros(num_samples - len(mask))), dtype=mask.dtype)
+ else:
+ mask = mask[:num_samples]
+
+ new_mask = np.array([np.mean(mask[i*hop_size : i*hop_size + frame_size]) for i in range(num_frames)])
+
+ return new_mask
+
+def power_spectrum(x, window_size=160, hop_size=40, window='hamming'):
+ num_spectra = (len(x) - window_size - hop_size) // hop_size
+ window = scipy.signal.get_window(window, window_size)
+ N = window_size // 2
+
+ frames = np.concatenate([x[np.newaxis, i * hop_size : i * hop_size + window_size] for i in range(num_spectra)]) * window
+ psd = np.abs(np.fft.fft(frames, axis=1)[:, :N + 1]) ** 2
+
+ return psd
+
+
+def frequency_mask(num_bands, up_factor, down_factor):
+
+ up_mask = np.zeros((num_bands, num_bands))
+ down_mask = np.zeros((num_bands, num_bands))
+
+ for i in range(num_bands):
+ up_mask[i, : i + 1] = up_factor ** np.arange(i, -1, -1)
+ down_mask[i, i :] = down_factor ** np.arange(num_bands - i)
+
+ return down_mask @ up_mask
+
+
+def rect_fb(band_limits, num_bins=None):
+ num_bands = len(band_limits) - 1
+ if num_bins is None:
+ num_bins = band_limits[-1]
+
+ fb = np.zeros((num_bands, num_bins))
+ for i in range(num_bands):
+ fb[i, band_limits[i]:band_limits[i+1]] = 1
+
+ return fb
+
+
+def _compare(x, y, apply_vad=False, factor=1):
+ """ Modified version of opus_compare for 16 kHz mono signals
+
+ Args:
+ x (np.ndarray): reference input signal scaled to [-1, 1]
+ y (np.ndarray): test signal scaled to [-1, 1]
+
+ Returns:
+ float: perceptually weighted error
+ """
+ # filter bank: bark scale with minimum-2-bin bands and cutoff at 7.5 kHz
+ band_limits = [factor * b for b in [0, 2, 4, 6, 7, 9, 11, 13, 15, 18, 22, 26, 31, 36, 43, 51, 60, 75]]
+ window_size = factor * 160
+ hop_size = factor * 40
+ num_bins = window_size // 2 + 1
+ num_bands = len(band_limits) - 1
+ fb = rect_fb(band_limits, num_bins=num_bins)
+
+ # trim samples to same size
+ num_samples = min(len(x), len(y))
+ x = x[:num_samples].copy() * 2**15
+ y = y[:num_samples].copy() * 2**15
+
+ psd_x = power_spectrum(x, window_size=window_size, hop_size=hop_size) + 100000
+ psd_y = power_spectrum(y, window_size=window_size, hop_size=hop_size) + 100000
+
+ num_frames = psd_x.shape[0]
+
+ # average band energies
+ be_x = (psd_x @ fb.T) / np.sum(fb, axis=1)
+
+ # frequecy masking
+ f_mask = frequency_mask(num_bands, 0.1, 0.03)
+ mask_x = be_x @ f_mask.T
+
+ # temporal masking
+ for i in range(1, num_frames):
+ mask_x[i, :] += (0.5 ** factor) * mask_x[i-1, :]
+
+ # apply mask
+ masked_psd_x = psd_x + 0.1 * (mask_x @ fb)
+ masked_psd_y = psd_y + 0.1 * (mask_x @ fb)
+
+ # 2-frame average
+ masked_psd_x = masked_psd_x[1:] + masked_psd_x[:-1]
+ masked_psd_y = masked_psd_y[1:] + masked_psd_y[:-1]
+
+ # distortion metric
+ re = masked_psd_y / masked_psd_x
+ #im = re - np.log(re) - 1
+ im = np.log(re) ** 2
+ Eb = ((im @ fb.T) / np.sum(fb, axis=1))
+ Ef = np.mean(Eb ** 1, axis=1)
+
+ if apply_vad:
+ _, mask = compute_vad_mask(x, 16000)
+ mask = convert_mask(mask, Ef.shape[0])
+ else:
+ mask = np.ones_like(Ef)
+
+ err = np.mean(np.abs(Ef[mask > 1e-6]) ** 3) ** (1/6)
+
+ return float(err)
+
+def compare(x, y, apply_vad=False):
+ err = np.linalg.norm([_compare(x, y, apply_vad=apply_vad, factor=1)], ord=2)
+ return err
+
+if __name__ == "__main__":
+ import argparse
+ from scipy.io import wavfile
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('ref', type=str, help='reference wav file')
+ parser.add_argument('deg', type=str, help='degraded wav file')
+ parser.add_argument('--apply-vad', action='store_true')
+ args = parser.parse_args()
+
+
+ fs1, x = wavfile.read(args.ref)
+ fs2, y = wavfile.read(args.deg)
+
+ if max(fs1, fs2) != 16000:
+ raise ValueError('error: encountered sampling frequency diffrent from 16kHz')
+
+ x = x.astype(np.float32) / 2**15
+ y = y.astype(np.float32) / 2**15
+
+ err = compare(x, y, apply_vad=args.apply_vad)
+
+ print(f"MOC: {err}")
diff --git a/dnn/torch/osce/stndrd/evaluation/process_dataset.sh b/dnn/torch/osce/stndrd/evaluation/process_dataset.sh
new file mode 100755
index 00000000..a490da93
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/process_dataset.sh
@@ -0,0 +1,98 @@
+#!/bin/bash
+
+if [ ! -f "$PYTHON" ]
+then
+ echo "PYTHON variable does not link to a file. Please point it to your python executable."
+ exit 1
+fi
+
+if [ ! -f "$TESTMODEL" ]
+then
+ echo "TESTMODEL variable does not link to a file. Please point it to your copy of test_model.py"
+ exit 1
+fi
+
+if [ ! -f "$OPUSDEMO" ]
+then
+ echo "OPUSDEMO variable does not link to a file. Please point it to your patched version of opus_demo."
+ exit 1
+fi
+
+if [ ! -f "$LACE" ]
+then
+ echo "LACE variable does not link to a file. Please point it to your copy of the LACE checkpoint."
+ exit 1
+fi
+
+if [ ! -f "$NOLACE" ]
+then
+ echo "LACE variable does not link to a file. Please point it to your copy of the NOLACE checkpoint."
+ exit 1
+fi
+
+case $# in
+ 2) INPUT=$1; OUTPUT=$2;;
+ *) echo "process_dataset.sh <input folder> <output folder>"; exit 1;;
+esac
+
+if [ -d $OUTPUT ]
+then
+ echo "output folder $OUTPUT exists, aborting..."
+ exit 1
+fi
+
+mkdir -p $OUTPUT
+
+if [ "$BITRATES" == "" ]
+then
+ BITRATES=( 6000 7500 9000 12000 15000 18000 24000 32000 )
+ echo "BITRATES variable not defined. Proceeding with default bitrates ${BITRATES[@]}."
+fi
+
+
+echo "LACE=${LACE}" > ${OUTPUT}/info.txt
+echo "NOLACE=${NOLACE}" >> ${OUTPUT}/info.txt
+
+ITEMFILE=${OUTPUT}/items.txt
+BITRATEFILE=${OUTPUT}/bitrates.txt
+
+FPROCESSING=${OUTPUT}/processing
+FCLEAN=${OUTPUT}/clean
+FOPUS=${OUTPUT}/opus
+FLACE=${OUTPUT}/lace
+FNOLACE=${OUTPUT}/nolace
+
+mkdir -p $FPROCESSING $FCLEAN $FOPUS $FLACE $FNOLACE
+
+echo "${BITRATES[@]}" > $BITRATEFILE
+
+for fn in $(find $INPUT -type f -name "*.wav")
+do
+ UUID=$(uuid)
+ echo "$UUID $fn" >> $ITEMFILE
+ PIDS=( )
+ for br in ${BITRATES[@]}
+ do
+ # run opus
+ pfolder=${FPROCESSING}/${UUID}_${br}
+ mkdir -p $pfolder
+ sox $fn -c 1 -r 16000 -b 16 -e signed-integer $pfolder/clean.s16
+ (cd ${pfolder} && $OPUSDEMO voip 16000 1 $br clean.s16 noisy.s16)
+
+ # copy clean and opus
+ sox -c 1 -r 16000 -b 16 -e signed-integer $pfolder/clean.s16 $FCLEAN/${UUID}_${br}_clean.wav
+ sox -c 1 -r 16000 -b 16 -e signed-integer $pfolder/noisy.s16 $FOPUS/${UUID}_${br}_opus.wav
+
+ # run LACE
+ $PYTHON $TESTMODEL $pfolder $LACE $FLACE/${UUID}_${br}_lace.wav &
+ PIDS+=( "$!" )
+
+ # run NoLACE
+ $PYTHON $TESTMODEL $pfolder $NOLACE $FNOLACE/${UUID}_${br}_nolace.wav &
+ PIDS+=( "$!" )
+ done
+ for pid in ${PIDS[@]}
+ do
+ wait $pid
+ done
+done
diff --git a/dnn/torch/osce/stndrd/evaluation/run_nomad.py b/dnn/torch/osce/stndrd/evaluation/run_nomad.py
new file mode 100644
index 00000000..0267bc92
--- /dev/null
+++ b/dnn/torch/osce/stndrd/evaluation/run_nomad.py
@@ -0,0 +1,138 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+import tempfile
+import shutil
+
+import pandas as pd
+from scipy.spatial.distance import cdist
+from scipy.io import wavfile
+import numpy as np
+
+from nomad_audio.nomad import Nomad
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('folder', type=str, help='folder with processed items')
+parser.add_argument('--full-reference', action='store_true', help='use NOMAD as full-reference metric')
+parser.add_argument('--device', type=str, default=None, help='device for Nomad')
+
+
+def get_bitrates(folder):
+ with open(os.path.join(folder, 'bitrates.txt')) as f:
+ x = f.read()
+
+ bitrates = [int(y) for y in x.rstrip('\n').split()]
+
+ return bitrates
+
+def get_itemlist(folder):
+ with open(os.path.join(folder, 'items.txt')) as f:
+ lines = f.readlines()
+
+ items = [x.split()[0] for x in lines]
+
+ return items
+
+
+def nomad_wrapper(ref_folder, deg_folder, full_reference=False, ref_embeddings=None, device=None):
+ model = Nomad(device=device)
+ if not full_reference:
+ results = model.predict(nmr=ref_folder, deg=deg_folder)[0].to_dict()['NOMAD']
+ return results, None
+ else:
+ if ref_embeddings is None:
+ print(f"Computing reference embeddings from {ref_folder}")
+ ref_data = pd.DataFrame(sorted(os.listdir(ref_folder)))
+ ref_data.columns = ['filename']
+ ref_data['filename'] = [os.path.join(ref_folder, x) for x in ref_data['filename']]
+ ref_embeddings = model.get_embeddings_csv(model.model, ref_data).set_index('filename')
+
+ print(f"Computing degraded embeddings from {deg_folder}")
+ deg_data = pd.DataFrame(sorted(os.listdir(deg_folder)))
+ deg_data.columns = ['filename']
+ deg_data['filename'] = [os.path.join(deg_folder, x) for x in deg_data['filename']]
+ deg_embeddings = model.get_embeddings_csv(model.model, deg_data).set_index('filename')
+
+ dist = np.diag(cdist(ref_embeddings, deg_embeddings)) # wasteful
+ test_files = [x.split('/')[-1].split('.')[0] for x in deg_embeddings.index]
+
+ results = dict(zip(test_files, dist))
+
+ return results, ref_embeddings
+
+
+
+
+def nomad_process_all(folder, full_reference=False, device=None):
+ bitrates = get_bitrates(folder)
+ items = get_itemlist(folder)
+ with tempfile.TemporaryDirectory() as dir:
+ cleandir = os.path.join(dir, 'clean')
+ opusdir = os.path.join(dir, 'opus')
+ lacedir = os.path.join(dir, 'lace')
+ nolacedir = os.path.join(dir, 'nolace')
+
+ # prepare files
+ for d in [cleandir, opusdir, lacedir, nolacedir]: os.makedirs(d)
+ for br in bitrates:
+ for item in items:
+ for cond in ['clean', 'opus', 'lace', 'nolace']:
+ shutil.copyfile(os.path.join(folder, cond, f"{item}_{br}_{cond}.wav"), os.path.join(dir, cond, f"{item}_{br}.wav"))
+
+ nomad_opus, ref_embeddings = nomad_wrapper(cleandir, opusdir, full_reference=full_reference, ref_embeddings=None)
+ nomad_lace, ref_embeddings = nomad_wrapper(cleandir, lacedir, full_reference=full_reference, ref_embeddings=ref_embeddings)
+ nomad_nolace, ref_embeddings = nomad_wrapper(cleandir, nolacedir, full_reference=full_reference, ref_embeddings=ref_embeddings)
+
+ results = dict()
+ for br in bitrates:
+ results[br] = np.zeros((len(items), 3))
+ for i, item in enumerate(items):
+ key = f"{item}_{br}"
+ results[br][i, 0] = nomad_opus[key]
+ results[br][i, 1] = nomad_lace[key]
+ results[br][i, 2] = nomad_nolace[key]
+
+ return results
+
+
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+ items = get_itemlist(args.folder)
+ bitrates = get_bitrates(args.folder)
+
+ results = nomad_process_all(args.folder, full_reference=args.full_reference, device=args.device)
+
+ np.save(os.path.join(args.folder, f'results_nomad.npy'), results)
+
+ print("Done.")
diff --git a/dnn/torch/osce/stndrd/presentation/endoscopy.py b/dnn/torch/osce/stndrd/presentation/endoscopy.py
new file mode 100644
index 00000000..05dd4750
--- /dev/null
+++ b/dnn/torch/osce/stndrd/presentation/endoscopy.py
@@ -0,0 +1,205 @@
+""" module for inspecting models during inference """
+
+import os
+
+import yaml
+import matplotlib.pyplot as plt
+import matplotlib.animation as animation
+
+import torch
+import numpy as np
+
+# stores entries {key : {'fid' : fid, 'fs' : fs, 'dim' : dim, 'dtype' : dtype}}
+_state = dict()
+_folder = 'endoscopy'
+
+def get_gru_gates(gru, input, state):
+ hidden_size = gru.hidden_size
+
+ direct = torch.matmul(gru.weight_ih_l0, input.squeeze())
+ recurrent = torch.matmul(gru.weight_hh_l0, state.squeeze())
+
+ # reset gate
+ start, stop = 0 * hidden_size, 1 * hidden_size
+ reset_gate = torch.sigmoid(direct[start : stop] + gru.bias_ih_l0[start : stop] + recurrent[start : stop] + gru.bias_hh_l0[start : stop])
+
+ # update gate
+ start, stop = 1 * hidden_size, 2 * hidden_size
+ update_gate = torch.sigmoid(direct[start : stop] + gru.bias_ih_l0[start : stop] + recurrent[start : stop] + gru.bias_hh_l0[start : stop])
+
+ # new gate
+ start, stop = 2 * hidden_size, 3 * hidden_size
+ new_gate = torch.tanh(direct[start : stop] + gru.bias_ih_l0[start : stop] + reset_gate * (recurrent[start : stop] + gru.bias_hh_l0[start : stop]))
+
+ return {'reset_gate' : reset_gate, 'update_gate' : update_gate, 'new_gate' : new_gate}
+
+
+def init(folder='endoscopy'):
+ """ sets up output folder for endoscopy data """
+
+ global _folder
+ _folder = folder
+
+ if not os.path.exists(folder):
+ os.makedirs(folder)
+ else:
+ print(f"warning: endoscopy folder {folder} exists. Content may be lost or inconsistent results may occur.")
+
+def write_data(key, data, fs):
+ """ appends data to previous data written under key """
+
+ global _state
+
+ # convert to numpy if torch.Tensor is given
+ if isinstance(data, torch.Tensor):
+ data = data.detach().numpy()
+
+ if not key in _state:
+ _state[key] = {
+ 'fid' : open(os.path.join(_folder, key + '.bin'), 'wb'),
+ 'fs' : fs,
+ 'dim' : tuple(data.shape),
+ 'dtype' : str(data.dtype)
+ }
+
+ with open(os.path.join(_folder, key + '.yml'), 'w') as f:
+ f.write(yaml.dump({'fs' : fs, 'dim' : tuple(data.shape), 'dtype' : str(data.dtype).split('.')[-1]}))
+ else:
+ if _state[key]['fs'] != fs:
+ raise ValueError(f"fs changed for key {key}: {_state[key]['fs']} vs. {fs}")
+ if _state[key]['dtype'] != str(data.dtype):
+ raise ValueError(f"dtype changed for key {key}: {_state[key]['dtype']} vs. {str(data.dtype)}")
+ if _state[key]['dim'] != tuple(data.shape):
+ raise ValueError(f"dim changed for key {key}: {_state[key]['dim']} vs. {tuple(data.shape)}")
+
+ _state[key]['fid'].write(data.tobytes())
+
+def close(folder='endoscopy'):
+ """ clean up """
+ for key in _state.keys():
+ _state[key]['fid'].close()
+
+
+def read_data(folder='endoscopy'):
+ """ retrieves written data as numpy arrays """
+
+
+ keys = [name[:-4] for name in os.listdir(folder) if name.endswith('.yml')]
+
+ return_dict = dict()
+
+ for key in keys:
+ with open(os.path.join(folder, key + '.yml'), 'r') as f:
+ value = yaml.load(f.read(), yaml.FullLoader)
+
+ with open(os.path.join(folder, key + '.bin'), 'rb') as f:
+ data = np.frombuffer(f.read(), dtype=value['dtype'])
+
+ value['data'] = data.reshape((-1,) + value['dim'])
+
+ return_dict[key] = value
+
+ return return_dict
+
+def get_best_reshape(shape, target_ratio=1):
+ """ calculated the best 2d reshape of shape given the target ratio (rows/cols)"""
+
+ if len(shape) > 1:
+ pixel_count = 1
+ for s in shape:
+ pixel_count *= s
+ else:
+ pixel_count = shape[0]
+
+ if pixel_count == 1:
+ return (1,)
+
+ num_columns = int((pixel_count / target_ratio)**.5)
+
+ while (pixel_count % num_columns):
+ num_columns -= 1
+
+ num_rows = pixel_count // num_columns
+
+ return (num_rows, num_columns)
+
+def get_type_and_shape(shape):
+
+ # can happen if data is one dimensional
+ if len(shape) == 0:
+ shape = (1,)
+
+ # calculate pixel count
+ if len(shape) > 1:
+ pixel_count = 1
+ for s in shape:
+ pixel_count *= s
+ else:
+ pixel_count = shape[0]
+
+ if pixel_count == 1:
+ return 'plot', (1, )
+
+ # stay with shape if already 2-dimensional
+ if len(shape) == 2:
+ if (shape[0] != pixel_count) or (shape[1] != pixel_count):
+ return 'image', shape
+
+ return 'image', get_best_reshape(shape)
+
+def make_animation(data, filename, start_index=80, stop_index=-80, interval=20, half_signal_window_length=80):
+
+ # determine plot setup
+ num_keys = len(data.keys())
+
+ num_rows = int((num_keys * 3/4) ** .5)
+
+ num_cols = (num_keys + num_rows - 1) // num_rows
+
+ fig, axs = plt.subplots(num_rows, num_cols)
+ fig.set_size_inches(num_cols * 5, num_rows * 5)
+
+ display = dict()
+
+ fs_max = max([val['fs'] for val in data.values()])
+
+ num_samples = max([val['data'].shape[0] for val in data.values()])
+
+ keys = sorted(data.keys())
+
+ # inspect data
+ for i, key in enumerate(keys):
+ axs[i // num_cols, i % num_cols].title.set_text(key)
+
+ display[key] = dict()
+
+ display[key]['type'], display[key]['shape'] = get_type_and_shape(data[key]['dim'])
+ display[key]['down_factor'] = data[key]['fs'] / fs_max
+
+ start_index = max(start_index, half_signal_window_length)
+ while stop_index < 0:
+ stop_index += num_samples
+
+ stop_index = min(stop_index, num_samples - half_signal_window_length)
+
+ # actual plotting
+ frames = []
+ for index in range(start_index, stop_index):
+ ims = []
+ for i, key in enumerate(keys):
+ feature_index = int(round(index * display[key]['down_factor']))
+
+ if display[key]['type'] == 'plot':
+ ims.append(axs[i // num_cols, i % num_cols].plot(data[key]['data'][index - half_signal_window_length : index + half_signal_window_length], marker='P', markevery=[half_signal_window_length], animated=True, color='blue')[0])
+
+ elif display[key]['type'] == 'image':
+ ims.append(axs[i // num_cols, i % num_cols].imshow(data[key]['data'][index].reshape(display[key]['shape']), animated=True))
+
+ frames.append(ims)
+
+ ani = animation.ArtistAnimation(fig, frames, interval=interval, blit=True, repeat_delay=1000)
+
+ if not filename.endswith('.mp4'):
+ filename += '.mp4'
+
+ ani.save(filename) \ No newline at end of file
diff --git a/dnn/torch/osce/stndrd/presentation/lace_demo.ipynb b/dnn/torch/osce/stndrd/presentation/lace_demo.ipynb
new file mode 100644
index 00000000..5e1820a6
--- /dev/null
+++ b/dnn/torch/osce/stndrd/presentation/lace_demo.ipynb
@@ -0,0 +1,313 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "import matplotlib.animation\n",
+ "from scipy.io import wavfile\n",
+ "import scipy.signal\n",
+ "import torch\n",
+ "\n",
+ "from playback import make_playback_animation\n",
+ "from endoscopy import read_data\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.rcParams.update({\n",
+ " \"text.usetex\": True,\n",
+ " \"font.family\": \"Helvetica\",\n",
+ " \"font.size\": 24\n",
+ "})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 513.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 511.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# comparison opus@6kb/s vs. LACE@6kb/s\n",
+ "for name in ['opus_6kbps', 'lace_6kbps']:\n",
+ " fs, y = wavfile.read(f'lace_demo/{name}.wav')\n",
+ " spec, freqs, t, im = plt.specgram(y, NFFT=512, cmap='inferno', noverlap=256 + 128, pad_to=4096)\n",
+ " spec = 10*np.log10(spec)\n",
+ " \n",
+ " make_playback_animation(f'lace_demo/{name}_spec.mp4', spec, len(y)/16)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "data = read_data('lace_demo/endoscopy/')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_adaconv_impz(data, prefix, frame_size=80, overlap_size=40):\n",
+ " win1 = .5 + .5 * np.cos((np.arange(overlap_size) + 0.5) * torch.pi / overlap_size)\n",
+ " win1 = win1[:, np.newaxis]\n",
+ " win2 = win1[::-1]\n",
+ " kernels = data[prefix + '_kernels']['data'][0] * data[prefix + '_gains']['data'][0, :, np.newaxis]\n",
+ " kernels = kernels[:, ::-1]\n",
+ " num_frames = len(kernels)\n",
+ " impz = np.repeat(kernels, frame_size, 0)\n",
+ " for i in range(num_frames - 2, 0, -1):\n",
+ " idx = i * frame_size\n",
+ " impz[idx : idx + overlap_size] = win2 * impz[idx : idx + overlap_size] + win1 * impz[idx - overlap_size : idx]\n",
+ " \n",
+ " return impz"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_adacomb_impz(data, prefix, frame_size=80, overlap_size=40):\n",
+ " win1 = .5 + .5 * np.cos((np.arange(overlap_size) + 0.5) * torch.pi / overlap_size)\n",
+ " win1 = win1[:, np.newaxis]\n",
+ " win2 = win1[::-1]\n",
+ " kernels = data[prefix + '_kernels']['data'][0]\n",
+ " gg = data[prefix + '_global_conv_gains']['data'][0]\n",
+ " g = data[prefix + '_gains']['data'][0]\n",
+ " lags = data[prefix + '_lags']['data'][0]\n",
+ " \n",
+ " num_frames = len(kernels)\n",
+ " max_lag = int(lags.max())\n",
+ " kernel_size = kernels.shape[1]\n",
+ " padding = kernel_size // 2\n",
+ " \n",
+ " impz = np.zeros((num_frames, max_lag + padding + 1))\n",
+ " for i in range(num_frames):\n",
+ " p = int(lags[i])\n",
+ " impz[i, 0] = gg[i]\n",
+ " impz[i, p - padding : p - padding + kernel_size] = gg[i] * g[i] * kernels[i, ::-1]\n",
+ " \n",
+ " impz = np.repeat(impz, frame_size, 0)\n",
+ " for i in range(num_frames - 2, 0, -1):\n",
+ " idx = i * frame_size\n",
+ " impz[idx : idx + overlap_size] = win2 * impz[idx : idx + overlap_size] + win1 * impz[idx - overlap_size : idx]\n",
+ " \n",
+ " return impz"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "impz_comb1 = get_adacomb_impz(data, 'limited_adaptive_comb1d_1')\n",
+ "impz_comb2 = get_adacomb_impz(data, 'limited_adaptive_comb1d_2')\n",
+ "impz_conv1 = get_adaconv_impz(data, 'limited_adaptive_conv1d_1')\n",
+ "\n",
+ "phi = data['hidden_features']['data'][0, 0]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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rq6M/fRPS7/xG9L3v1/9XE88IR092tj2o/kIXwAAAEAdZ8/aT9GXvyzt2yf95m/mg9GePdn5f/u3/H0eeig7//DD2fn9+7Pz//t/5+/zrW9JF18sffCDg2872gvhCwAAAKjjbW+TFi7MhyVJ+vd/z84/8kh2fts2O120yIYZph56SFq61M5v2ZJt/9a3svNPPpm/z1/8hZ1+8YvVbbv1VunBB8+9D+1g0ybp5S+XjhzJb9+5U/qt37IgWxRj7aGdnYjwBQAA0AH27y8fdhajtHGjdOpU89s0Fpw6JX3+89KBA9K//mv+ugcflM4/384/8US2fft2O73mGpvz5VWzs2elH/xAeutbpQkTpEcfze6zc6edvupV1eHLA9yuXVJfX7a9v196+9ull7ykNe//wYPV244ds/a86lXSyZPZ9hilNWuku++W/n//v/x9NmyQ/vIvbV9SZ85Ib3yjDcccLQhfAAAAbWLXLhvKVnTypLRggfRTP1V93c03S69/vfTXf53ffuaM9I532HC148fz18VolRYPCbBq1a/9mvSFL+S3b92anX/ggfx1O3dKr32tnU/D17ZtUggWQE6fzio6/f32XnR32/vZ25vdZ88eaeJE6WUvs/unge3xx6UXv9jet7QNmzZl57/xjeq23X//yFWNtm+X5s2zz19q82YLpd/5Tn6I5dNPZ+f/7//N3+e737XTrVvz7f3856Uvfcl+J9LQKUn/8A/STTcNezeariXhK4SwNoSwscZ1XSGEVZXb3Jxs7wshbAwhMNoVAIAmiLH82/Rjx/IdqdTx49Lzz49su0azd77TAtZXvpLf7nOAvv1t6amn8td56CoOSduyxSo1Tzxh90v90z9ZVebnf766DT/4gfSnf1q9St9o94lPSJ/6lPTf/3t++z332Ok11+SDz3PPWXh6wQtsGGFardq+3Spi3d122ataPq9rwQIbxpgOs9u9W1qyRFq50sL2M8/Y9l27pBMnpJ/4Cbuczif72tey8+nwxhhtRcUrrqgOZX79QO3bJ61eXR0877jDTj/0ofzvfDqU8r77svM+FHP27PwwyTNnpO99z84fOpTtt2Th0X396/n2v+td0vr10p13Dnxf2kFLwleM8Y46V18vqcdvE0JYV9l+XYxxTYxx/Yg3EACANhZjNtG/qL/fOjNFe/faEtbF1dUeflh63eusw506dkzq6bH5GcWO2i/9krR8uXTvvfntP/iBNH269Lu/W/38/+N/2LCisk5futDAWPDoo9bx/sVfzG8/cUK66y47/6lP5a9L5wOlHe7Dh7Mg/I1v5MNyWrFJKySSLWku2RCww4fz1/2n/yT91/+azTNK/Z//I914owWP0eY737HThx6yEOCeflqaPFn6yZ+0UOVDPz0EechKw9fu3dIFF2RDEnftslP/rM+fXx2+9uyxELdsmV32wPb443b6mtfYaRpOnnrKQtb06fnKWzo80ffLfepT0ty5VqFK/eAH9rcgnbsmWRi9807pox/Nb0+HDu7YkZ3fskU67zxpxoz83wgPX695je2b/y3Yu9f+3lx3nV1O59U9+qi9juPH59ubBsFiFe3//T/73WrXeXBtN+wwxrghxrihcrFbUuWtUlcIobtFzQIAoG1s325zIP78z/PbDx2yUHTxxdaZcTFakPqN37AhPKlPfco68zfckN/++c9b5/3ee/PzUk6ezDo7f/mX+ft8/OPZ9jQAHjhgHbjf+q3qEPClL1kVoLgc92hw9Ki9jsVV8r7wBauY/Mu/2Gvjvv99e90mT64Ow489ZkF4/Ph8J99v99a3WvXBO+ySvXdz5khXX129dPndd9t1kvTNb2bbjx/PQluxorBnj1XKNmywOWapkyel97zHhu2VaZcFE/bvl170Ivt9SL+IOHHC9vvKK62tXomRLEilocjDj7/WF1xgQSp9L/v6LOAUw5cPM5w/3xbjKIav886z+/ljSNKzz9rpi15kn400fHnIW7kyC2lSvvqUno/RVmbs768OLddfb38L/vZv89t9UZGvfS3/Pj72mPTCF9r5NPg98ogNkbzssvzn+MknbZ7bK19pvxsecH1/rr3WTovh68UvttfFX8N0n+bOrV7Q5OMft9+t/+//U1tqu/DlKkHrYIzR/0zPlXQwhHBLC5sFAEDLdXfb6muf/GR+Ls/Xv25VjO3b899279qVDSG7/fb8Y/lQtYcfzlcz0uFCaWBLH7c4DCkdIpR2+NIhdMVv4X3ORrGj1NdnnbRf/MX26bgP1steJv3cz0mf/Wx++1e/mp2/++7svIeqt73NOrPp+/H449KP/ZiF67Sj653bN77RTtMKxP33W1Wkuzsfyg4etCrEr/+6XU7n5Xz/+xZKrr7awlt/f3ZdOnSxOMTxf/9v6TOfsTBfHBa5e7cFh//1v9Ry//7v9lnfsiUfSB9/3MLrr/yKXU6rP7t3W+d/4UK77IHJw8D551sISBef6OuzcDtvnl32YFZv2GGt8OWPO2+eXV8MX0uXVoeve++Vxo2T3vKWfMVo164sdKbDEU+ezL5kSUP3sWN2n8WLrULlQdCHRXpgSj+Tzz5r7Vy6NLu9ZJ+55culFSvssn8mvYL40pfalwt+OcYs4C1dmg9fTz9tc+quvTYfvk6dyoaJFr9w+P73pauuqg6Xzda24UvS2hjjjX6hUhHrl9QfQlhbvHEIYV0IYXMIYXNvOnsRAIBR6Od/3oLWD3+YbUurSt4BkbKQtHRpfvhaX591iK6+2qoz6WPdd5+0apXNQUmH73gHb+1au71XuJ5/3oLAz/6sXU6/vf72t6VZs6wTlYayEyey8PCd7+SrdV/6kk3C/5d/yYcDye7zX/9r+8wt+/73qxcD2Lkzez2Lgff737dQOX58ttCAZKF53Dirap49m3WGDx2yTuwLXiBddFG+8vXkk7ZIgw9JS8PXrl3W0V22zNrjFTi/zapVFhDS+XseoN/zHuv8pp3qb39bmjrV5qSl1TIp/7kq7u+HP2xh5v3vr15ifMsWqzaVHVR4OD73uXzIdf/xH/Z6SfnqnQeBVaukSZPy86p8OGAxfPnpokUWmA4cyL4o8PA1frw0c2ZW5UkrXwsX2mf+2DH7LB86ZKHMK5Ieujy4zZljv4/etpMn7fG8KpfO0du1ywLQ5Zfbvnng8t/lq6+2vxH+mXjsMftdXrnSKoC+SqG//2vW2KkHaw9CV11lwwv9dmfP2md10aLqcLlrl1XpLrgg/xgeJs87z14XD6j9/fY3wodvpvv39NP2Wlx2mZ0/ccK233effWnxkpfYZy4dUvsnf2JBtLgwzaFD9uVP8cukkdKW4SuEsNbndlUW31gXQlhV7z6VcNYTY+xZsGBBcxoKAECLXHqpnabf0N97r/TjP26d9LQz652Kt77VOioeWnylu5/7OTtNQ5ZXTVasyFcytm+3zutP/ZR1crzT9dhj9q3zm95k30inq+ht22ahoacnPw/phz+0zpoPeUy/wU6rZWmojFH6mZ+xBSE+8pG6L1FTfO97VuF61avywws9VF1+uXXyvVN+/Lh1zC+91CpS6T5v22ad6Be9yC57yPK5MitX2utYrDIsXGj3Gzcue6+8E7xkiV136lTWEfZOrHfa0/D19NMWsK66yi6nYe7BB21/Xvzi/Gp8koXnN7/ZOslpBVSyoOaBorjwx2/8hn0mfv7nqyuc999vwa1Wp/jw4fKq6GOPWfXw2murn+/uu+3zfvnl+aGFHr6WLbMQ4K9RjFl1qRi+vCo4a5ZVpZ5/3oLUmTPWofd9njUrC1/790vTptlP+ngeSmfPzu6XVr5mz7Yhe0uWZGElnXM2f749t1dLDx60QLhkie2Dt9l/x9/ylvxnwr8EefOb7dSDkX8+i+HLT5cvt8+kf0b7+izoLV5sAezAgezvzYED1k4PX/6aP/OM/c1YtCi/AmRaJTz/fGuTv99PPZUNsY4xe37/cuhd77Ltvr8xZlXmrVvzVcoPftDmqTZr5cRWrXa4WlJPWsHy1Q8r190cQtgSQtgiG254W+W6tdI5F+wAAGDUu+gi+1Y9DV87dliH/iUvyVeeHnzQOpUvfal1DL2z7QFpzRr7tt/vc/y4dYAuvtjCV9oB94Dg4c87PX562WXWUU3D144d9jiXXGIdWe8gesfIJ9qnc8u+8x0LiytXZotQSFYF81DiKwC6Q4dsPssnPlHzZWs4X2b74YfzVazvfc9CzDveYZ10r16k3/J7h9Jt22bv3+LFdtmHbHkHefFi67j29WXf9HuVYeJEe939vdq/3zrBHr6k7H2vF7527rTn8KFh6Xu/d282xO3kyexxjh2zz+FVV9nnIv3s7d1rj/Gbv2mX0/Dd22uv0/nn274Xl71/3/tskYf3vU9VvvpVCynXXls9py6tbKRDPv2zv3Kl/aTPt3OnhZvFi20ffd+8+rJ0qYUAb7dkn7dZs+z30IcKHjyYhTIPUbNn5ytf8+fnrz90KAtfs2bZezljRr7y5Y+/YEH2WUrfR39Mvy4NX1L2uduxw4Lii1+c7bdk79m4cVnI8s+Eh5nVq+20GL6WLbPPn78me/fa6eLFWbj0EOVtKm5/5hlr/8SJ+cpXujjJ0qX2OfNK1tNPZ0E5fV7/fXrlK/OXd+2y35W3vc0+L+nIAK+QfuMb2e/VSGrVaoebYoxz0hAVY1yTXHdRjPHKys+mGGN/jHFrjPGOGGMHrugPAEBjTZpkAczD18mT9k348uXWeU6/JX76abutL3tdDEwrV+aHM6UBYcUKu78PL9y+3R6n2KlL73PhhVnHNkbr8F14Ybb4gHcaH37Y9uOnfsq++fZ9OXPG7v/CF1qYS+eyeOf9l3/Zwlq6UuL69Ra8fvM3ay+FP1R/9mdWMUk77DFaVecNb7DL6fytxx+3CqSHVK9W+eu0ZEl1+Hr6aXv/vJPvHUoPXwsWZNd5J9srX/6Yfp/0eTx8eYd5927raC9aZM9XFr66uuwnDV/PPGOd6pUr8/vkj3vRRba/jzySBSKfb/T619vnJl39zquz/+2/2Wm6lPgPf2gB/IILrHpVXLnuAx+w5/ja16qPi/aNb9iqfWvW5K/bs8cC6YoV9nncsSNr586d9tkdPz4fvvzztXChfVa7uvKVr64uO5+GL69Y+bbZs7PQ4AtxSBawJFt8wq+fNSu7r4evgwezuWNpFc0/A/PnZ9cXg07x93TvXttWrD7t2WP7ePHFdtk/E3v32j4uWWL74e+1/61YutSe25/XvzBYtMh+fFuMWZsmT7YvJjykPvNM1s6yypeHL8nelxizz2mxGrl7t71Gl1xil/33y6unfhBn/wLnmWfsc/y611nwSquhI6Uthx0CAIBzW7kyC1C+dPOKFdapP34869z4RP5i+Nq+3b59nz27fDiTh6/Tp7NtXsWqFb4WLcqHr337rFNz4YX5DlTarunT7TF9iJPPUenuzuY4eSd561a7/bveZZe92hSjHXfoBS+wIPeP/5h/re66y4JZcc7RQDz4oPRf/outXPjhD2fbH33UOsBve5sFnHSo51NP2TbvzHqATF/b88+3y2fPWvv377fOpFcAvCPrndGFC7MKh3dM9+3LOrnz5lVX2JYsqa6k7d5t2yZMsDb292edf+/USvmq58mTFhwWL7b3RKoOXytWWPjyRRrS27zwhVZ5TYck3n23hcBf+AXrlKcdX39f//mf7TSdv7Vzp30OPvIR66z/0z9l1x06ZM9xzTV28OMf/jB7z31fPHydPJmF1XS/fdhhjNnvkIesdB7TucJXWeXr6NEsdJWFr5kzs8fzx0krX7Nn23DBkyezx5w9e+CVL6+UFudd7d1r761/QeLha9++LPBfcEEW1g4etAA1ZUr+c1dW+dq3z/bx9OlsP7q6stc2DaQLFpRXvvz6/n57rJMn7fGL1UgfIjprlv2d8P3zdr/85dZm/xvoCwv96q/aaXGZ/ZFA+AIAoEMtXpx1qNMOcLq8dYxZyFm61L7Z947Vrl1ZVSStfKUBIT3m0OnT1iFassQ6MF1d+fA1f75VB5Yutctnz+bnhhSX3fZ2SfkJ9d4xuugiC5jPPZe16b77bL5OT49d9orIU09ZePuN37Dr02Nhbdtm1anf/E3r6NdbPfHgwerjpP3jP1og+oVfsADmnUYPWy9/uQ25SxcT8SrWhRdawPDwVRx2ePp01jk9dSqrYCxalA9fEydahzINXzFmnWnJ7uvVEu8EL1mSDwZS1kGV8kPATp+29vn7lL4n3hbvoI8bl32O0vfYA75v277dQsa8efZeptWmhx6ysDxjhoW2dNjpAw9Y5/nVr7bHTedueTXrzW+2isVdd2Xv6dat9vivfGU2d84fNw1fxWGVXhHy9+b4cQttHoA8ZKWVmUOHLPhI9cNXWq06dsz2S6pf+ZozJ1/5SsOXP3dZ+CpWvjx4p5WvxYvt/ZgyJQsl/jmaMsU+E/7+9fZmn5H587M2+YIikj1Wf3/2WZbsPmnly8OZf77T8HX4cLZf/hxnzmT7Mm9e9vr39eVf37lz7bOYVr7OP9++gEkry7t22d++JUvyx2Tz9//Vr7YwmVbZRwrhCwCADuXHCUpDzrJl+ZDT12ffEp93nnVSFizIr9bmHat0Ceu0apJ+s3zwoHVyfZsvP+338c7rwoXWeervz3fai5UvX8K7+PzeMerurh7i9sQTVkWZMcP20zvWPqyop8c65N/+dja37H/9L5v0/773Sf/2b9lxi9zZszbkbc0a6+hdfnl+wYXPfc7mvPzqr9pr6UHgBz+wsPmCF9jPjh3ZYguHDtl7MXmyvS7p8K5Jk6qPAVXsnKbhy6sPIWSd7N5ee45Tp8orX2kFwoObX1eslkl2nX+W/D1Jg4a/N4sXWyd24cKsfTt22HMsWVL9HvuQ0xAs8Dz/fPZY27dnVbQXvrA6fL34xfaZfdWr8ocouOsua/+LXmSd5t27syDoYfwlL8mOQeXVDO9oL1tWHb72789e27TKUqx8zZqVBaWhVL6OHatf+SobdnjgQH7YoWS3T8NXOuzwxAn77M+da5+1efPsNU/Degj54ZUeyiR7/9Mhr/43Ys6cbN+K4cu3+euVtunAgWxfyipfaYhdsMDaeeCA7cukSfY6+XP19+cfa9w4e9+KlS8p/+VBOqw0DV9PPWWf3fPOq16uf6QQvgAA6FCLFtm3zX191ZUJyTr1aaVFyg+bSjtWS5ZkCzns2WOhYc6cfPhKhwH5fdKhimmn3e/jnaIFC6zjOHNmvvLlHaX0sZ56yjpJ55+fHyrpFbALL7RtL3xh1rH2Tvdll9mKjydPWhXkzBnp1luzg1J3d9sBn8+etddu/XrrjP/ET1hV7bd+yzrkfrDgBx+05/65n7Ml0ceNyybrP/SQVWwmTLA2nT5t++ZBYPnybN+KITWE/JCweuGrtzd7Tf10//58lcHve+SIBbK+PnsPp03LrisOSZOy0wMHsuvT5+rttc5wWkmT8sHbh1iOG1cdvrZvz94vfz127MhWqEvfy2eftY54jBa+XvISu+7qq+3x/Pk2b7ZtIVgwk7Jhig89ZPu6aFH1ojQ7dthndPLkLGh44Dx4MPtce6BKw5d3/mfOzIYxpuErrUgVg1Q65+vo0erK15Ej1feZOdNuG6Ndlz6WP8+hQ1atmTixftDxatLRo1bR832fPz9bHj+toKZD/9K/EWkgTMNXOuTx0CHbL19i3/evLHx5kPOFS6RsPw4ezAJxCOWVL38s/5vmn1P/jKbHBtu1K/u76PP9JPvsXnCBfXYvvpjwBQAA6vBO1N691vmYMcM629752LMnP4RQqh++/LHSgJAOZ0qXfvb7pNWydNK8ZB33dLGI9D5HjtiPt2vJEuscHjlibV60KFv8QMqHGq9aeLUkRut0r1hhHb6Xvcyu/973bCjgM8/YiooTJ0p/+Ie26MMHPmDHxrrpJjt48b/+q3XI/vzPLZz9279Zhetzn7PX4Wd+Jhsel4avyy6z8x4itm/PVyH9fUpDavF12r+/PHz5a5cO/Zozx9qzf391JzQ9OG9fX9Zh9etqDRmT7LpiGxYssCB35Ei+kuanaaD092n2bPsM+pDX7duz9yutNh08aKHCw7VXqR591F6jgwez8LVqlZ1u3WqfkUcesSAs2es/aVI25POhh6xiFoJt7+7ODzv0NnR12eert9dC1Nmz9cOXb5sxw9ogWWjw7VOn2umJE9mBzz1kzZpl230p+oEMO5w2zR7n+eftC4T0saSs8uXP79XN/furg45XrNLFMKRsZcH+fnuf01DW25sN/fPPaVr56u+v/gzt358fQjhxog1jrBW++vuzcOn38f07ciRf8UvfE38sf37/kuDIEfsCJP0d8i8P/Bhjkn1W/e+Pf3EgZXNoi6tnNhrhCwCABgkhrA0hrA4hrGvG86VzKtIgNXGidUx6e/PDxaQsfPnBXf0+6aIMaYfLj0mUVrG8k7pwYda5Se9TrHxNm5Z1Hr2jVKzI+ekzz+SD3OTJtp+7dmWLeKTVEj8A8YMPZstnL15snezvfMcOqhuC9JM/ade94x12/s/+zIYN/su/2Byit789a+Nv/Ibt4+//vvR3f2dVMX+denosfB06ZGGwLHwVK1/FoOL7mgbbYvCZO9c65c8/n1+e3Jc1906zlHVc0+pH2jn26/yYS0eOVHecfZhXui19H7396efFt+3dm30Wfa7N7t3WqT56NOvc+uvx1FPl76VkwcqHkHr4uuIKe9wtWyw4x5jN+Zs40W63dWsWwv098cf3MLxjR9aGdAhusaKbHmerr88qm15B9MrX2bP54XLjx1vYO348C19+n7RalYavyZPtfh6+Qsiumz49+x2t9Vj9/dllyQLKoUPl4evgwfJK6YED2ftYrHwdPGj7mVa+nnvOgmRZgPfKV9omH6ZZ1qb+ftu/s2ez0OWnXkH0y5Mm2WtQVvny97H4HAsWWHuPHs0PR0yrs368MMlOn38+ez1GCuELAIAGSI5FualyefVIP2et8CVlIafY4fKKSrqCnpTvQKXfOKePVax8zZtnHZv+fhvmV9Zpr9Wu4rfw6RC8NHxJ1pnfuTO/YIKULSf94INW3fDwJVlguvNOq1z19OTDi1e1nnjCwljR9Om2/PlXv2pByo9RJdnCGr290pe+ZJe9o+/D7rzyNWlSft+8kpBWvqZPtw54Wfjyb/q9k10MUvv3Z3N+/Lbpe1isfPliHB7Y0gqE36es8iVlYdmPxSTZvu3dWz1kTcrmEvnQQw+bfmDhHTuyRVW88tXdbe/No49m4cvfz5kzbU7d1q3Z0vVe+fLzW7fa637kSD58LV+ezcV7+unssyNlXx4Uw1ex8tXVZcHI23L0qP2cPZsPGl6tOnEiq7xJ2W38mGEesELIKmmHD9tjjxuXPZYHB6l25asYdMqqTL5yYtlwxP3780OD/fTw4ez9SytfUhaAyqqnxTZ5WE2PY+avcX9/ft5acf+OHMmGLvrzF+d8+WnZdm/3jh0W8vwz6uFr+3b7XKfhS8ofVH4kEL4AAGiMqyRVupTaJmnVSD9hcdhh2gH2b4N7e60T6J2YhQutQ+cdjHQlMykbXlgMX2knza/zU58nUdZpT+cr+XVloTAdKlkMX37csu3bs8nxUlYt+cIXbLhR2ul+y1usQ3b//bZKYWriRFsNz/e5zG/+pg0//Ou/tiGH7qqr7PTv/s5O/TknTrSQ6JUvn0ci2ft09qxt7+/P2u/DOtPwVQxFvohBGqS8wjGYytfcuVkok7LrJkywxztX+Ern0vg+eZWgvz/7LEpZ+EqPBeU8DBUrX+kQwQcesDCb7vOqVVb52rzZXuf0+a680trw//6fXU5D+PLl1v4nnsiO8eWKla+yIW7F13HGDHsv/csDDwuSDT30YYfTpmWBzYck+vN4kPLH8/CVPpZXuvw9Kat8lQWdWlWmsorRvHkWSvx98t8HP/VDP6TVWMles7R66m3wQJjuh1e+PET6fnR1Zatrpo+Rhq+jR/PhywNbX5/9vvlj+f4Vf4f88/vQQ3ZaDF93321fHnjo8s8G4QsAgM7QVbg8r3iDEMK6EMLmEMLmXk8yw3nCLuu01qt8efjxjqDf5gc/yG4nVVe+0mDic0D277fO0eTJ+fsUO2mTJ1unqVb4OnAg67ymc8Ekq3D19lZXvnbtyoaNeai54ALr2N5xh11OO92vf70FrEWLqsPXQIwbJ/3RH0nvfW/22km2EuLkydJXvmIdZx9SJ2XHN0vnkaT75geI9vDl+9/ba6/J7NkWhqQsAOzZY1WbtJPtq+cVK1/FykSx8tXfnwWAYiXN53xNn27zdLxtUha+0sDj5/24XcXK15492WIH6f76scO2bbPPVdq59jl86WIbbtUq+2x88Yv5qpdfJ2WB+Md+LP98kh14Ob0s1a58pZWq4uvo7fXPrwcAP+/DDj1wSdnr6eHA53r5eZ/rmG73x/U/E355KJUvrxj58xcXyij+/vr77nPl0seRsgp0ughJvTZ5kJo+Pfvd9dt40CkuKFKr8uUVvLlzs99LD3K+omi6EIeUhS+/7OHLF2mh8gUAQGfqlzS33g1ijBtijD0xxp4FaSIZohCsQ7F3b35RBik/VDB9qlodktmzbdjXM89Yx6ds2GE690iqHb78Pvv2lYfCM2ey+6RDvaZMsRUHY8yHLz8Q8P335zvP48Zlq+TNmpVVwiTrrH7729lwuUaZPNkW6pDsQL7jkp5UGr68IydlQcXDV7pvXvlKlzqXqju6aQDw40b199t75pWUtONaVvnyBTD8cnqdz/kqvodSNuxwMOHr+eez64rh6+mnbalvH3LoXvIS+1z6ohmpq6/O9u2Nb8xfd9llVgm57z4LXmnn39+Hr389e/50//bty0JJOjR11qys8lWsLklZ5TYNX175OnGieruUPU9Z5eu55/KBzW/j4csvT5pkvyfnqnz5HCnJPgcxVn+W/L32kOWX/XXwVSKLC7r4Mu1p9XTqVAtLtYYdpgeX9v2Wsqqb3ycNcsVA6iskpkMe03b4UNZi5cu/aPLP6PTp9ljf/KZd9s/IjBl2X3+dRgrhCwCAxrhHWfWrW9LGZjzpokXWuThzJh9yFi7MKkwDCV8hWOerOIRQyle+BhO+alW+JOmHP8wqd/78S5ZkKwmmQ9XSxRh8mJrzIYErV2bzkVJp1apR3v9+O/3v/z2//cILsxUm/fhVUhZU7r3XTtMwkg47TF8/7yCXhS9futw7ur6PXj1I5yoVH8/DV9liHMU2TJ9uHfh9+2pXvu67L39Zyt6773/fnjcNIsuX2/zA7363Onz5svFSdeXrmmuyOX5vf3v+usmT7fACkh14OeXHidtY+W1MK5ILF2ZzmyZPzoci7+gfOZIfRjfQyle6vVj5Sp/H55A991x2u/RxvSqXPt6MGTZcsDhUMV3cIq0MpeFk1iwLl1K+8pUelsB/R4u/18WQk36GPPilKxembTpX+PL9mDjRgtyhQ+XDDsuewz/bHgprDTtM/z6+8IUWkqVsFURJ+tM/ldau1YgifAEA0AAxxjskdVcW2ujyhTdG2uLFWUUlDSxeYXr88drhK12FULLOmH8LnoasBQusQ/n00/nH8k5OWWBbsMA6aSdPloevH/wg3xmSLHz5wZTTypF3uqV85UKyAx9PnSr9wR+oad74RusYvuIV+e1pMEwrNwOpfJ0rfJUNOyxWZSZMsPdz925779POcTF8pZWvdNhhsUq4YIG9v6dOVc/5krJAWRwmKtlqk37eeRg6ftyOq5RKX8/XvjZ/XQgW5h5/PN8hd//wD3Ystve+N7990SJ7jfbvtzamAcc/iw8/nB1PyqXhK30+Dw1DGXZYb87XyZMDD1/Tpln4On68Osj5sMP0/U1DU7o93f9587L998/Ao4/mj9dVq/IlWXg6eNACTa3KV/o6eruL4cvP791rc+vS+6QBL729t+PJJ7NFbPw5pk3L2pv+HXrRi+x0xYrs9pJ0443StddqRBG+AABokBjj+hjjphjj+mY956JF2XFp0oqKD7Hp66sOUr69GH7mzasegpTe55FH6le+0o7dwoXZN+Tp83i79uzJd4aK7U/DV1ohSefzSBY4jx+3BTaaKe30Ol/+XMqHr6lTrUP67LP5A+JK9hr4MZgGU/k6etQ6u2lHV7JOqc9ZGWjly4cdFgOgt88PYJ1Wt2bPtqqld9DT8JV+CZAGZykfsLxa5ebMkf7kT2w+XfpcbubMLLwVLV1qK1umnxvJAsWll9r5YnD3z6WHr5RXF4vhq17lq9aww3rhy4NUsfJVa9ihn/eDI6fP43O+DhyoHlYq2Rcb6fvur/GxY/n33atmR47kK2izZtn5WpUvn+NXVvkqDiH0/fHhm+lrPGtWttJicfu5wlfaJin7PMyenX99PfgX5w82A+GrxOtfXz2eGACAdlSca+OKCwu46dOzjk8xfM2fbx06KR+MysKbZB2/yZOzb7V9CGHxdun5tHNcDF/egZ89u7qi4970JrUt7+RL1R197+ied16+wuKv7a5d1UP+xo+vHb4kW2Ag3e7XefiqVfmaPj0/RHPePKuk7d1bHr68DWkgCiG/Tz6UTbLPpO9jOg9PyneaX/lKVfm935PWrKnePhz+Gl9+eX67f/527qze73S43EDDV61hh/XmfE2ebFWvwQw79OPuFR9r1iz7/d25s7zCdfJk9Vwp/xyk+z9hQna79HHGjbPttSpfvuBFsfL13HP2BUPZsEN/HYv7USt8nTpl+55+jvyzffBg9d8U/+Km+PvoId6P/9dMhK8SJ09mB7UDAKCdlc3DkfJD4IodYA9d9SpP6dyYWkGs2AFPlQ11lKyj5B2qYofI5/n4Kn6phx+24JB28tvRXXdJf/u3+YU4pCxYphUiqbySKNlr29WVBanisEPJhoEWK19p+CoLbDt25DvU6fMeOVIevlyxGuWX0zkzknXePWQXP3uSLQm/fn0+UIykdess0P3pn+a3p5/LYuVr1izr5J8+PbTwVW+1wzRoTJli4aRW+Cqudujny8KXt634Hqdf0KTb09/fWu978bMyZ05Waa9V+SoOIZRswZayytezz9rnJf3ipl74kqxaVlb5Ku6rlP0dLM4hXLvWFmF5z3vUdISvEuPH21hpAADa3ctelp1POzBpx7I4VM+vK1a+0kUi0o57rSqWlAWo4nCvevfx/7HFDtHP/7ydpsHPXXJJdVhrR9dcI/3Kr1Rv905u8bWoFb6kbIVCqXq1Q8kqM8XO8ezZ1pGXyitfxe3F560XvorB0d+nYvCWpBtusNPivC7JFsX4wAeqt4+UN7/ZhjIW97vWlwpS7QBQHA44mGGHfly2NGSdq/JV9jzTp5dv98/F6dP5z0VXV1bhKu6nvybF96lW+PLLU6bk25uGobLVIZ99tnb4mj49Xw2eNSsrgpSFr3rPV/yb5rcrfkEQgq1aOhIL8pwL4asE4QsA0Cl8Ce6itFNRnCfj3w4XOyq15tMUj/mV8m+WBxO+jh+302L4mj7d5hfddVd5OzqZr8pYXEK9Xvjy92fy5HxHN+1sFu9TtgqclM3XkapDSNrBLltwo+yxpez4aT40LvUnfyJ99avSy19efV278MMrSOXhyys8Aw1f5xp26OErXeChVvjy5/HXNq2kTZtmVcr0dsV2Fitc/pzFFSb98Ytznzy0FP8m+Gen+BlKn7s458uVDTs8cya/vXj/9Lpa4Sudc1j8m+Z/617wArWNCee+ydhD+AIAdIoQbLnv06err/OVxopLsP/+79v8pOKQm7TylUo78cWl3r2iUxySdsUVduorjqVe8hI7kK6vOJa67LLyNnS6t7/dVlErrtSXhptiBck7ksXtaee0WJmoNRxr3Ljs+GC1hh0Wz0v597tYJXjzm6V3v9t+isaPl37iJ6q3t5MQsspiWfhy6Xs2aZK9lh6kiqHo+PFsuXTnwceH06bhy4cdTphQXvk6cMDOp699cfENl/6eFt9jryQVf8d9tcFi+Hr6aTstLopSPOCyO1flq3h+6tTs9S+Gr1qvfa3wJdnfn2eeqQ5f73ufbbv+erUNwleJCRMIXwCAzlFcSMBt22YHuy268sryVb68o13siI4bJ33kI/btcrFyU5zb5JYssbkZhw9XX/flL9txvtIO6lhQdlztNOykqyVK2bf29ebT1at8FTuoXV0WAAYz7PCtb7Uw4WE6NX689Dd/U729k1x1lXT33dWVkVoBIAQLQ0ePWlhK5yBOnWr9x8OH8184TJhgPx6+isMOz57NHs/5/c+cqf7yolhtc7XmdklZyCxWvv72b6Wbb64e0ut/A1796vz2RlW+QrDgePRo9cqhZYtpFLeXfbal6vA1YUL1ceFajfBVYvz48m8QAQDoJMUQdS5Tp9rxksqGin3oQ+X3efObbSGDN7yh+roFC8oDx+LF5UuJj0XpPL1iGPWOZPG1SsNYMSx5J3Tq1OrFSbxTXOw4p6tkFh9vwgSr8njnfbT58pctLBXnBNUKX1IWGmqFotOnq9/LqVOzoYLFYYeSBbB0+8SJ9tqfPl0dTorzv1z6OSmGL1cMX7/yK+VzFP/3/5a2bKkOM75fxS8E0uerFZKK++GvY73KV3FFxbLzUvbZTl/DdsWcrxIMOwQAjFXvfOfg5ke8/OXWcWznuT3t7sMflm6/vXp7OucrlQa2YljyOTonTlQ/ni+qUAwGkyZZ9edd76oOIVL1wbhHk9mzy/e5Xvjy8DPQipSUVbUmTMhXi9NqV3peyl7z4mMVjxPm0hBTDF8f/aiFs2JoqWXJkvLDOvzUT9lw0vWFIxmmcz7Tz2f62hW/DPL9qBe+0s9qraGNkh1gW6o9b7WdEL5KEL4AABi4VqwYNpr86Z/a0tdF3hmvt7x+sZNdNjzQveMddlrWx7n6aunv/i5/TLWxbCjhqzj/K+XvZTFIF+d/DeR5alW+0t/D4ufipptsTtRwrVljC6kUhynWWok0fR2Ly8B76Ko37LDW9uL+vfOdtjplcehuO+JXrARzvgAAQKt5BaF4IOVUsfJVr2r5cz8n/eu/WvUC9dUb4jaQUFSsLtYKX/UqX/54ZcP1ys6nag07HCnF1U5dWtUqhq+BVL5SU6faAiBXX22HnigqO+RBOyJ8lWDOFwAAaLUbbpB+8IPy+Xa+RHkxfI0fbx3TskVYQmi/xQfaVRoIiuHXQ0MxYNUbdui3bXTlq9aBqgc6vLBRitVBl1ZSi/PHfOhgMUAWhxS6EKRvfGNo7WsnhK8SDDsEAACtNmmSzdUpc/fd0he/WN1hl6SHHx7Zdo0FvgjJJz5RfZ0HnmJ1qdHDDmvN+UovF4ekvuxl9tlox6HAxTlfPT3Sf/xH9WIuHhzbcR8agfBVgvAFAADa2eWX1z7EAIZv9mzrC5YdSsHDT3Elz1YMOyz66lfLD+/QDNu315+fWLzuFa+w0wcfzG/38DVa5x+O0t0aHsIXAADA2FbrGHYeiorD6Jo17NAPlPyyl5W3rdZQxJFWa9GNWvwYYv/pP+W3E77GIBbcAAAAQBlfF6DWMbCK56UsEA2m8lVr2OFll9nB0+tVmdrJZZeVr6Uwa1b58eM8fP3ar41su1qF8FWCBTcAAABQ5uhROx1M5csDRSMqX1JnVYWKwwrPZdIk6dix8vmMo0EHvXXNw7BDAAAAlPHwVW/OVzEw+Qp+xUCRhq9iMKs152ssaNXQyWbgIMslCF8AAAAoc+yYnRaXRK837NBv68duc2kYK64GWK/yhc7VkvAVQlgbQth4jutXhxDW1ds2UpjzBQAAgDLLltlpsfI1eXK2PHqtytfzz1ffxy1Zkr+u1pwvdLaWhK8Y4x21rgshrK3cZlPl8uqybSPZPuZ8AQAAoMynPy194QvSypX57SFkFa9ala/nnstvTytfxQU0xvKww9GsHYcdXiVpW+X8NkmramwbMQw7BAAAQJnZs6W3vKX8umnTpIkTqxfEqBW+ivO8io+VnmJ0aMfw1VW4PK/GtpwQwroQwuYQwube3t5hNWD8eOns2fLlLwEAAIAytY6zRfiCa8fw1S9p7gC25cQYN8QYe2KMPQuKg3AHyb+tOHt2WA8DAACAMWTq1Oohh1IWvk6cyG/3BTje857q+/hwQ4Ydji7tuNT8PcoqXd2SNlYuF7eNGB9ze+ZM5xzADgAAAK01bVr1ohpSdpyvYuUrBFu6vuyYVi98oTRzprRiRcObiRZq1WqHqyX1+EIalW0bpR8txtFduU1XjHFT2baRbJ8HLhbdAAAAwEANdtihZJWtsi/7L79cOnxYWrq0sW1Ea7Wk8lUJT3MK29Yk59dXzm6qt22kpJUvAAAAYCC6usq3e+Xrkkua1hS0qXYcdthyhC8AAAAM1v/8n+XDDmfMkL7yFWnViK7XjU5A+CrhC24QvgAAADBQF19c+7o1a2pfh7GjHVc7bDnmfAEAAABoNMJXCYYdAgAAAGg0wlcJwhcAAACARiN8lWDOFwAAAIBGI3yVoPIFABiKEEJfCGFjCOGDrW4LAKD9sNphCRbcAAAM0XWVY1kCAFCFylcJKl8AgCHqCiF0t7oRAID2RPgqQfgCAAzRXEkHQwi3lF0ZQlgXQtgcQtjc29vb5KYBAFqNYYclWHADAFAmhLBWFrBS23yoYYxxQ+V2/SGEtTHGO9IbVq7fIEk9PT2xCU0GALQRwlcJ5nwBAMoUw1QqhLBO0uYY49YmNgkA0EEYdliCYYcAgCG4TfpRdaxuUAMAjE1UvkoQvgAAgxVj7Je0tfJD8AIAVKHyVYI5XwAAAAAajfBVgsoXAAAAgEYjfJVgwQ0AAAAAjUb4KkHlCwAAAECjEb5KEL4AAAAANBrhqwQLbgAAAABoNMJXCeZ8AQAAAGg0wlcJhh0CAAAAaDTCVwnCFwAAAIBGI3yVYM4XAAAAgEYjfJWg8gUAAACg0QhfJVhwAwAAAECjEb5KUPkCAAAA0GiErxLM+QIAAADQaISvElS+AAAAADQa4asEc74AAAAANFpLwlcIYW0IYXUIYV3JdatCCE+GELZUfm6ubO8LIWwMIXxwpNtH5QsAAABAo01o9hOGENZKUoxxUwhhXQhhdYxxU3KTuTHGiyq3XSWpv7L9usLtRgzhCwBGlxDCCkmrJHXL/q9sjjHe18ImAQDGoFZUvq6StK1yfpvsn+GPFAJWd4zRb9sVQuhuQvtYcAMARokQwktDCLdJuknSRZLulRQkvT2EcGsI4T0tbSAAYExpeuVLUlfh8ryyG4UQ1sUYNySb5ko6GEK4JcZ440g1TqLyBQCjSE+M8frCtjv9TCWcXUEVDADQDK2ofPXLgtS5rEkvxBg3xBj7JfX70MVUZQjj5hDC5t7e3mE1kAU3AGB0iDF+uritMgTRr7+X4AUAaJZWhK97lFW/uiVtLN4ghNBVuLyuMv+rpko464kx9ixYsGBYDRxXeVWofAFAZwshvK7yxdyXQwhXhBAOStoUQjgTQnh3q9sHABhbmh6+Yox3SOoOIayW1OVzvEIIaQibK+lgcvm2ym3WJo8xYkKw6hfhCwA63i2y+V4fk7RFtnjTStmQ9/WtbBgAYOxpxZwvxRj9H96mZNua5Pw2STcml/slba38jGjwcoQvABgVQozxTkkKIWz38zHG/hBCX2ubBgAYazjIcg3jxzPnCwBGgZicL4YtwhcAoKlaUvnqBFS+AGBUuCiE8GXZ8vJXVs6rcrnuXGIAABptwOErhDArxnh4JBvTTghfADAqXNnqBgAA4M4ZvkIIL5V0u6QVkiaEEDZLeneM8f4RbltLTZhA+AKAThdjvLfVbQAAwA2k8rVBdsytmyuXr5f0ZUkXj1Sj2gGVLwDofCGEJ5TN+yoeY/LWGOP7mtwkAMAYNpAFN7pijNtV+edVWYnw0Ii2qg2w4AYAdL4Y48oY48WS7pW0LsY4L8Y4T7ai7rzWtg4AMNYMJHzdGUK4VXZsrteFEG5TyYGRRxsqXwAwqlwYY/ysX/BjTrawPQCAMeicww5jjO8NIbxN0lWSXi/pFj9OymjGnC8AGFW2hxA+IunWyuW3S9o+nAcMIayVdGN6nMrKtn5J3THGDcN5fADA6DOg43zFGD8bY/xQ5WfUBy+JyhcAjCYxxuslHZS0XtLvSTpQ2Tacx7wjvVwJXooxbqpcXj2cxwcAjD41w1cI4YkQwuOVnwOFn79uZiNbgTlfAND5QggfDSFcIUkxxo/FGF8fY7w+xvixEMKsEMLv+vUNcJWkbZXz28RxxAAABTWHHcYYV0pSZY7XrT5WvvLN3nXNaV7rUPkCgM4XY/xQCOEDIYT1soWjtslWPeyqnF9fWVSqEboKl6sW9AghrJO0TpKWLVvWoKcFAHSKgSw1XzVJOYRw0wi2qS0QvgBgdIgxfkzSx0IIs2WLbPQPJHBVvmwsLk+/zYcVlugvuX2xLRtkh3BRT09PrHdbAMDoM5Dw1fBJyp2ABTcAYHSJMR6SLTk/0Nvfce5b5dyjrPrVrTGwMjAAYHDOueDGSExS7gRUvgBg9AkhzGrgY62W1JMstHGH7LAsq2XHyKxVIQMAjFEDqXz9aMjGCLelrbDgBgCMHiGEl0q6XdIKSRNCCJslvTvGeP9QH7MSruYUtq2vnCV4AQCqnDN8Vf5BFcelb44x/trINKk9UPkCgFFlg6Q1km6uXL5e0pclXdyyFgEAxpyBHGS5x89XJivfIOnCkWxUO2DOFwCMKl0xxu0hhChJMcZtIYRDrW4UAGBsGdBBll2M8VBlpaZRf+wSKl8AMKrcGUK4VTYn63WVw6iwIAYAoKkGMuzwA8oPO1wpKYxYi9rE+PHSqVOtbgUAoBFijO8NIbxNdiDk10u6JcZ4Z4ubBQAYYway4Ma2wuV7x8I/LCpfADC6VI5Z+dlz3hAAgBEykPAVY4yfSzeEEN5a3DbaMOcLADpfCOEJZaM3igdAvm20Lx4FAGgvNcNXCOFaSTdKujaEcEN6laSXShrV4YvKFwB0vhjjSkmqzPG6tVL9UuXYXNe1sm0AgLGnXuXrHknbJX1U0k1K5nnFGLePcLtajvAFAKPKhR68JDsgcgjhplY2CAAw9tQLX+tijB8PIWyUtDa9IoSgGOPHR7ZprcVBlgFgVNkeQviIpFsrl98u+4IRAICmqRe+/J/SNkldheuKB10edah8AcDoEWO8vrJ673pJ/ZLuiTF+qLWtAgCMNfXCV3cI4XdLtgeNgfDFghsAMLrEGD8m6WOtbgcAYOyqF76KS8yPKVS+AGD0CCFsVvUXh5tZ7RAA0Ew1w1c6MVmSQggrKtt3jGyT2gNzvgBg9Igx9vj5EMJsSTdIurB1LQIAjEXjznWDEMK1IYSDkm6XdEcI4UAI4SdGvmmtReULAEanGOOhGOMGSata3RYAwNgykIMsf0rSlb68fAihS7YM/cUj2K6WY84XAIwelcU20mGHK5UcQgUAgGYYSPg6lB7XK8bYH0I4NIJtagtUvgBgVCnOY743xnhnS1oCABizBhK+NoUQ7pEdGyVIul7SkyGEt0pSjPFzg33SEMJa2VK/3ZWhH8Xr+yRtlrQxxrh+IPdpNMIXAIwqsfj/KoTw1qH8DwMAYKgGEr6elHSgcj5Kuq1yepGGsOR8JUQpxrgphLAuhLA6xripcLPr0m0DvE9DseAGAHS+EMK1km6UdG0I4Yb0KkkvlUT4AgA0zYAqX7J/XF3JtjiM5XmvklXRJBsGsqryHKmuEEJ3jHHbIO7TUFS+AGBUuEfSdkkflXSTknle6ZB6AACaYSDh6yuSbpb982qErsLleSW3mSvpYAjhlhhjMfiV3ieEsE7SOklatmzZsBvJghsAMCqsizF+PISwUdLa9IoQgmKMH29RuwAAY9BAF9z4mwY+Z78sXNXkc7pCCP3JXK+B3GeDJPX09Ax6OGQRlS8AGBX8i8Ntqv4ib9j/KwAAGIyBhK9bKgtu5Ib5xRg/PMTnvEfZP8BuSRvTKysVrM0xxq0Dvc9IYM4XAIwK3SGE3y3ZHkT4AgA02UDC1wclrVf1Mr1DEmO8I4TwwRDCakldvnBGCGFjjHGNbEGP7mSRjTsq11fdZySNH2+nZ89K4855KGoAQJtqyP8uAAAaYSDha3uM8dONfFJfPl5JNa0SvBRj7Je0tfJzR737jKQJlVfmzBnCFwB0qhjjZ9PLIYQVle07WtEeAMDYNpDw1d/gYYcdwStfZ85IEye2ti0AgOGpLDl/u+zwKSGEcKGktTHGr7W2ZQCAsWRAc75GvBVtKA1fAICO9ylJV/ry8iGELtl84otb2SgAwNhyzvAVY7yzuC2E8NaRaU778PDFohsAMCocSo/rFWPsDyEcamWDAABjz0AqXwohzJK0WtINsuOkbJL0uRFsV8tR+QKAUWVTZQj9rbKVDq+X9KR/mRhjHNX/0wAA7aFm+KoErusrPz2SNku6UNLcGOOo/7YwXXADANDxnpR0oHI+ylbWjZIuEkvOAwCapF7lq1/SFkk3xRi/KkkhhM1jIXhJVL4AYJTZJOlG5Q+0HGOMv9aa5gAAxqJ64eu9siGGt4cQbpUt+z5mvh1kzhcAjCpfkXSzpO3nuiEAACOlZviKMW6QtCGEMFs29PBDkq6sBLFbvBo2WlH5AoBR5VCM8W9a3QgAwNg2kNUOD0n6dOVHIYS1sqEbozp8MecLAEaVWxp9zEr/fxhjXJNs65PNkd4YY1w/1McGAIxOA1rtMBVjvEM2BHFUo/IFAKPKByWtl7StUQ8YY7wjhHBjYfN1McZNpXcAAIx5gw5fYwXhCwBGle0xxk834Xm6QgjdMcaGhTwAwOhB+KqBBTcAYFTpb/SwwxrmSjoYQrglxlisiimEsE7SOklatmxZg58aANDuCF81MOcLAEaVWwZ7h8qcrrmFzdvqDSusLFalEEJ/CGFtZah+8foNktTT0zNmVhAGABjCVw0MOwSA0SPGeGdxWwjhree4z6DmN1eqWptjjFsH2TwAwBgxrtUNaFeELwAYXUIIs0IIbw0h3BpCOCNbuXc4j7daUk+lQiZJt1W2r5UGH94AAKMfla8amPMFAJ0vhDBLdqzK6yX1yJaBv1DS3MqhVIasMvxwTnK5X9LWyg/BCwBQhfBVA5UvABgV+iVtkXRTjPGrkhRC2Dzc4AUAwFAw7LAGFtwAgFHhvZL6JN0eQvirEMLrJLHQBQCgJQhfNVD5AoDOF2PcEGN8vaRuSfdK+pCkKyvzvl7X2tYBAMYawlcNhC8AGD1ijIdijJ+OMb4+xjhO0u0a5oIbAAAMFnO+amDBDQAYvSorEbIoBgCgqah81cCcLwAAAACNRPiqgWGHAAAAABqJ8FUD4QsAAABAIxG+amDOFwAAAIBGInzVQOULAAAAQCMRvmpgwQ0AAAAAjUT4qoHKFwAAAIBGInzVQPgCAAAA0EiErxpYcAMAAABAI7UkfIUQ1oYQVocQ1pVc1xVCWFW5zc3J9r4QwsYQwgeb0UbmfAEAAABopKaHrxDCWkmKMW6qXF5duMn1knpijHdUrveAdl2McU2McX0z2smwQwAAAACN1IrK11WStlXOb5O0Kr0yxrghxrihcrE7uW1XCKG7OU0kfAEAAABorFaEr67C5XllN6oErYNeIZM0V9LBEMItNW6/LoSwOYSwube3d9iNZM4X0Hn++I+ld7+71a0AAAAo14rw1S8LUueyNsZ4o1+oVMT6JfX70MVU5fqeGGPPggULht1IKl9A5/n616W77mp1KwAAAMpNaMFz3qOs+tUtaWPxBiGEtT63K4SwSlKPpM0xxq3NaiQLbgCdZ/9+6fjxVrcCAACgXNMrX5WFNLorC210JQtvbKycrpZ0cwhhSwhhi6xKdlvlurXJY4woKl9A5zlwQDp2rNWtAAAAKNeKypeSFQs3JdvWVE43Sbqo5G5bKz8jHrwk5nwBnSZGC198YQIAANoVB1muIQT7oSMHdIbjx6XnnpOef95+AAAA2g3hq44JEwhfQKc4cCA7z7wvAADQjghfdYwfT/gCOsX+/dl55n0BAIB2RPiqg/AFdI608kX4AgAA7YjwVcf48Sy4AXQKhh0CAIB2R/iqgzlfQOdg2CEAAGh3hK86GHYIdA4qXwAAoN0RvuogfAGdo6zyFaN0//2taQ8AAEAR4asO5nwBnePAATs2n5RVvr7xDemKK6SHHmpZswAAAH6E8FUHlS+gcxw4IC1ZYue98rVnj53u3t2aNgEAAKQIX3Ww4AbQOfbvl5Yts/Ne+Tp82E77+1vSJIxiIYSuEMKqEMLaEMLNyfa1IYTVIYR1rWwfAKA9Eb7qoPIFdI4DB6QLLrDzXvk6csROCV8YAddL6okx3iFJIYR1IYS1khRj3FTZtrqF7QMAtCHCVx2EL6Bz7N8vLV1q876Kla++vta1C6NTjHFDjHFD5WK3pG2SrqqcqnK6qhVtAwC0L8JXHSy4AXSGkyet2jV/vjR9elb5YtghRloIoVvSwUq1q6tw9byS268LIWwOIWzu7e1tRhMBAG1kQqsb0M6Y8wV0Bj/G1/z50rRpzPlCY1SGEc4tbN7mwwor1sYYb6yc7y+5fU6lWrZBknp6emKDmgoA6BCErzoYdgh0Bj/G17x55ZUvhh1iKHw+Vy0hhLUxxvWV86sk3aOs+tUtaeOINhAA0HEYdlgH4QvoDFS+0GyVxTRuDiFsCSFskTS3Eta6K9d1FSpkAABQ+aqHOV9A67z//bZ64Qc+kN8eo3TnndK112YHVT5X5YvwhUarBKuLSravr5wleAEAqlD5qoPKF9A6n/2s9Cd/Ip04kd++caO0Zo30ne9k27zyNW9evvLlS80z7BAAALQDwlcdLLgBjKwf/EC68cbq37MYrZp16JCFsNQDD9jprl3ZtjR8UfkCAADtivBVB5UvYGR9+tPShg3SM8/ktx89Kp06ld0m9cMf2um+fdm2/fulGTOkyZNrh6/IunIAAKDFCF91EL6AkXXXXXbqwwOdz+G65BK7zWOPZdc9/LCdpuHrwAFbbEPKhh2ePWuPO2WK9Pzz2VBEAACAViF81cGCG8DIOXRIuu8+O18rfP3O79jv4Wc+Y5djrF35mlc5nK1Xvo4etcsXXGCnDD0EAACtRviqgzlfwMj59rezoYC1wtdll0lvfKP0L/9it92zJxtKeK7Kl99u+XI7JXwBAIBWI3zVwbBDYOT4kEOpdviaP9/C1+7d0qOPZlWvKVPqV76eey4LW8uW2SnhCwAAtBrH+aqD8AWMnG9+U1q40EKUDxF0afi69lo7f+ed2TDgV7wiW+3QK2LnnWeXp02z02eftVMPXyw3DwAAWo3KVx3M+QJGxvHj0j33SD/903a5rPI1frw0e7bU3W1DB++80xbbmDvXhiN65Wv/fqt0+dyu6dPt1FdQZNghAABoF4SvOqh8ASPj7rttBcI3vtEul4WvefOkceOkEKz69bWvSQ8+KL3oRdKiRbZgx8mT0s6ddh+vcHnla+9eO2XBDQAA0C4IX3Ww4AYwMu66y0LVmjV2Wha+fAENycJXf7/03e9a+Fq40Lb39kpPP23na1W+GHYIAADaBeGrDipfwMi46y7piiukri47OHJZ+FqwILvs875ilC69NAtf+/ZllS8PX8XK17x5FsiofAEAgFYjfNVB+AIa79Qpq2Bdc41dnjnz3JWvRYtsnpeUr3x5+Jo8OQtrXvny8DVzpoU8whcAAGi1lqx2GEJYK6lfUneMccNArj/XfUYCC24Ajbd1q3TihPTjP26XBxK+JKt+PfSQha+TJ22bh6/zz7fhi1K+8jV1qjRxooUvhh0CAIBWa3rlqxKiFGPcVLm8+lzXn+s+I4U5X0Dj+fG9alW+zp7NHzTZ/e7vSp/6lAUtr3J5+PIhh1J+ztesWXZ+zhwqXwAAoPVaUfm6StKtlfPbJK2StOkc1887x31GxIQJ9g38RReN9DMBo9u4cdJHPiKtXWvh65JLsqGDxfB16JB96VEMX+efL914Y3afyZOz8PWa12S388pXX5+0cqWd7+qyAzVL0i/+og17RPPNnWuHGAAAYKxqRfjqKlyeN4Drz3UfhRDWSVonSct8ebNh+qVfsm/LY2zIwwFj1ne+I/3O70hveIP0rW9JN9yQXTdzprRjR3Y5PcByLSFYeHvmGQtVZZUvKat8dXXZkMV9+6R/+RfpZS+TLr54uHuFwfL3AwCAsaoV4atf0txBXn+u+6gyD2yDJPX09DQkLl11lfSP/9iIRwLGtk2bbFn597/fKls+30uy8HX0aHZ5IOFLsvD1wANWJTtX+PJhh9/6ll3+sz+TXvnKIe8OAADAkLRitcN7lFWyuiVtHMD157oPgDZ27bU2NPAzn7HLPt9Lqh52OJjw9YMf2Pk0fE2dmp1PK1+HDklf/7pd39MzlL0AAAAYnqaHrxjjHZK6K4tmdCWLaGysdX2t+wDoDCFIf/zHdn758uzAx9LwwpcviJOGr3HjsgCWhq8YpS9+UXrFK6RJk4a8KwAAAEPWkqXmY4zrK2c3JdvWnOP6qm0AOsc110jveY904YX57TNnSs89Z4d18EVupIGFL1ec5jltmi1nnw47lKTt26Vf/uWh7wMAAMBwtCR8ARibPv3p6m0zZtjpkSMWkvbvt5UM07lbZTx8zZghzZ6dv276dFuufuZMu9zVlV2XrowIAADQTK2Y8wUAP+IByYce+gGW/aDJtfixvi64oPq2vtx8OuxQsgMuv+xlw24yAADAkBC+ALRUrfB1Ll75Sud7Oa+aFYcdXn11fkEOAACAZiJ8AWipkQhftSpfDDkEAACtRPgC0FLNqHwtXy794R9K733vsJoKAAAwLCy4AaClhhq+li6V3vc+6W1vq77OK1/+2CFIf/AHw28rAADAcBC+ALRUGr5On5b6+qR58859v3HjpE9+svy6YuULAACgHTDsEEBLefg6elTas8cOhnz++cN7zOKcLwAAgHZA+ALQUmnl66mn7Pzy5cN7TCpfAACgHRG+ALTUlCnS+PEWvnbssG0rVgzvMQlfAACgHTHnC0BLhSDNmJEPX8uWDe8x3/IWezwffgg0WgihS1J35eeqGONNle19kjZL2hhjXN+6FgIA2hHhC0DLzZxpYenYMWnJEquGDUdPj/0AI+h6SYoxbgghXBVCWBdj3CDpuhjjpha3DQDQpghfAFrOw1d///CHHALNUAlarlvSxsr5rhBCd4xx2/+fvTuPk6wq7z/+PbP3rD0zDMMO9rALCEODOyrMYFxiEm3QuCRxYYgaoyYKmp8makwU1ESTaJzRJC4YBUaDOzoDihIFZ0EkiooMi4LAwEwPzL70+f3x3GPdvn2r6lbVrXtvdX/er1e/qrrWU1v3/dZzznNLGBYAoOJY8wWgdCF83X034Qu9xTk3IGlLrNq1QNIW59zKOpdf4Zxb75xbv3nz5sLGCQCoBipfAEo3Z460bZt0773SBReUPRrAOOeGZGEqblNiWuGQ9/7i8EuoiDnnhp1zQ9771fErR+evkqTBwUHfnZEDAKqK8AWgdHPmSBs3Svv2UflCdSSDU1IUri6Pji+VNChpvfd+YxHjAwD0HqYdAijdnDnS1q12nPCFXuCcWybpMufcBufcBlmF7KrovCGpeXgDAEw8VL4AlC7saFkifKE3RFMPl6SctTH6IXgBAMag8gWgdPHw1ek+vgAAAKqK8AWgdCF8LV4s9fWVOxYAAIBuIXwBKF0IX0w5BAAA4xnhC0DpZs+2Q8IXAAAYzwhfAEpH5QsAAEwEhC8ApSN8AQCAiYDwBaB0hx1mhyefXO44AAAAuon9fAEo3WmnST/9KeELAACMb1S+AFQCwQsAAIx3hC8AAAAAKADhCwAAAAAKQPgCAAAAgAKUEr6cc0POuWXOuRUp5/U755ZGl7ksdvpW59wa59wlxY4WAAAAADpXePhyzg1Jkvd+bfT7ssRFLpQ06L1fHZ0fAtoF3vvl3vvLCxssAAAAAOSkjMrXWZI2Rcc3SVoaP9N7v8p7vyr6dSB22X7n3EAxQwQAAACAfJURvvoTvy9Mu1AUtLaECpmkBZK2OOdW1rn8Cufceufc+s2bN+c2WAAAAADIQ1d2shxNLVyQOHlTFKSGU85LM+S9vzj8Eqphzrlh59xQmJaYOH+VJA0ODvoOhg8AAAAAuetK+EoGo4R1qlW/BiStSV4gCleXR8eXShqUtN57vzHnoQIAAABAIQqfdhgFs4Go0UZ/rPHGmuhwmaTLnHMbnHMbZFWyq6LzhmK3AQAAAAA9oyuVr2ZiHQvXxk5bHh2ulbQk5Wobox+CFwAAAICew06WAQAAAKAAhC8AAAAAKADhCwAAAAAKQPgCAAAAgAIQvgAAAACgAIQvAAAAACgA4QsAAAAACkD4AgAAAIACEL4AAAAAoACELwAAAAAoAOELAAAAAArgvPdljyF3zrnNku7p8GYOkvRwDsMpCuPtrl4aby+NVWK83Tbexnu0935RUYPppgn4v6pXxtor45QYa7cw1u6YSGOt+79qXIavPDjn1nvvB8seR1aMt7t6aby9NFaJ8XYb4x3feun56pWx9so4JcbaLYy1OxirYdohAAAAABSA8AUAAAAABSB81beq7AG0iPF2Vy+Nt5fGKjHebmO841svPV+9MtZeGafEWLuFsXYHYxVrvgAAAACgEFS+AAAAAKAAhK8E59yQc26Zc25F2WOpxznX75xbGo31stjpvTD2nhhv7Pkdip1WyfGmjauKY43GtCbltEqOPTneqn/u0p7f2HmVGm+d90LPfOaqoJeem0bvzSqp9xmvquj1X9YLYw2qPlbn3Fbn3Brn3CVlj6WZtL+ZVRSN807n3Ibop+rvga7/bSV8xYQ3sPd+bfT7snJHVNeFkga996slyTm3ohfGHo1pIDpe9fG+PXp+FzjnBqo63mgcm6JxbQp/jKXqjTW8X4O0cVZp7MnxquKfu5TxSqrm567OWHviM1cFvfbc1HtvVtCYz3jJ46nLObdU0tLoPbDUOTdQ9piaif8tqrALvPfLvfeXlz2QDEb9zSx7MA0s8N4v8d6fKekiSSvLHlA9adtU3bgfwtdoZ0naFB3fJKkrT3qnvPervPdhIeCAbKyVHnv0h2FT7KTKjjf6h7vOOTcQPddVfn7XS7o6+gMx4L3fqOqONSltnJUdO5+77umxz1wV8Nx0QZ3PeCV57zd67y93zvXLNhYrO1Yp9W9RVfVXPMhIqvs3s5LCl0SRgSqPVenbVLkjfI3Wn/h9YRmDyCr6A7ElemP3J86u2tiTH7j+xPlVGu8S2Xi2OOdWRv/c+hOXqcR4vffDsm+RrpZ0ZnRyf+JilRhriv7E7wvrnFYpfO66omc+cxXRn/id5yZHic941Q1KurPsQWRQ9Y3uYIGiv0NlD6SJtL+ZleacW1H1KnidbarcEb5GG5Z98HrFkPf+4uj4sCo6dufcspR/YsOq6Hgjd0Yfwg2SVqii441K5Gu990skDUfTkYZVwbGmGNbYcaadVjV87rqjJz5zFTEsnptuin/GKy36jC+p8rqfOn+LKimqIg2r9v+0ypJ/M6tuedkDaKbONlXuCF+jrVPtG8UBSZVdIOycGwpzkqPyaJXHviW2nmegB8a7Lna8X7ahU9XxLo2Vxd8n2yCr6liT0sZZ6bHzueuaXvrMVQHPTZekfMYryTl3WWxN2rCqHcbT/hZVTrSOt5JjS5H2N7OyeqEyF0nbpsod4SsmKocORMm3v6rf1ETjuyx0jpEtZqzs2KO56Wtlb+L+6LQqj3e1bN73suj3VRUe76roH8YySRdWeazReAZjzQLGjLNKY0+Ot+qfu5Tnt7KfuzrvhV75zJWu156b5OtdVWmf8bLH1MBKWUOA8B6o7M5r0/4WVdRV0qiGNpWdIpf2N7PkITWzQNKWsgeRwZhtqm7cCTtZBgAAAIACUPkCAAAAgAIQvgAAAACgAIQvAAAAACgA4QsAAAAACkD4AirOOXe1c+5O59zW6OdO5xytpQEAAHoM3Q6BHhHt02WJ9/7SsscCAKiuaL9KWyVtip28tld2Hh3nnLtM0p3S6JbqzrkBSSu998uTl01rER79D10f248TUIopZQ8AQHucc1fLdrAqSRd57zeGgCZpmWx/KhdXfR9AAICu2Oi9P7PsQXTCOXeJpCslDXZ6W977VdH/zQs6HhjQAaYdAj3IObdU0qboH+tFkt4endUvaVl0+nJJV5czQgBA1TjnhpxzYUfSA865ldFU9g3R/5Vwuauj066OAku47iWxy2yIHR91O865ZdFpYdr8UOKyG6Kf/ugyS6PzljnnVsaGvDxZqYrGv1QNRGNdE/1sjb6YlKQtUcUMKA3hC+hB0T+jldE/lMsSZ18ZXWaTpE38owGACWlpLOTEw9VQ9AXdUkkLvPdLZNWgELIukbQlusyVspkUdUXBasztyL4IvED2ReDbo8uukDQc3fbK6PQrJb04us7F0elh6mTyvi6Tfvc/UJKWxR+jpBXR+auj6YgXR48lTEO8M3rcQGmYdgj0IOfcMlnoel902HPz+AEAXTVm2mH0ZVyYin6WpIFQ2YqO90env0+yEOOc+0ST+xlzO5Jmh/vx3m+KBanlsdteFY2pX7XZG0tjwWpAo9esXSwLTvNjp61NWfMVl5xmuFGEL5SMyhfQm5bLFhqvlv2D6o+d92Lpd//QBqIKGAAAkjQcO36p9/6C6Md574c1+v9JqpSq1KjbkbQ9cT9xC+K3E93nlqgqFl+jPBy/rCyIXaqxsz3qjfESWTiLT1scaDAuoBCEL6A3rZR0QdRyfomkwdiUkk3R9IsNYmExACDdGkWzJqL1Xxtip4cv8YZilx+W/b+RpAsz3E69+7wgdtshSF0t+7/2u/Ve0ReH/fHreu8vl001bDidPvp/+OKU7sBLJK1vdF2g25h2CPSIeOvc6J/S8tjZl0q/m454ZVQRAwAglfd+rXNueRSW+hWFIu/95VGjig2yatOW2OUvjb7026goxNS5nQVj7lC1joOxgHZedLhWthYs2QZ+U1qVTRbSGk23f7uk/tg+Ma+O/ocO0GoeZWM/X8A4Ek2z2ET4AgDkwTl3Z9RMo5v3MSTprGSlKqpwDUUVr07vY6mkwbR9gAFFInwBAAAgVbfDV2z64ZnR+q/k+UvzqFbldTtApwhfAAAAAFAAGm4AAAAAQAEIXwAAAABQAMIXAAAAABSA8AUAAAAABSB8AQAAAEABCF8AAAAAUADCFwAAAAAUgPAFAAAAAAUgfAEAAABAAQhfAAAAAFAAwhcAAAAAFIDwBQAAAAAFIHwBAAAAQAGmlD2AbjjooIP8McccU/YwAAA527Bhw8Pe+0VljyMP/K8CgPGp0f+qcRm+jjnmGK1fv77sYQAAcuacu6fsMeSF/1UAMD41+l/FtEMAAAAAKADhCwAAAAAKQPgCAAAAgAIQvgAAAACgAIQvAAAAACgA4QsAAAAACkD4AgAAAIACEL4AAMiJc26rc26Nc+6SsscCAKiecbmTZQAASnKB935t2YMAAFQTlS8AAPLT75wbKHsQAIBqInwBAJCfBZK2OOdWpp3pnFvhnFvvnFu/efPmgocGACgb4QsAgJx471d574clDTvnhuqcP+i9H1y0aFHxAwQAlIrwBQBADqKq1tKyxwEAqC7CFwAA+bhKkkLFy3u/utzhAACqhm6HAADkIJpuuDH6IXgBAMag8gUAAAAABSB8AQAAAEABCF8AAAAAUADCFwAAAAAUgPAFAAAAAAUgfAEAAABAAQhfAAAAAFAAwhcAAAAAFIDwBQAAAAAFIHwBAAAAQAEIXwAAAABQAMIXAAAAABSA8AUAAAAABSB8AQAAAEABCF8AAAAAUADCFwAAAAAUgPAFAAAAAAUgfAEAAABAAQhfAAAAAFAAwhcAAAAAFIDwBQAAAAAFIHwBAAAAQAEIXwAAAABQAMIXAAAAABSA8AUAAAAABSB8AQAAAEABCF8AAAAAUADCFwAAAAAUgPAFAAAAAAWoTPhyzg0559Y0OX+Zc25FkeMCAAAAgDxUJnx571fXO885NxRdZm30+7KixgUAQFZ8UQgAaKQy4auJsyRtio5vkrS0xLEAADAGXxQCAJrplfDVn/h9YfICzrkVzrn1zrn1mzdvLmZUAADU8EUhAKChXglfw5IWNLqA936V937Qez+4aNGiYkYFAEBNf+J3vigEAIzSK+FrnWr/1AYk1W3MAQBASYbFF4UAgAYqE76iufGDYc58dNoa6XfNOAaiy/SH+fQAAFQIXxQCABqaUvYAgihQzU+ctjx2/PLoKMELAFA53vvVzrlL+KIQAFBPZcIXAAC9ji8KAQCNEL4AAEAudu2SfvEL6Z577Gd4WNq9W9q7V5ozR5o/X1q8WDrpJOn446UZM8oeMQAUi/AFAADa8sAD0tq10vXXS+vXSz/7mXTgwOjLTJ1qPzt3jj592jTpiU+UzjtPuuAC6eSTixs3AJSF8AUAADL76U+lL3xB+spXpJ/8xE5bsEA6+2zpBS+QTjtNetzjpKOPttOnRFsa+/dL27ZJ990n3X67hbUbbpDe/W7pXe+STj9desMbpJe+lIoYgPGL8AUAABratMkC1xe+IN12mzRpknTOOdL73iedf74Fp0lN+idPmSItXGg/p50mvfjFdvpvfyutXi198pPSq18t/c3fSH/7t9JFF1nFDADGk8q0mgcAANVx//3Shz9sUwOXLJH+3/+T5s6V/vVf7bzvfEd629ukpUubB69GDj3UKl4//rF03XXSCSdIr3+9dMop0ne/m9ODKdHOndJvfmPVvnvukR59tOwRASgTlS8AACBJ2rxZ+uIXpSuvtCmB3ktnnCFdfrl04YU2lbBbnJPOPVd61rOkb3xDeuMb7fhFF0n//M/SrFndu+88/eY3NiXz29+WbrlFuvfesZdZvFg69VR7vM99rlUCnSt+rEAVPPigtHGjfXY2b5YmT5Zmz5YGBuyzcfjhZY8wX857X/YYcjc4OOjXr19f9jAAADlzzm3w3g+WPY48VOV/1ZYt0v/8jwWu66+3hhknnCD98R9LL3mJHS/Dzp22FuyDH5ROPFG66iqrhlWR9xYY//VfpW99y04bGLB1cKecIh18sG1M7tolPfyw9POf28bmrbfaZU891ULmK19plwPGM++lm26Srr5a+vKXbVpzIyeeKA0NSa96la0n7QWN/lcRvgAAPYPw1bmREZvi981v2s9NN1ngGhiwdVgvfnG1KjHXX29NOB59VPr856U/+IOyRzTaDTdIb32rtG6ddNhh0sUXW5XwhBOaP4e//a1tfH7yk9KGDdJBB9ltveENUl9fMeOfKEZGrOHLjh3WaXP2bGnmzLJHNbHs2yddcYX0kY/YFw/TpknLl1uFe3DQ/gYddJCFs8cek375S/tcfPWrNgXZOenlL7cmPd2swueB8AUAGBcIX63xXnroIen//k+6+WYLWj/8oVVfJOnMM6Xf+z3pD//QjlclcCU98ICFrnXrbAriG99Y9oikrVttHJ/9rHTEEdLf/730spe13yTkhz+U3vMe6dprbSP03/5Nes5z8h3zRHDggDWFufFG28C//Xbb99wjj9jnIe6gg6TjjpOe9CTpGc+Qnv1sOm12g/fSl75kzXR++Uur9L7hDfZFz9y52W7jvvusCr5ypU1LvPxy+6Kjk/Wm3UT4AgCMC4Qvs3evBaldu2wnxrt32/GHHrKg8tvfSnffbdPbhodr1zvxRGugce65tqG5eHEuD6UQO3fat97/8z/Sm98sfehD5YXF66+3sWzeLL397faTV6XqO9+RXvc6e+2GhqSPf9w6RCLdgQM2hfP6660K+YMfWIVLsnB18sn2vj/kENv1wcyZ9vnZts0aoPzsZ7bbg927LQgMDUl//dfsdy4vP/+5Tam98UZ7Tt/3Pun3f7/9z+4999jtrVljX8h87nPVXA/a6H8VDTcAAOgxjz1m39SnmTnTOggeeaSt2zrhBOmkk6SzzpLmzy92nHmaOdPWiPzVX1n1a88eW2NV5Dff3kuXXWadH084Qfr6160hSZ6e9Syr2Hzwg7bm7Qc/sKlaz3pWvveTl+FhW7Nz110WaHbutNemr882iufPt8rg4YdLixZ1/nqNjFgl67rr7Oe73619wXDyybZO8WlPk57+9OxT0/buteD2+c/b7hT+8z+lF77QXoNeWWNUNfv2WXXqPe+x98EnPmFrGidP7ux2jz7a1lV+5CMWks85R/ra1+xvXq+g8gUA6BlUvsz+/dL3vmdTpPr67HDGDNu4He8NG7yXLr1U+sAHbNrRxz5WTAA7cED68z+39VkveYltTHb7ub7lFgvQv/yl9M53Sn/3d+VPs3rgAVuD873v2U9aN8d6pk61IHbUUbYRfdRRteOHHCLNmWMb6t5bJWr7duuAd++9VkHZsMGek9Cu/5hjpPPOs59zz82nkvvwwxbq/+mf7DV/5zulSy7pPDRMJL/+tU0p/OEPbf3jv/xLd6rsX/+63c9RR9l78aCD8r+PdjHtEAAwLhC+INnG+f/7fzaF6fWvt43lbk5B3LdP+rM/k/77v6V3vMO+zS9qyuOOHfYYP/1pm651xRXZ18nkZfdu64Z5xRU2vW9kxDamzznHKqpLltg6tTCtb+pUu86OHbbW6r77LESFIBV+7rvPbiuL6dOlJzzB1iYODkrPfKbdZ7f85jfSm95ku14491yrih18cPfub7z45jelV7zCqp+f+IR9UdFNN9xg61ZPOcXem3PmdPf+smLaIQAAGDeck/7hHywUffCDtibq3e/uzn3t2WMbkNdcI73//VZ1K9KsWdJ//ZeFnDe9ydbsXXNNMbsA2LJF+vd/t3D74IMWsv7mb6za8PjHNw6g8+bZ4cCAjT3N/v0WwO69125/xw6rdk2aZJXcmTNtuuJRR1knySkFbrUecYS0erVNQXz9621n4tdeW93dHZRt/36rzP7jP1pDjdWrpeOP7/79PuMZNh35j/7IqsRf+Ur51eFmCF8AAKDnOGdrSrZssUrUggX5d0HcudPW/nzrWzZ16g1vyPf2s3LOAsApp1hDiLPPtkYDz39+d+5v9257vP/4j7aO6/d+T3rLW6wClGfFb8oUm3JY5bbhr3qVBa/nPtc29L/5TXv+UXP//bY7iBtukF7zGnvvFLmrhOc/X/rwh6W/+Av7Uuad7yzuvttR8WwIAACQzjlrPf3CF1pV6DOfye+2H3vMNri//W3pP/6jvOAV94xn2LqnY4+1KYjvfnf2aXtZjIzY1MITTrAK31OfWtsn3HnnVXdXBN12+unWra+/356HDRvKHlF1XHedNZ1Zt84+f5/4RDn7qHvd66wD6bveZbvVqDLCFwAA6FlTptharPPOsyrFV77S+W1u3Wo7f73xRqswvepVnd9mXo46ysb1J39iG5p/8AejdyfQrhtvtIrOK15h0zivu84aGjzhCZ3f9ngwMGBNHRYutFC+aVPZIyrXgQMW/pcvt+dk3Tp775TFOemjH7Xpon/yJ7brjaoifAEAgJ42fbrt/2vpUuuu9u1vt39bDz1kbd1vucWaLfzxH+c3zrz09Umf+pStxbr2WmtAcdNN7d3WvffamranP906GX7mM7bfq3PPzXXI48Lhh9vzvX+/TcXMI/T2ol//2t4f73qXVZvWravGftHmzrX1kb/8pa3PrCrCFwAA6Hlz5tj0uBNOsGrQ9de3fhv33Wdd9H7xC6ug/cEf5D7M3Dhna1y+8x1rPPLUp0pve1utDXszjzxS21/Zl78s/e3f2uN+xSuq37CgTCeeaM/XXXdJr361dd6cSL78ZZuGuWGDBfXPfKZaOzk+91xbf3bZZdWtTvLxAgAA48LChdLatbYm6vnPt2CS1c9/Lj3lKfat/rXXSs9+dvfGmaenPU267TZrhX/ZZdaR8IMftP1VJXlvG81vfKM1uXjf+6xL3C9+YVPIqrQRXWVPe5pVVr70Jenf/q3s0RTjwQct1PzhH9r+1W65pdxpho184AO2u4O3va3skaQjfAEAgHFj0SJbr/S4x9nUsE99qvl1vvtdqxzt3m0d257xjG6PMl9z51pTkB/9yNZovfWt0qGHSk96koWy175WetGL7DkZHJQ+/nGr6t12m62XO+qosh9B7/mrv7KmJ3/919L//V/Zo+meffvs/XLSSTYN9+/+TvrBD6Tjjit7ZPUddpj05jdbC/pbbil7NGMRvgAAwLhy8MHS979vOwF+5SstfGzbNvZyO3faTpPPO89C2w9+YOvGetVZZ1nl79ZbLYBNm2bTL1evln76U2uosXKlre363OdsX11oj3MWeOfOlVasyLfrZBXs3m1fXJx0kn1+TjvN3lfvepetsay6v/oraf78aradZz9fAABg3FmwwNaAXXqp9M//bAHkT/9UevKTbU3Tj34kffrT0m9/a93R/u3fbN3YeHDaafaD7lq0SPqnf7L31cc/bu3Oe9nIiO1a4AtfsJ1LP/KIvY+++lXpec/rrV0N9PdbAHvnO60yWaWdYzs/DlcKDg4O+vXr15c9DABAzpxzG7z3g2WPIw/8ryrOhg3Se98rfe1r1qlOsgD27GdLb3+7dfoD2uG9dP75tm+pO+6QFi8ue0Stuf9+ac0a25H4mjW2VnDKFFvb9drXWufPXgpdcY88YlNqL7zQuiAWqdH/KipfAABgXDvzTGtF/+ij0p132jf8xx4rzZtX9sjQ68L+pU4+Wfr7v69+A459+2x/Zd/8pu2S4bbb7PSDD7Y1ks9+toXJgw8ud5x5WLjQ9tG3cqX0j/9o6yCrgDVfAABgQpg7VzrjDAtjBC/k5fjjbd3XypXSr35V9mjG8t6qWn/yJ1aZW7bM9hG3aJF1yLzlFpt++9nP2n67xkPwCt74RgucRVe+GiF8AQAAAB3427+1BifveEfZI6k5cKDWNOP8823t1vOfby3yt2yxrqCXXGL77Rqv+3Y79libOvkf/1Gdpijj9KkGAAAAinHIIdbg4cora1P5yvS//2udO1/5Stt/22c+Y9Wtz3zG9u02kfbp9prX2A6Xv/vdskdiCF8AAABAh970JmnmTNvJdVn277d9cZ1zjq1x/MIXpPXrbYfIM2aUN64yvfCF1v0wyz7/ikD4AgAgJ865rc65Nc65S8oeC4BiLVwovfrVtuPq3/ym+PvfscOmFb7nPRa2fvIT6cUv7t1uhXmZMcOqfV/+srRnT9mjIXwBAJCnC7z3y733l5c9EADFe/ObbW3RRz5S7P1u2WKNNNaskVatsirPeNlvXR4uvNAqgd/+dtkjIXwBAJCnfufcQNmDAFCOxz1OuuAC63z46KPF3OeOHdJzniNt3Gg7E7/oomLut5ecd540f7501VVlj4TwBQBAnhZI2uKcW1n2QACU401vkh57zJpvdNvIiPTHf2zruq66yqbXYaypU+25+cpXrPV8mQhfAABk5Jwbcs6tSPwsC+d771d574clDTvnhlKuv8I5t945t37z5s1FDh1AQZ74RNvp8n/8R/fv633vsxbyH/mI9Ad/0P3762XPe55VI2+6qdxxTCn37gEA6B3e+9X1znPOrZC03nu/scH1V0laJUmDg4M+/xECKJtz1njjr/9a+ulPpcc/vjv3c+ONtn+xl75Uev3ru3Mf48l550lTpkjf/Kb09KeXNw4qXwAA5OMqyapjUuOgBmB8e8UrbEP/P/+zO7e/a5f0qldJRx0lffzjdDTMYt486SlPka69ttxxEL4AAMiB937Ye7/Re7/ae39p2eMBUJ5Fi6QXvMB2arx3b/63//d/L91xh/TJT9LVsBXPeY50yy22w+myEL4AAACAnL3yldLDD0tr1+Z7u3fdJX3oQ1ZdO++8fG97vDv/fDu84YbyxkD4AgAAAHJ2/vk21S3v9uaXXmpTGt/3vnxvdyI47TSrFN54Y3ljIHwBAAAAOZs2zdqbX3ONtGdPPrd5663S1VdLb3mLdPjh+dzmRDJlivTkJ0vf/355YyB8AQAAAF1w4YXStm3St7+dz+29973S3LnSm9+cz+1NRE97mnTbbdLwcDn3T/gCAAAAuuC882zq4Ze/3Plt3X679MUvSn/5l1J/f+e3N1E97WmS99IPf1jO/RO+AAAAgC6YNk36vd+TvvY1aWSks9v6l3+x23vjG/MZ20R19tk2/bCsdV+VCV/OuSHn3LJoJ5Vp5291zq1xzl1S9NgAAACAdjz/+dKDD0obNrR/G8PD1rb+pS+VDjoot6FNSLNmSaec0tnr0YlKhK/YDinXRr8vS7nYBd775d77ywsdHAAAANCm5zxHmjTJql/t+tSnpJ07pTe8IbdhTWhnnCH9+Mfl3HclwpeksyRtio5vkrQ05TL9zrmB4oYEAAAAdGbhQukpT5G+/vX2ru+97Uz5iU+00IDOnX66VSPL2NlyVcJXf+L3hSmXWSBpi3NuZfeHAwAAAOTj/POljRulLVtav+4tt0g//an0Z3+W+7AmrNNPt8Myql9VCV/DsnBVl/d+lfd+WNJwmKYY55xb4Zxb75xbv3nz5u6MEgAAAGjReedZBes732n9up/5jDXaePGL8x/XRPWEJ9jhRA5f61Srfg1IWhM/MwpWaVMRfycKZ4Pe+8FFixZ1Z5QAAABAi846S5o9W7ruutaut3+/9N//Lb3gBdL8+d0Z20Q0b540MGBVxaJVInx571dLGogabfTHGm+EEHZV9PtQ7PJdGov0rnfZTuwAAACATk2dKp1zTuvh6/vflzZvll7yku6MayI7/fQJHL4kyXt/ufd+bbybofd+eXQ47L3f6L1f7b2/tNtjuesu6Z3vtB3ZAQAAAJ067zzpl7+UfvOb7Nf54helvj7bVxjy9fjHS5s2SXv3Fnu/lQlfVeGctHKl9OQnS694RTmJGACQL+fcMc65Fzrn3uKce41z7vSyxwRgYjnvPDvMWv0aGZG+9CVrVT9rVvfGNVEdd5w9x5s2Nb9snghfKWbMkP7nf2wndi94QTltKAEAnXPOneGcu0rSpZKWSLpFkpP0Eufclc6515Q6QAATxqmn2rZl1vB18822DfrCF3Z3XBPVccfZ4R13FHu/U4q9u96xeLH0la9IT32q9Ed/JH33uxbKAAA9ZdB7f2HitN9t+kTh7HTv/Y+LHRaAiWbSJOncc63jofc226qRb3zDrvPc5xYzvommrPBF5auB00+XrrjCvnl4zWvsgwIA6B3e+08kT3POHRM7/xaCF4CiPOMZtubr7rubX/Zb35Ke9CS6HHbLwoX23BK+KuaP/kj6h3+QPvc56f3vL3s0AIBWOOfOjfYB+S3n3OnOuS2S1jrnDjjnXl32+ABMLOecY4c33ND4cg8/LK1fLz372d0f00R23HGEr0p6+9ull75U+pu/ka65puzRAABasFK23usDkjZIusB7f6ykhZIub3RFAMjbySdbxeV732t8uTVrbMYV4au7jjvOOlAWifCVgXPSJz8pnX229PKXS7feWvaIAAAZOe/9ddH+I+/y3l8n2S5MJG0tdWQAJpxJk6SnP7155etb35IWLJAGB4sZ10R1/PHSr38t7dpV3H0SvjLq67OqV3+/9Pu/Lz34YNkjAgBkEF+tmwxbhC8AhXvGM6y9eb39fXlv4Wv5cmny5GLHNtGEpht33lncfRK+WnDoodYB8eGHre3nnj1ljwgA0MSSaL3XtyWdGR0Pvy8te3AAJp6w7qve1MOf/ER64AF2rFyEJUvs8K67irvPjlrNO+fmeu8fzWswvWDpUukzn5EuuEBasUL61KeatwoFAJTmzLIHAABxT3iCNHeuha+XvnTs+ddea4fnn1/suCaiI4+0w1//urj7bCt8OefOkHS1pGMkTXHOrZf0au/9hFgNNTQkvfvd0t/9nXTKKdJb31r2iAAAabz3t5Q9BgCImzxZetrT6le+vvUt6bTTpMMOK3ZcE9HixdLUqdK99xZ3n+1OO1wlabmkL0W/XyhpdS4j6hHvfKd04YXSpZdKX/1q2aMBAKRxzv3KOXdH9PNI4udjZY8PwMR0zjnS7bdLDz00+vTHHpNuvJEuh0WZNEk6/PBiK1/thq9+7/1dihYye+83SdqW26h6gHPSf/2XdOaZVjK+7bayRwQASPLeH+u9P07SLZJWeO8Xeu8XSrpY1m4eAAr3jGfY4fe/P/r066+X9u2TnvOc4sc0UR15ZG+Er+ucc1dKGoh2YHmVpDU5jqsnzJxpHRDnzJFe8AJp8+ayRwQAqONx3vsvhl+896slDZQ4HgAT2Jln2nZksuX8N78pzZ4tPfWp5YxrIuqJ8OW9/3NJV0m6TtL5klZ679+e58B6xeGHS1/+snWleeELpb17yx4RACDFXc659znnTo9+3i+pwP5WAFAzdar0lKeMXvflvYWv5culadPKG9tEc8QR0v332/NfhLZbzXvvv+i9f1v0c12eg+o1Z51lUxBvvFF67WuLe/EAANl47y+UtEXS5ZL+RtIj0WkAUIpzzrG28lu22O+3326NH5hyWKxDDrHiybaCFlC1FL6aLFz+924Nshe85CXWhOM//1P6538uezQAAElyzr3fOXe6JHnvP+C9P997f6H3/gPOubnOubeE89u47SHn3JqU05Y551Z0PnoA49n559sX9l/7mv2+erX1FHjuc8sd10SzeLEdPvBAMffXUvhqsnB5QTcG2Eve9S7pRS+y1vPf+EbZowEAeO/fJmm5c+7b0c6V/905d6Vz7luSLpP0Re/9j9u87VFdfp1zQ9Hpa6Pfl3U2egDj2dlnS0cdJX3+89LIiPTZz0rPfKYtaUFxDjnEDisZvmJYuJxi0iTp05+2nef98R9LP/tZ2SMCAISKl2y3KKskvc17/2zv/Wujzr15OUvSpuj4JklLc7xtAOOMc9IrX2k7VX7DG6Rf/Uq66KKyRzXxhPD14IPF3F+74YuFy3XMmmUNOPr6pN//fenhh8seEQBAkrz327z3t+QcuOL6E7/Tyh5AQ296k017+9jHrIfAi19c9ogmnqKnHU5p50re+wudc2+VLVwelrQumtoBWcvKa66x0vHQkPTtb9O1BgCqwDk313v/aMbLDmnslPpNYVphiuGUyydvc4WkFZJ01FFHZRkGgHGsv1/auNG+uH/FK2wWFYo1f751nyyq8tVW+JJsGoekD+Q4lnHlSU+S/uM/pJe/3ErJH/+4lZcBAMVzzp0h6WpJx0ia4pxbL+nV3vtb610nuaYrg3WqVb8GlLL/S+/9KtnURw0ODtIbF4AOO8y6ZaMckyZJixZVfNqhc269c25d4mdCdztM87KXSW9/u7RqlfSv/1r2aABgQlslabmkL0W/Xyip1XA1StRQYzDWaGO1pIHo9P4GFTIAQIUsXFhr+d9t7U47HAzHnXPzJL1Y0uPyGtR48t73WuONN79ZOuEE6dnPLntEADAh9Xvv73LOeUny3m9yznW0V5coXM1PnHZ5dJTgBQA9YuFC6ZFHirmvjmeWRguYV4muTqkmTZKuuEI65RRbRPnzn5c9IgCYkK5zzl0pq0yd65y7SinTAgEAE8+CBRWvfEXNNuJz1Y+VxIqmOmbPlr7yFeti8/u/L918s73IAIBieO//3Dn3Ilk7+PMlrfTeX1fysAAAFVBk5avdhhubEr/fwj+xxo4+Wvqf/5HOPVe64ALbp8PUqWWPCgAmjmj/lF9sekEAwIQSKl/ed79BXrvTDr33/ouxn+uccy/MdWTj0FOfKq1cKV1/vfTGN9oLDADoHufcr5xzd0Q/jyR+aBQFANDChdLevdKOHd2/r5YqX8658yRdLOk851x8N3BO0hmqdZFCHX/2Z9JPfyp98IPST34iffjD0uBgs2sBANrhvT9WkqI1XldG1a+wD68LyhwbAKAawnKgLVtsuVA3tVr5WifpbZKuk3RpdPxtki713h+X89jGrcsus/bzd9xh68D+9E+l++4re1QAMK49LgQvqdYWvsTxAAAqIoSvItZ9tRq+VnjvN8k6RA1JelH4cc69Je/BjVeTJkkXXWTh69JLpS98QTr+eOnv/17aubPs0QHAuHSXc+59zrnTo5/3S7qr7EEBAMoXr3x1W6vhK/yj2hQdj/8km3Cgiblzpfe/X7r9dum5z5X+9m+lE0+UPv951oMBQJ689xdK2iLpckl/I+mR6DQAwAQ3b54dPvpo9++r1fA1EFW4zpDtVDn8DIjpG20bGJCuvlq64QbpoIOkl75UespTrCU9ACAf3vsPeO/P995f6L3/QNnjAQBUw5w5dvjYY92/r1bDV1rF667Y6ejAOedI69ZJ//mf0t13S096kvTyl0u//nXZIwOA3uacW++cW5f4odshAKDQ8NVSt8P4YmVJcs4dE51+d35DmtgmT5Ze+UppaMgac3zwg9KXviRdcon01rdKs2aVPUJUyb//u4Xz97xHmtLuXvuACcB7/7u+ss65eZJeLJu5AQCY4Kpc+ZJkLeedc1skXS1pdbS/lGflO7SJbc4c6b3vlX7+c+kFL5De/W7phBOkK66QRkbKHh2q4MMfll73Oul975Ne8hJpz56yRwT0Bu/9Nu/9KklLyx4LAKB8M2ZYAaSKa76Cj0s603t/VvRt4hJJq/IbFoJjjrFuiDfeKB16qPSKV9h0xB/8oOyRoUwf/7j05jdLL3yhVUe/+EUL6UXsHBDoRc65tzrn3hL7+bhsH5UAgAnOOWuEV9nKl6Rt3vvfrfHy3g9L2pbLiJDqqU+1Bhyf/rTtE+ypT5X++I+le+4pe2Td8b3vSSefLD3xidI//IP0f/9HB8jg05+WXvta65D5+c9Lf/3Xtk5w7Vrp/POl4eGyRwhUUnLN8tXe+/PLHRIAoCrmzKl2+FobLVZ+S/Rt4jpJdzrnXuice2GeA0TNpEnSn/yJ9MtfWlv6a66x1vTveIe0fXvZo8vHyIhNo3vWs6R9++y0d7xDOvVUackSq/Z85zu18yaaq66SXvUq6bzzrNo1bZqd/spX2nnr1knPfKb04IOlDhM9xHtp9Wr7ouNlL7Oq+jj9osN7778Y+7mO/1cAgKDq4etOSVdFx310fJ1s+iEt57ts1ixbA/aLX9i0s3/4B9tJ86c+1dvrwTZvtmrO3/yNdOGF0saNVu277z5p5UqrhP37v0vnnistXmydIK+6qpj5uVXwla/YxvFTniJ9+cs2PznuRS+SvvY123n3058+fquiyM/NN9t75YILrGL6ta9ZVX3pUumTnxwfO32P1ihfJekTzrkrYz9XSbqs7PEBAKphzpxqr/laK2mhpGOjnyWSlkT7UPlgXoNDY0cdJX3uc9IPf2jHX/lK6eyzpe9/v+yRte7GG6UzzpC++11bz/Tf/13rPHPYYdKKFbZh+PDD1v3xBS+Qrr1WevGLbd9oz3629NGPjt+2/N/+tm0gn3GG9PWv1+96ef750po10kMPSU97mjVsAZLuucf2J/ikJ0l33il94hPSz35mX3R8/OPSgQPSRRdJhx8u/dVfWaDvYeskvU3SdZIujY6/TdKl3vvjyhwYAKA6qr7m69uSfiXrdhh+Vuc1KLQmNOD43Odsutk559iG+l09sOe1kRHp/e+3qXJ9fdJNN0kXX2wLH9PMni390R9Zle/BBy1ovvGN9lj/4i8shC5dapXBW24ZH9OnbrhB+sM/lE46yQLn3LmNL/+Up9h19u6198IttxQyTPSARx+V3v5265x6zTXSO99pweo1r7EuT7Nn2+fv1ltt3eWzny39679aZf33fk/66lctmPWYFd77TZLWSBqS9KLw45x7S6kjAwBURtWnHW7z3n/Se39d/CfXkaElkybZN9m/+IUFj298w9aDXXqptK2irVAeflh6/vNtY/BFL5I2bJBOPz379SdPturOBz5g6+Buv932jdbXZ8/B0qXS0UdbKFuzxsJIr7npJnuOjjnGql8LFmS73hOeYMG0r8+CbS9WQ5Gf/futonXssfZlx4UX2t+K97zHAleSczYd8QtfkO691z5Pt91mFeclS+w2Nm8u/nG0KXwNlWy4cVd0GgAAlZ92uDJquPG++E+uI0NbZs60Zhy//KWFsQ98wDa4PvYx2wCriv/9Xwta111nY/vCF5pXdJo58UTbGfX//q/0wAPWAXDpUjs8/3xp0SKbpvjf/y1t3ZrLw+iqjRut2rB4sXUyPPjg1q5//PG1XRQ8+9lWNcPE4r30zW9aGH/ta23d5Pr10mc+Ix15ZLbbOPRQ+5ty993WmGNgwL4wOfJIawB0882VrzAPRBWuM2Q7VQ4/A2KNMgAgUvXK1yWy/XqtTfygIg4/XPqv/7INrVNOkV7/eum006wiVuaG0siIdPnl0jOeYQ0jbrrJNgrrTTNs18EH2xq4a66RHnnEmlVceKFNx3vZy+z8886ztS5VCqXBT39qgXHePAuohx3W3u0ceaRNHzvxRKtaXHVV8+tgfLjtNgvdz32udQf9n/+xTqFnntne7U2dahXq66+39+drXmO3+aQnSWedZX9vdu3K9zHkJK3idVfsdAAA1Ncn7d7d/ftpN3zd5b3/RJ7TDp1zQ865Zc65Fe2cj3RLl9rG0jXXWMh43vNsg+y224ofyyOPWAC49FLr0rhhgzWQ6La+Pun3f9+C1v33W4OSt75V+u1vrZHH2Wdbi/aq+OUvLRhOm2bB6+ijO7u9gw+2je4nPlF6yUusix3GrwcesGYZp59uX758+MO2n7w//MP8vuQ4+WTp3/7NPk8f/aiFrle9SjriCPtsbarQZL5Ee/kvStogaUPsdwAANGOGtGdP94sU7Yav4TynHTrnhiTJe782+n1ZK+ejMeekP/gD2wD78Idtg+z00y14PPBAMWP4wQ/sPtessY21K6+0qk7RJk2yb+r/8R/t2/urrrLn4IlPlN7whvLXx919twWvkRELXscem8/tzpsnfetbFrwvukj60IfyuV1Ux86d0nvfa++ZT3/aGtH86ld2GPYHl7c5c6TXvc7+tnznO7YbiH/+ZxvD855nlfaq7P4iajm/RVGDKOfcI865Z5U9LgBANYRd+OzZ0937aXvNl6xVb17TDs9SbeHzJklLWzwfGUybNnqD7L/+SzruOAsi3ZouNDIiffCDNs1w2jQLYa97Xf7TDNvhnHWFvP12m5b50Y9aR8Grry5nauZ999nG644dFlJPOinf25850/YPduGF0lveYjuvrvhanUo7cMDWO733vfa6Pec5to+6q66yDoJFhY6REemzn7UOhu98pwXsn/1M+qd/yt6gpVPOWWOXq6+2NvbvfKetWXze8yyIfeADVvku2cclnem9P8t7PyjbRcqqkscEAKiIEL66PfWwrfCVnG4YTTnspI7Rn/h9YYvnyzm3wjm33jm3fnMPteEqw4IFtmH2s59Jy5dL/+//2Ybb5z6X7wbjli1WcXvrW+1w48b215t007x51k775pulQw6xcPK85xXbqv/BB63i9fDDVqF6whO6cz/TplnDkde8xnbO/YY3VKcy0QvuuktatUoaGrIGLk96kjWjGB62KXgf+IA1dTn+eGsg89SnWrfNT37SKs55/0G/4QabNvsnf2Lv3e99T/riF/OrmLbj8MOtO+K991qF+8gjrRHOEUfYOswi5tPXsc17/7tPtfd+WFJFe8ECAIrW12eHlQxfkuScm+uce6Fz7krn3AFJF3cwjmFJjb6jbXa+vPervPeD3vvBRYsWdTCUieO442yHxd/9rm1IvvzltjF5442d3/ZNN9l6rm99y4LN1VeXM82wFWedJf3oRzZt6nvfkx7/eGupvW9fd+/3kUekZctsB9Hf+IaNo5smT7YA8Za3WLXvT/+0+4+xV23bZuslX/c6+7wMDNh+sG6+2fY394Uv2A6tN260fWM99pitZfyP/7A1UJMmWWfBiy6y13X2bOnUUy0s/dM/2XrMLVtaH9cdd9j9P/OZFtyvuMLG9PSn5/0MtG/q1FqTm5/8RPqzP7OqWPhmsQRro+nyb3HOvdU5t07SndH/sReWNioAQCUUVfma0sqFnXNzJV0Y/QxKWi9r2bvAe9/JN4jrVKtuDch2htnK+ejAM55hDSeuuMKmTT396fbN/mWX2cZmK7y3jcq3vc2+8f7BD6TBwe6MuxumTJHe9Cbr6vbGN1pL7SuukFautCpG3oaHravhHXdIX/+67besCM5Z18n5863y+eijVqUoccO4EvbvtwD+7W/b1M+bb7bphbNmSc96llUKzz/fKsVpU2dnzLAmN0tjE6NHRqxidsst0o9/bD/XXWdTBYOjjrI1kWecYYenn26NVpL3sWWL7Zvrox+1+/qHf5De/Obat3VVdeqp0r//e+nTXO+UFCY/eklXRYdLokMAwARWVPhyvoX/hs65EVmnqEu999dHp62P5s93NhDnLpG0UdJS7/3l0WlrvPfL651fz+DgoF+/fn2nQ5qQduywZgyXXWYbon/5l7Zx3t/f/Lpbtti0oq98xb6V/8//zHa9KvvqV23a2L332lS9yy7Lbx3N9u22Ib9+vVVXnvvcfG63VR/7mK15O/dcG8ecOeWMowzeS3feaUHr29+2StSjj1roOessm5Z7/vlWEc67acVDD1m1LB7KfvGL2jTQ/v5aEDvjDNup8T/8g1XjXvMam9p3yCH5jqkXOOc2tPM/xzn3ONkMjf7Yyd57/9q8xtYq/lcBQHVcc41tv27c2Hk37kb/q1oNXyskDUk6U9KVklZLusx73+WJUq3hH1rn7r/fGjJ86lMWNt79buuOOHVq+uVvvtnWutx/vzXYeMMbqtFUIw/bt9vj/+d/tufiQx+yKZqdPL5duyxsff/7VnF60YvyG287rrjCpoWdeabtlLebjRp277agsW6d/axfb6F/wQJp4UI7TPtJnjd9env3v3WrhaxQ3Qpr+44+2oLW+edbEC2qWUXczp22G4gf/7gWyn7yk1pDnGc/2z5fp5xS/NiqooPwdYeky5TYt1enu0npBP+rAKA6rr3Wmmf94AfSk5/c2W3lFr5iNzhPNvXwAknLZK17V4ZqWNn4h5afH/9Y+qu/sjbSJ55oG37PfW4teHgvfeQjtqD+8MMtSJx9dqlD7ppbb62t9zn3XJtGdfzxrd/Onj22z6Vvfcumnr3sZbkPtS1f/rIF6GOPtWDS7o6d4/bvt5b+IWitW2fhIuzYevFiqzAtWGCV0+RPox1gz5xZP5glf0ZG7D28Zo2NYWTEKnznnlurbh17bDW/MDhwwPb9tmvX6OmME1UH4SuXWRp54n8VAFTHd79rSwyuv94OO5F7+Eq5gyFJF3jvX9zxjeWAf2j58l762tesQUPYAfCHPmTrVF75Stto/8M/tGmG8+eXPdruGhmxZhVve5ttDL/97XY861qpffusCcE111gHvFe/uqvDbdn119uOsBcvltaulR73uOzXHRmx3RjEg9Ytt9SqNv39tv7vrLPsZ3DQOuDVCzzeW9XxkUfSg1mj09MaiEyaZPtzC2Hr7LPrV3JRXR2Er4skrVBityje+7d3MJYhSReH6fHRaVtl66HXMEUeAHrHTTdZxesb37AKWCe6Hr6qhn9o3bFvn/Txj0vvepdN3TroIDv8wAesOUUVqwbd8sADVhH8/OetC97HP25VlEYOHLDpil/4gvQv/2JTM6voRz+yPzrTp1ul6PGPH3sZ7607YzxobdhQ20l1X59VaULQOussackSC0Dd5r1NY4wHsr17bd1Wr69BRMfTDi9XbZ+Rkjqfdhhfmxz9vsx7n2m/l/yvAoDquPVWW2f9pS/Z2q9ONPpf1VK3Q0xsU6daYHj5y23HsjfcYA0pnvjEskdWvEMOsf1l/dmfWRvy886z5+VDH5IOPnjs5UdGrEnCF75gTTuqGrwkqwjdcINVh845x+ZAH3PM6KC1bp01jJDsfXHaadJLXlILWiefbJ0jy+CctXSfPduqs0DkLu/9Jwq4n37n3ID3flPziwIAqqKSreYByaYWfuhDZY+iGs4/39Yw/eM/Wqj6+tft8NWvrlV5vLew9alPWdXwkkvKHHE2p5xizUCWL7cS/IEDdrpz0kknWWUsBK3TTqNFPXrCcLRvr9ymHdaxQNIW59xK7/2Y/V9GjatWSNJRfDsAAJVB+AJ6RF+f9Pd/L730pdKf/7l1hfz0p20q4uMfL731rdbO/ZJLpL/927JHm92SJbbD7Q99SDr0UAtaS5dOrFb0GFdWtnqFaE1Xsu/lpkbTCr33q6LrDjvnhrz3q1POXyXZtMNWxwQA6I4QvsJa9W4hfAE5Oekk65Tz6U9bc5IzzrBuOWvW2L7C3v/+3lsXd9hhVDkxPqSt7XLOvbDJdVY3Oj/l9lZIWu+939ji8AAAJSuq8lXA8ndg4nDO1oH9/OfSK15hwevVr7Z2/L0WvIDxxjk31zn3Qufclc65A7KdLndye8skDUYVMkm6Kjp9SGo9vAEAysO0Q6CHHXSQtd5/73ttyh7BCyiHc26ubL+UF0oalLWBf5ykBd77bZ3cdjT9cH7s92FJG6MfghcA9JBp02x7jfAF9LA8dlQMoCPDkjZIutR7f730ux0udxS8AADji3NW/WLaIQAA7ftzSVslXe2c+5hz7lxJNLoAAIxB+AIAoAPe+1Xe+/MlDUi6RdLbJJ0Zrftqsmt0AMBEUkT4YtohAGDci6YZfiL6CU0xLpZ0fZnjAgBUx2c/Ky1e3N37IHwBACacqBMhTTEAAL9z3nndvw+mHQIAAABAAQhfAAAAAFAAwhcAAAAAFIDwBQAAAAAFIHwBAAAAQAEIXwAAAABQAMIXAAAAABSA8AUAAAAABSB8AQAAAEABCF8AAAAAUADCFwAAAAAUgPAFAAAAAAUgfAEAAABAAQhfAAAAAFAAwhcAAAAAFIDwBQAAAAAFIHwBAAAAQAEIXwAAAABQAMIXAAAAABSA8AUAAAAABSB8AQAAAEABCF8AAAAAUADCFwAAAAAUgPAFAAAAAAUgfAEAAABAAaaUPQAAAHqNc65f0kD0c5b3/tLo9CFJw5IGvPerShsgAKCSqHwBANC6CyUNeu9XS5JzbkUUvOS9XxudtqzE8QEAKojwBQBAi7z3q2KVrQFJmySdFR0qOlxaxtgAANVF+AIAoE3OuQFJW6JqV3/i7IUpl1/hnFvvnFu/efPmIoYIAKgQ1nwBAJAimka4IHHypjCtMDLkvb84Oj6ccvlRomrZKkkaHBz0OQ0VANAjCF8AAKQI67nqcc4Nee8vj44vlbROterXgKQ1XR0gAKDnVGbaoXNuyDm3zDm3os75W51za5xzlxQ9NgAA4qJmGpc55zY45zZIWhCFtYHovP5EhQwAgGpUvuIdoqL58MtS/mldwD8yAEAVRP+PlqScfnl0lP9XAIAxqlL5ytIhqj9a2AwAAAAAPacq4as/8fuYDlGyRcxbnHMr026ADlIAAAAAqqywaYdNukYNp5w3StifinNuOFrkvDrlfDpIAQAAAKikwsJXk65RDTtERU041nvvN3ZndAAAAADQXZWYdlivQ5RzLoSwq6Lfh2KXBwAAAICeUYluh1J6hyjv/fLocFjSxuiH4AUAAACg51Si8gUAAAAA4x3hCwAAAAAKQPgCAAAAgAIQvgAAAACgAIQvAAAAACgA4QsAAAAACkD4AgAAAIACEL4AAAAAoACELwAAAAAoAOELAAAAAApA+AIAAACAAhC+AAAAAKAAhC8AAAAAKADhCwAAAAAKQPgCAAAAgAIQvgAAAACgAIQvAAAAACgA4QsAAAAACkD4AgAAAIACEL4AAAAAoACELwAAAAAoAOELAAAAAApA+AIAAACAAhC+AAAAAKAAhC8AAFrknOt3zi11zg055y6Lnb7VObfGOXdJmeMDAFQT4QsAgNZdKGnQe79akpxzK6LTL/DeL/feX17e0AAAVTWl7AEAANBrvPerYr8OSFoTHe93zg147zeVMCwAQMVR+QIAoE3OuQFJW7z3a6OTFkja4pxbWefyK5xz651z6zdv3lzYOAEA1UDlCwCAFM65IVmYitsUC1qSNOS9vzj8Eipizrlh59xQmJaYOH+VJA0ODvrujBwAUFWELwAAUiSDU1IUri6Pji+VNChpvfd+YxHjAwD0HqYdAgDQIufcMkmXOec2OOc2yCpkV0XnDUnNwxsAYOKh8gUAQIuiqYdLUs7aGP0QvAAAY1D5AgAAAIACEL4AAAAAoACELwAAAAAoAOELAAAAAApA+AIAAACAAhC+AAAAAKAAhC8AAAAAKADhCwAAAAAKQPgCAAAAgAIQvgAAAACgAIQvAAAAACgA4QsAAAAAClCZ8OWcG3LOrWly/jLn3IoixwUAAAAAeahM+PLer653nnNuKLrM2uj3ZUWNCwAAAADyUJnw1cRZkjZFxzdJWlriWAAAAACgZb0SvvoTvy9MXsA5t8I5t945t37z5s3FjAoAAAAAMppS1B1FUwcXJE7eFKYSNjGcct1RvPerJK2SpMHBQd/OGAEAAACgWwoLX43WdGWwTrXq14Ckuo05AAAAAKCKKjPtMGqiMRiaa0SnrZF+F9wGosv0Z6yWAQAAAEBlFFb5aiYKVPMTpy2PHb88OkrwAgAAANBzKlP5AgAAAIDxjPAFAAAAAAUgfAEAAABAAQhfAAAAAFAAwhcAAAAAFIDwBQAAAAAFIHwBAAAAQAEIXwAAAABQAMIXAAAAABSA8AUAAAAABSB8AQAAAEABCF8AAAAAUADCFwAAAAAUwHnvyx5D7pxzmyXd0+HNHCTp4RyGUyYeQzXwGMrX6+OXeAzB0d77RXkMpmwT8H9Vr4y1V8YpMdZuYazdMZHGWvd/1bgMX3lwzq333g+WPY5O8BiqgcdQvl4fv8RjQLpeek57Zay9Mk6JsXYLY+0OxmqYdggAAAAABSB8AQAAAEABCF/1rSp7ADngMVQDj6F8vT5+iceAdL30nPbKWHtlnBJj7RbG2h2MVaz5AgAAAIBCUPkCAAAAgAIQvhKcc0POuWXOuRVlj6Udzrl+59zS6HFcVvZ4OtXLjyH2OgyVPZZ29ernIRr3mpTTeuaxJB9DL362016H2Hk98Riqqpfez43eB1XSa5+x6PVf1gtjDao+VufcVufcGufcJWWPpZle2caIxnmnc25D9FP190DX/7YSvmLCG9h7vzb6fVm5I2rLhZIGvferJakX/jHXEz3/A2WPowNvj16HBc65nnsc0fO/Kfo8bHLOLS17TFmF93/Qi5/t5GNQD362Ux6DpHHx2S5Vr72f670PKqhnPmPR3+Ol0XtgaS/8j+mRz/0F3vvl3vvLyx5IBr2yjbHAe7/Ee3+mpIskrSx7QPUUtd1D+BrtLEmbouObJPXMxmbgvV/lvQ+LBAdUezw9JfpD0pNjl373T3udc24gek168bGsl3R19MdnwHu/sewBdYDPdkX0+me7Inr+/VxFvfQZ895v9N5f7pzrl20sVnasUk997vsrHmQk9dY2RviSKDJQ5bGqoO0ewtdo/YnfF5YxiDxEfzy2JN70vaTqH9BmlsjeP1uccyujf5A9xXs/LPuG6mpJZ5Y7mo71J37ns12eXv9sV0F/4veefT9XUY99xgYl3Vn2IDLolc/9AkX/t8seSBM9t43hnFtR9Sp4Uds9hK/RhmUfvPFgyHt/cdmDaIdzblmP/NNr5s7og7xBUmWnr9QTld/Xeu+XSBqu+rzyJobFZ7t04+izXbZhjZ/3cxX1zGcs+jwtqfLf51763EdVpGH1xv+8XtvGWF72AJoparuH8DXaOtW+URyQVPkFwmmcc0NhvnIvrdOJ2RItdhySNNCjj2Fd7Hi/bGOp1yyNldzfp97e2OOzXQ3j4bNdBePi/VxFvfIZc85dFluTNqxq/33uic+9c25FVceWoqe2MXqhMhcpZLuH8BUTlUMHouTb3yvf1MRFY78sdJVRtf8gp4rmsq+Vjb2/5OG0JXov9YeF8LF1BL1kVfTPaJmkC3vpMURjHow1Jui5z3byMfTiZzvldej5z3YV9Nr7Ofk+qKoe+4ytlDUECO+Byv597qHP/VXSqIY2lZ0i14PbGAskbSl7EBkUst3DTpYBAAAAoABUvgAAAACgAIQvAAAAACgA4QsAAAAACkD4AgAAAIACEL4AAAAAoACEL6DinHNXO+fudM5tjX7udM6xXx8AQCrnXL9zzkf/L8LPyrLH1Y6wT7PYfs3C6QPJ/4WJ/Z8lb6eX9uOFcWxK2QMA0Jj3/gLJ/nFIWuK9v7TkIQEAqm+j9/7MsgfRCefcJZKulDTY6W1571c5566WdEHHAwM6QOUL6FFRRWxD9LM0Om1F9M3fhuibzmVljxMAUA3OuaHY/4gB59zK6H/FhnhVKPb/5eoosITrXhK7zIbY8VG345xbFp0WZm4MJS4b/nf1R5cJ/8OWJSp0y733GxOP4bJmFaxorGuin62xatgW59xAO88dkBfCF9CDon88m6JvNS+S9PborH5Jy6LTl0u6upwRAgBKtjQWcuLhaij6H7FU0gLv/RJZNSiErEskbYkuc6Wkhl/iRcFqzO3I/hddIPtf9PbosiskDUe3vTI6/UpJL46uc3F0upxz/Sn3dZkkxQLZsvhjlLQiOn+19355dHtbvPerosvfGT1uoDRMOwR6kPd+o3NuOPpHdoGk4djZV0aX2eSc2+ScG/DebypjnACA0oyZdhhVfdZGv54laSBUtqLj/dHp75MsxDjnPtHkfsbcjqTZ4X6i/0X90XnLY7e9KhpTv2pfIC6NBasBSfH/XRfLgtP82Glro5AVHt9libElpxluFOELJaPyBfSgaDrh1ZK2SEr+swEAoJ7h2PFLvfcXRD/Oez8sm0HRUEpVatTtSNqeuJ+4BfHbie5zS/Rl4trY5Ybjl5UFsUuV8X9eVMFbm5i2ONBgXEAhCF9Ab1ouaaX3frXsn0l/7LwXS7/750jVCwCQZo2smhQ6B26InR7+jwzFLj8saUl0/MIMt1PvPkMTqSHVgtTVsumGv1vvFf3v6o9f13t/uWyqYcN1W9EUyxenNKhaIml9o+sC3Ub4AnrTSkkXRG12l0gajM3n3xT989sgujoBAFJ479eq9v9ijWz9sKKAszQ6/cWyGRbh8gOx/zvrG91OnftcJWlBdNm3yypZklW8hpPNNaLb7U+cdqliIa2Ot0vqjzXdCA03BlLuAyiU896XPQYAOYmmWWyKKmIAAHTEOXdn1Eyjm/cxJOmsZKUqqnANRYGw0/tYKmkw1nwDKAUNNwAAAFCK2PTDMfski5p1rB17rfYQvFAFVL4AAAAAoACs+QIAAACAAhC+AAAAAKAAhC8AAAAAKADhCwAAAAAKQPgCAAAAgAIQvgAAAACgAIQvAAAAACgA4QsAAAAACkD4AgAAAIACEL4AAAAAoACELwAAAAAoAOELAAAAAApA+AIAAACAAkwpewDdcNBBB/ljjjmm7GEAAHK2YcOGh733i8oeBwAA7RiX4euYY47R+vXryx4GACBnzrl7yh4DAADtYtohAAAAABSA8AUAAAAABSB8AQAAAEABCF8AAAAAUADCFwAAAAAUgPAFAAAAAAUgfAEAAABAAUoJX865IefcmibnL3POrWh0GgAAAAD0ilLCl/d+db3znHND0WXWRr8vSzutiHECAAAAQF6qOO3wLEmbouObJC2tcxoAAAAA9Iwqhq/+xO8L65w2inNuhXNuvXNu/ebNm7s0NAAAAABoTxXD17CkBRlOG8V7v8p7P+i9H1y0aFGXhpYP76X9+8seBQAAAIAiVTF8rVOt0jUgaU2d03rWqlXSsceWPQoAAAAARSqr2+EySYOhkUZ02hrpd804BqLL9Hvv16adVsa487Jpk3TPPVYBAwAAADAxTCnjTqPwND9x2vLY8cujo2sbndar9u61wwMHpCmlvAIAAAAAilbFaYfjXghf+/aVOw4AAAAAxSF8lSCEL5puAAAAABMH4asEoeJF+AIAAAAmDsJXCah8IW73bmvAAgAAgPGN8FUCwhfiVq6UTjvNGrAAAABg/CJ8lYDwhbiHH5YefVTaubPskQAAAKCbCF8loNthb/Je+ou/kH7843xvN4RwwhcAAMD4RvgqAZWv3rRtm/TRj0rf+Ea+txtC+I4d+d4uAAAAqoXwVQLCV2/qVoWKyldz73iH9Dd/U/YoAAAAOjOl7AFMRISv3tStChXhq7nvfpeGJAAAoPcRvkpA+OpN3Q5fTDusb+9e1kgCAIDeR/gqAeGrN3UrfIXbpfJV39690p49ZY8CAACgM6z5KgHdDntTt0ISla/m9u6Vdu0qexQAAACdIXyVgMpXb2LNV3n27JF27y57FAAAAJ0hfJUgbMQTvnoLa77Ks3cv4QsAAPQ+wlcJqHz1JtZ8lYfwBQAAxgPCVwkIX72JNV/lCQ03RkbKHgkAAED7CF8lIHz1JtZ8lSd8Zuh4CAAAehnhq2DeE756VbcqVBM1fN19t7R6dbbLhs8MUw8BAEAvI3wVLB64qtxq/tprmQaX1K1ph92qqFXdypXSS17SfCrhyEjtc0O7eQAA0MsIXwUL3+BL1a18Pfig9JznSF/4QtkjqZYQkvbtyzc4T9TK144d0oED0vbtjS8X/8xQ+QIAAL2M8FWwXghfYWN427Zyx1E18cCVZ5VqojbcCFWsZu+zTsLX8HBrlwcAAOgmwlfBeiF8haYGE60S00y3w9dEe75DkOpW+LrlFmnhQumOO1ofGwAAQDcQvgrWC+ErbOCyvma0ePjKMyhR+Wp8ufhnppX35L332nqxBx5ofWwAAADdUEr4cs4NOeeWOedWpJy31Dl3p3NuQ/RzWXT6VufcGufcJcWPOD+9EL6ofKXrVuVrou5kuduVrxDUDhxobVwAAADdMqXoO3TODUmS936tc26Fc26Z935t7CILvPdLossulTQcnX5B4nI9Kb4hWdVuh1S+0jHtMF/dXvNF+AIAAFVTRuXrLEmbouObJC2Nn5kIWAPe+3DZfufcQAHj66r4BjyVr95Cw418Za18xXes3MoXAuGyVf2cAQCAiaeM8NWf+H1h2oWccyu89/FdsC6QtMU5t7Le5Z1z651z6zdv3pzPSLugF6Ydho1iwtdo3V7zNdGe7xCOmnUkpPIFAADGizLC17AsSDWzPP6L936V935Y0nCYuphy/qD3fnDRokW5DLQbeiF8hUoD0w5H6/aarx07JO/zu92qY80XAACYaMoIX+tUq34NSFqTvIBzrj/x+4po/VfP64XwReUrXfz16sa0w5GR0e+P8a7b3Q4JXwAAoGoKD1/RVMIB59wySf1hjZdzLh7CFkjaEvv9qugyQ7Hb6Em9EL6ofKXr9rRDaWKt++p25StclvAFAACqovBuh5Lkvb88Oro2dtry2PFNki6O/T4saWP007PBS+qtbodUvkbrZsONWbPsNnfulBZkmZQ7DhTV7bCqX3IAAICJh50sF4zKV+/at0+aMUOaMiX/NV/z5tnxiRR4WfMFAAAmGsJXwXopfE2kIJDFvn3S1Km1KlVe9u+vha+JMu1wZKT2Put2q3nCFwAAqIpSph1OZL0QvtjJcrp9+6zq1deX/5qvuXPt+EQJvPFAReULAABMFFS+CtYL4YvKV7pQ+Zo5M78K1ciItZefaJWvEIycyx6+nGPNFwAA6G2Er4KFDcnp06u7URg2cPfssXAA041ph6GJx0Rb8xXeY4sWSY8+2vh9Fj4zc+Yw7RAAAPQ2wlfBwobkzJnV7XbY7hqb8a4b4SsE8Ila+TrkEKv8bd9e/7LhMzNvHq3mAQBAbyN8FSwevqpe+ZIIX3HxaYd5VajCe2CirfkK77HFi+2w0dTDdsMXlS8AAFA1hK+ChWpXlcNXvPI1UcJAFvv3U/nKS7zyJWULX3PnMu0QAAD0NsJXwcKG5IwZ1Q1fVL7SdXPN10SrfIX3VZbK15491mxj9mwabgAAgN5G+CrY3r3StGm2EV/VjUIqX+ni4SvvaYezZkmTJk2c5zuEqKyVr2nTrMU/0w4BAEAvI3wVrBfCV3wDd6KEgSy60Wo+vAe6sfPmKktWvoaH6182fGZmzCB8AQCA3kb4KtjevbahPWVKtbsd9vXZcaYdD5zBiwAAXkhJREFU1iSnHXrf+W2G8DVlSr6NPKqu3cpX1vfjyEitgkv4AgAAVUH4KljYkJwypdqVr/nz7fhECQNZxMPXgQOjd5jdyW1K9n6YiJWvVsJXK5Wv+NTZqn7OAADAxEP4KlgvhK89e2rhi8pXTXzaoZRPMI1PO5yIla8FC+yz0Cx8TZ/eWviKv2+pfAEAgKogfBWsF8IXla90+/bVKlRSPlWq+LTDiVj56uuzNvvNuh2GylfWLwMIXwAAoIoIXwXrhfBF5StdfNqhlH/4moiVrxkzmoev+JqvffuyhSnCFwAAqCLCV8F6pdshla+xuhG+4mu+JlL4CuGolfA1Y4b9Hl/P1ez2JcIXAACoDsJXwah89a79+7u75msiTTvcvdvClHOth68s677i79uqfs4AAMDEQ/gqWDx8VbXV/O7dFgSmTp04lZgsmHaYn127amGqlWmH4bpZbj+g8gUAAKqC8FWwffuqXfk6cMB+pk9vbb9KE0G3w9dEq3yFMJUlfIVuh+G6WW4/IHwBAICqIHwVrOrTDsN6mhkzJlYlJotutJpvdc3X7bdLt97a+f2WrZ3KV7vTDglfAACgKghfBatC+Nq5U3rJS6Tf/GbseWHDlsrXWN2sfIXb3b27cVh461uliy7q/H7Llqx8PfqoNDKSftl4q3mJaYcAAKB3Eb4KVoVuh7ffLl15pXT99WPPo/JVXxFrvqTG4WLHDum++zq/37IlK1/eS9u3p182uearlcqXc9WsMAMAgImplPDlnBtyzi1zzq2oc/5W59wa59wlWa/TK6pQ+Qobpg89NPY8Kl/1Jacddit8NQq8+/ZJDz5Yv0rUK5KVL6n+1MNOph3OmkXlCwAAVEfh4cs5NyRJ3vu10e/LUi52gfd+uff+8hau0xP27rUN+DK7HYaN17TwReUrnfe1VvPhJ+81X1kqanv3WpjYsqXz+y5TsvIlZQ9frUw7nDOH8AUAAKqjjMrXWZI2Rcc3SVqacpl+59xAi9fpCb1U+SJ81cQrVFJ+nQnja76yVL727rXDBx/s/L7LFK989ffb4fBw+mU7mXZI5QsAAFRJGeGrP/H7wpTLLJC0xTm3Mut1nHMrnHPrnXPrN2/e3PEgu6UK4Str5YtphzWhQjV1qh3mHb6yVr7COHo9fLVa+Wqn1fyUKXY91nwBAICqKCN8DcvCVV3e+1Xe+2FJw9GUw6zXGfTeDy5atCinoeavCuGLylfrkuFr5sxy1nyFytcDD3R+32XatSv7mq9kt8O08PW2t0m///ujb3/GDHteqXwBAICqKCN8rVOtkjUgaU38zKiClZxW2PA6vSTe7dD7chonUPlqXXx6oGRVqrLWfEm9X/navTtb5cv7bGu+fvxjaePG2u8h3E2eTPgCAADVUXj48t6vljQQNc3ojzXRCIHqquj30GRjdb3r9Jr4hmRYO1RG9Ste+fJ+9HlUvtJ1e9ph1jVf42naYZbK14ED9h5ttuZreHj0mjHCFwAAqKIpZdxp6GIoaW3stOXR4bCkjdHP6kbX6TXxDckQvvbts9+LFDZe9+yRHntMmju3dh6Vr3Rp4SuEhUsuke68U/riF1u/3bRphxOt8tXXZ48/LXyFxzttmn0hEK6bNDxsoTV8uUH4AgAAVcROlgsUNuCrUvmSxk49TFa+QmvziS5tzdfOnfb6/cd/SOvWtXe7aQ03xnu3w5ERC/mhkuWcVb+aha/Jk+35T/tCIFS9tm61w3j4ouEGAACoCsJXgeIbkmWGr3jlIBm+kpUvqTerX7t2SQ8/nN/t1Zt2+P3v2z63Hnuss9udSA034u+xoFn4ClWvvr76lS9pbPii4QYAAKgSwleBqhK+Wql8Sb257utd75Ke/vT8bq9e+LrmGvv9scfGrp/LYv9+q/xMnpxt2uF4WPMV3n8h3Ev1G5jEPzOSBbZk+Nq1qxboQvgK0xqZdggAAKqE8NWh97/fOq1lEd+QDBvxZVW+nLPj47Xydd990l13tReI0qRNO9y+vRa+DhzItv+ppP37a0F88mQLvfXC7oEDNmXPOXvdyuiUmYfwPMUrX9On1957ceG0ePhKvh/jjTbSph0SvgAAQFUQvjqwfbv09rdLV12V7fJVqnwdeqgdH6+Vr1ANySs41qt83XuvdOaZdlo7Uw/j4St+u2nC++eQQ+x6IWhUwXXXpe+6IE1a5WvatNrji0tWvtKmHTYLX1k+Y/v2ZR8/AABAuwhfHbjnHjsMG+bNpIWvrNfN0+7dtsamv79+5Sve+rwXK19hzHkFlLTwJUmTJkkvfakdbyd87ds3Onw1au8fxnDEEXbYbN3Xxo3S5s2tj6mRH/7QpnPG3xMjI9JzniOtXJntNlqpfGWZdtgofGVd8/Xxj0snnZRfpRQAACAN4asD7YavqVPLr3zNmCEdfHB6+Joxw6a2hcpEr1a+JGuGkYd4YwypFr7OOUd63OPseLuVrxDoJAtfzSpfRx1lh83WfT3nOdIHP9j6mBr50IekG2+U7r+/dtqePfb8ZN3vWSeVr7TwFQ/Y7U47/M1v7L1CZ0QAANBNpezna7y4+247bKfyFZS15quvzwJE2rTD0FluPFS+8gpf8Z0hS7Xn5g//UJozx44XNe3wyCPtsFn4evTR9rswptm6VfrqV+14PACF42nhKU24fDx8tVL56saar/AFQzIMAwAA5InKVwfymHZY1cqXROUrLjnt8KST7Pl70YvyDV/z5lloajSGrOFr797sgSiLq66q3V5a+EoLT2nCaxOfdtis8tWo1XwIX9OmtR++wpiofAEAgG4ifHWgk8pX2d0O64WvtMoX4Wts+HrqU23N1RFHdBa+kmu+5s0bXcmJC++fgw+2cTRa8xU6I+a5pvAzn7E1blK5la964euYYyx8jYzY9UKr+SyfsXjlCwAAoFsIXx3ohcrXQw9JGzaMPi1UBQ4+2HZEHK8MpFW+ennaYbcabki1dv2dVr7it9nfn76zYWl0FWjx4saVrzDevCpfd94p/eAHto5M6ix8tVL5ytpqvq/PnpPh4dHhLmvDjXCbZTTAAQAAEwfhK8Xq1dKXvtT8cp1Uvorqdviud0nPfe7o0+KVL++lRx4ZfR6VLwttYR9eUnr4CvKedliv8hUfQ7PwFd5reYWvz37WwuarX22/l1X5Spt2uHWrhdb58+14vKFHO2u+AAAAuoXwleJf/kX6139tfJldu2obv1WufN1229gAEq98SaOnHo63yle74eszn5H+6I9qlbNG4Wv2bDvMI3z199vtpO1AOf7+yVr5yiPce2/h69xzpWOPtdOKXvPVbNphp+GLNV8AAKAIhK8Ujfa1FNx7b+141g3ccLkiw9ftt9t9xDdsQ+Vr0SL7PR6+4pWvXm244X3n4StUn7Zvt8NG4WvKFHuu8lrz5X16041Wwleela/Nm6VNm6TnP78WmLpR+eokfM2fXz98ZfmMtRK+3v9+6R3vaH65Ivzv/0rXXlv2KAAAQFaErxQzZzav9oT1Xs5Vt/L18MO1KYUhREjZK1+TJ9tYe63ytXdvbWe57a75Cs9Xci1QvTbkc+bkt+ZLSl/3Fd9P3CGHWPhKq5DFx5tH5Ss8B/Pm5RO+6lW+Gk07DF8I1FvzFSpf27fXXod2ph1meb6++c3qBJ5//EfpbW8rexQAACArwleKvr7m1Z6w3uvII8sNXxs32sZgmttvrx0P+47yfvSaL6l+5UvKVgWsmvjGebuVrxC+khvl3QhfycqXlL7uK145XbzYrlsvXOZZ+YqHpW6Fr+nTLUgmg1Lamq/9+0d/buJrviTpt7+tXbbVhhtZPo87dmSfYpnVyIj0T//U+ntox47e+3IEAICJjPCVIkvguOce27A76qhyW81ffrn0l3+Zft7Pf147HsJX2Gjs65MWLLDW4fUqX1K2KmDV5Bm+iqh8Jdd8SY0rXyF8SfWnHubZ7TCEq7zCVwj4oVukVAtXyVCT1u0webl45UuS7r+/dtluNNzoRvj66U+lv/7r0U1estixY+w0TAAAUF2ErxRZwtfdd1vVa8aM7AGqG5WvXbvqb/THK18hTMQ3pCdNsnVfjSpfWaqAVRMC09y5+VW+wusUD0px7YavffvSpx2mVb5aCV/hsnlMO8wavlppuBFf7yXV3nPJAJe25ivchmSV3GT4ile+utFwY/v2/MNXeL8l97vXzM6djcPXRz4ivfCF7Y8LAADki/CVIuuar6OPtg3nMlvN79kzej1XXFrlK96MQBq7o+XxVPk6/HBrXNFOwC2r8pVl2mFY8yXV39FyntMOk/vNmjSp88pX/D0m1a98xde5hTHE73PHDgtXaZWvVhpulF35Cvefd/i6+WbpxhvbHxcAAMgX4StFX59t9DXaELv7bumYY9oLX1On5lf52rvXNsBCg4m422+Xliyx4yF8xasY0tjwNR4qX+ExHn64Hdbbb1Yj7az5SutQ2Ey3px3mXflybmzHwXbWfLVS+Zo82X7CGOL3GV7b+fNrz108fGVZ87VvX+1zmOX52rEjv/2nxW9Tyj98sSYMAIBqIXylCDsXrrfRsnevbeC1W/nKM3zt2TO6tXqwc6dV5848035PVnLKqHzt2lXbMO6meOVLam/qYVrla9Ik+0kzd273K1/x8NUopMUvm3fDjXBYZOUrnBcfQxhTaDjSqPLVLHzF39/NPo/79tmYqlT52ru3ftdLwhcAANVC+EoRwle9is+vf22Bp53KV5i2lWf4ksZOPfzlL22Mg4P2exUqXx/8oPT4x3e/khY2Ng87zA7bCV8hSMUrX/WqXlJ+a76mTrX3X1qoilffJk2y1zC8rvUum3fDjXBY5Jqv+PuxXuWrUfjyPr0yHB9P0OzzmGxck5d2wpf3tevVq36FaZl5VEABAEDnCF8pmlW+Qpv5dipf4Vv8vLodho3A5EZ4aLZx1lmjz09WvhYvttCwc6d9e75v39hW8+E63/lO/bb2WTz4oG0sd3IbWSQrX+3s6ytt2mGz8LVjR/0KRD3Jypdk1a9mlS9JmjWrfpDtVsONcFhW5Su55isevkJDkDAVM4QvqXH1K/4cZg1fBw5ka+SRVbjdRjvOTtqzp/Z+axS+JKpfAABUBeErRdjAq7dhG3aw3En4ynPNlzS28vXzn1t15PTTR5+f3JA+9FA7/O1vaxu+8Q3jUPnat096+cult761/bGG27/66vZvI4tuTTtsFr7i18sqLXz19zdf8yU17srZrYYbUrFrvvbsSZ92mBa+JKt+hUASWs1LjT9nrVS+4q9vntWveOWrUZUu7ToS4QsAgF5RSvhyzg0555Y551aknNfvnFsaXeay2OlbnXNrnHOXdHt8zaYd3n23BZsjjug8fOXR7VBKr3w97nFWRZk0qX7lKy18pe1k+ctftulcv/lN+2MNG4hf+1r2jcG9e6UnP9mqbll1Gr727as9F61UvqTWpx62U/kK42gUvrrVcCMcVmXNV7zhRvxwypTaj5S98tXs+Yp/zvJsuhGfzpj1PUT4AgCg9xQevpxzQ5LkvV8b/b4scZELJQ1671dH54eAdoH3frn3/vJuj7FZ+LrnHltPFHaWnHUDd9++/Ctf9cLXz38unXSSdaebNav+mq8sla9du6SPfcx+37atvbVN4b6ds7Fce2226zzwgHTTTdL3vpf9fsKGZnhsrYav+HPZauWr1ecm7XbrVb6SHReLqnxlbbgRmr9kub1W1nw1mnYYppSGRiUhfIXLZZl22M6aL6k7lS8p+7qvLOErWcEFAADlKqPydZakTdHxTZKWxs/03q/y3q+Kfh2IXbbfOTdQxACbrfl66KHafpbKnnaY1nDjwAFruHHiifb7rFn1ux2GgHL//bUNuGTl69FHrfL0hCfYaffd195Yd++WTjlFWrgw+9TD0L69lbUw4THOmWNdCFtd8xV/LsuofPX31698TZ1qAVbKVvnavz/7NLZ6QmiO7+g4LXyF+8tye51WvuLTDmfPrj2H7YSvVtZ8dXvaoZRf+Io35CB8AQBQDWWEr/7E7wvTLhQFrS2hQiZpgaQtzrmVdS6/wjm33jm3fvPmzR0NsNmar23bat+0dyN8XXutdMIJ2TaYQqUg/o38XXfZhuFJJ9nvs2fXr3wtXGiPoV7lKwTRadOkd77Tjrc79XD3bhvLH/2R9NWvZnt8oQLUTvjq65MWLGi98hXfwO525avetMN6a77iQWTmzPrdDuMVpE6nHoawFEJfo/CVpdKWR7fD+LTDsN5Lqh++8lrz1a3KV/x2s4av+HXSwteuXbXgTfgCAKAayghfw7Ig1cyQ9/7i8EtUERuWNBymLsZF5w967wcXLVrU0QCbTTvMI3w5ZxuGaRt7t91mlatf/ar5baZVvu66yw6PPdYO49MOk5WvSZOsivfb36ZXvsLlLrxQOuMMO95u+Ar7ELvgAhvvt7/d/Dqh8vXAA9nvZ9cue1xTp3YevsJ7YP/+8itfyQDYqNth/D3Z6dTDZKWq0/C1e/fY8NVqt8PwuLOGr6zTDpOf5WQDjG6t+dq506q04T6zXidIC1dp02cBAEC5yghf61Srfg1IWpO8gHNuKKztippvrHDOLU1erltaDV8jI/VbjH/yk9IPf2jHkxuSU6akh6+wMRtCVD0jI7Xrxze0QtUkbIjGpx0mK1+STT2sV/kKG7avf31tv1mdVL5mzJCe9SwLRZdd1nyjsN3KV1+fBdz58/OrfCVDUlyea77mzbP3SrKakVb5arbmK9xHJ/IOX7t2jZ12mHXN17x59t75yU/s961ba+9zqXY83H6rDTfin8etW6WjjpKuuaZ2WremHe7YYfsNlPKbdkj4AgCgegoPX1EjjYGo0UZ/rPHGmuhwmaTLnHMbnHMbZFWyq6LzhmK30TXN1nzFw1ezroVveYv0kY/Y8bTwlXa9sFHXLHzFN/7SwlcYY1rlKy18pVW+XvIS6frrpSc9ya6zaFFn4Wv6dAsbH/2oNdJ48Ysbh4NOwpdkG+rtrvmK72C66DVf0tjqV7vhq9MKTTIszZgx+rMR3/BvFki8t8tkrXwlW81PmmTh/frr7bbyrnwlw9eePdKmTbXTutlwY/58+8wSvgAAGL9KaTXvvb/ce7823rnQe788OlzrvV/ivT8z+lnrvR/23m/03q/23l/a7fE1WvN14IBtnMcrX1J6gNi3z8JD2Clz3pWv+EZ1/Bv5MFUvTGNKrvmaOrW2USpZRev++9MrX7Nm2cZucMQRnVe+JAt1H/2orf161avqVw7DY9m+vf76pqRk+Gq38rVoUXlrvqSx676SY8jScEOq1rTDtMqrVPtcNKt8SdJ550n33ivdeWc+4ate5Ss8h/HXoZsNN2bOlA4+mPAFAMB4xk6WU4QNw7QN2xAGsoSvRx6xwxCiQre6oNPwVa/yFcYYAkGy22Gy6nDooRZQwkZmvPKVlFf4kqTXvlZ6z3ukK66wfX+liW/4Zq1+pYWvVjr+hQB18MHZK18zZ1pVppXwFaardlL5KqrhRvw9kxa+Jk0ae7/1bkuq33Cj2ZovycKXJF133djwFY7n0XAjPG/x16GbDTdmzSo+fP3sZ9KqVennAQCA/BG+UjhXv6qQnNLXKHw9/LAdPvSQbQjlXflqNO1w9uzahmdyP1/JqkNoN3/PPXaYPD8uz/AlSX/xF3Z4553p1wlBUmovfM2fb89xvGLRTDuVL+fsOW8lfIXXPm3Nl9Q8fM2aZeOqV3WNX68TzSpfe/bUqqzN7itt2qvUWuXruONsB9pr19p7vVHlq5OdLKdVvrrZcKPVylezbodpaxeTPv1p6XWv63x3BAAAIBvCVx0zZ6ZvsLQTviQLNskNyalTm4evRhtFjaYdho1hafS0w3qVr3B/UvPK1yOPtDeNKXQ7jOvvt/v77W/Tr5NH5UtqbephPHxlrXxJVmlsJ3zVq3xlmXYopb8W3W64ceBAbfy7d9feb82qQclum0GjbofJ96NzVv269lr7fOSx5mvWLDte1rTDHTvKmXa4d689N52+RwAAQDaErzrizRbi6oWvtBAVD1933dV65Wv79trUxTSNph3Gw1eofI2MpFefQhfDEL6aVb6k9na0nHbfztXWnKV59NFagMrabj4tfLXSdGP7dtvgnzs3e+VLssvnGb6yTDuU0t+n3W64IdXep/Hw1e6ar8mT7SdL5Uuy8BWCUFq3w1bXfIUpuvHPYzierHzVmyLZiZ07a9MOH3648Xjj1wnvyXZbzYfQxZowAACKQfiqo960w1bWfGUJX2nXi3+L3WjqYXzjL1n5CuOTat/q79rVuPIVGoM0q3xJrU89HBlJr2KE+29U+Qr7Kyuy8jV79uj3QJGVr3oNN1oJX3lPO0yu+Qqnh8NWpx0m34OSvTeadTsMzj23djyt8hXGmHXN1+zZtm4ty5qv8J7KK3x5X5t2uHix/d7oS5dg5057r0ye3H7lq9PwtWaNdOml2cIiAAAgfNWV55qv6dMt2Ozbl63ytWePbQxK2cLXpElj13wlpx1Kdpm06tOiRXYbrVS+Wg1faZ0Ug8MOaxy+DjpIWriw+PDV12fjDtOy8g5f4T2TvN1Zs2yDOq/KVzemHYbTQ+v4rOErvA/SQvi0adkrX0ccIR1/vB2Ph6++PgvzRx5pv2etfPX1jf0ypN60w4ULa2PLQ3geQ+VLyjb1MFTLkmvwgvA3Yfbs/MPXQw9JL3+5dP750uWXZ9shPAAAIHzVldear7lzpcc9rvVphyeeaMcbha+w8bdgQfNph5JdJq3yNXmyfeMeNuIbVb4OP9wOWw1f9aabSbax3Gja4bx5Nr52G25IrYevOXNq4Wb3bnudsoSveIOQZupVvpyzx9xszVf8dU3qdsONcHoIU+Hz0Oy+wrjSAlVa5ate+JJqXQ/j4cs56ac/ld74Rvs9S8ONXbvstU5+Hus13AjvqbwqX+FzF9Z8Sdne66Fa1ih8TZpkz0+e4eu3v5VOPVW66ipp2TI7Lc8pmAAAjGeErzpaXfNVL3wddJCFr7vvbi18HXywhaosla8FCxo33Agb6du3p1e+pNq6L6lx+Jo1yzY+8w5f27bVf77nzpUOOaTYNV9h2qFk4ypy2qFkG8xVqXw1WvMVXtewZqrZRngYV9pzmax8eT+2Whz3kpfYlwHHHDP69Pnza7ffauWrXvgKjW+6Me0whOd4+MpS+QpNOhqFr1mz7LHlGb7e+EZ7Tm6+WXrTm+y0tPsHAABjEb7qaDTtcNq02gZo1vCVVvlq1O1wxoza9eqJh6/ktMP4mq/4tMO0ypdUW/c1bZpVDxppp918s2mHUvrUw04rX319FibbnXYYbm/fvvSQFJdn+EqrfJW55qtZ+Mo67TCcX6/ylRYa630ZcM459j6MN9xIytrtsFH4OnCg9vnavr298DUyIq1enb4z8fD6tTPtsFH4ir+Pm4WvejvrTvr616Wrr5be8Q7pjDNq7wUqXwAAZEP4qqNR+IpXlbKEr2OOscrLrl3ZK1/Tp7cWvkLla2TEAkC9aYf1Kl8hfDWqegXthK9mlS9pbPjas6e2nqjd8OWcdNRR0oYN2cdaVOWr3povKb3yVa/VfL3KV3iuOwlf3o9tuBGOtxO+Gj3madNGb8Q3CmpZZWm4EUJM8suQ+Gc6BOEdO+y1cW5s4GhU/bnxRumCC2zH0Gn3L9kY5s+3Mbcavup1O8yz8rVjh+0T7OSTpUsusdPC3wsqXwAAZEP4qqPRmq94VSlr5SvI2u0wVL7uuSf923Jp9JqvsLPdEMLqTTusV/kK1adGzTaCTsJXvW6H0tjwFZ/iuXixjT9tfVPcgQP2vMQf45/+qW30/uIX2cZar/KVJXzt3Zs97HSz8rV3b+1172Ta4f799v4ro/KVR/jKuuarUcMNqTb1MASa5FhvvNFCWb21i2Ha66ZNY8+LTzucNMka4ORR+co7fL33vdK990orV9Zek2TnSwAA0Bjhq45Ga77yDF/Nph3u3Vt/gy5e+ZJsYyu5Jk1q3u1Qar3y9eCDrVVUGlW+QvBLPs54W/9DDrHjzapf4X7i4euii+x5/9jHso21k8qXlL36ldear3oNN0L46qTylfa6pYWv8H5rNv2sUcONZOUrHM+j8tVJww3JPle7dlkAmz177Fg3bbLf61Wqw5ci99479rz4tEMp+46WQ7fDvr5iwtcNN0jPeIb0tKfVTmPaIQAArSF81dFo2mGW8LVrl238hGmHQdZW8yF8SfU36JLha/v2WmBppduhVAtfWStfUv1QmKZR+FqwwJ6XepWvMO1Qah6+wkZk/H4OPtimfH3qU6Mbk9Tz2GPtV77C9bNoFr6ydjvsZuUr7fnMo/KV9lx2o/KVR8MNyYJwCLmh8hUPHOE1qLe2MFz3nnvqnxfCdCvhq8jKV3y9W8C0QwAAWkP4qmPmTNugSE75yxq+wj6+Fi2yDZawYR7f6MxS+ZJqOz9OCht/Yb9DO3Y0Dl+Nuh22WvmSWpt62KjhhnPp7ebjla9Ww1cyYP7FX9jtXXFF4+sfOFDb6W63K1+N1j/Nm2fjjYeGZOUrPMZ6DTeKrHy1uuYrS+WrqDVfofLVbM1Xo/AV3nf1wlcI/Wnhq93KV9HdDsOXEnFMOwQAoDWErzrChndyo6TV8HXQQRYuQpBq1u0w7NB3xgzp6KPttHqVr/iaL6l55WvrVps2lceaL6m18NWo8iVZ+GpU+QrTDpu1m68Xvp74RGnpUunf/q3WNjxNfMe04TZCmGo3fF11lbRixdjLN6t8JW8rGb4mT7YQUK/yFTaU8whf8eczLXxlva+qVb7277fPW7PK17ZttQA1e/bYsTarfGWZdthK5cv71ipf9cJRq5WveuGLaYcAAGRD+KojPuUsrp3wJdWmHjabdhivEM2YYaEo67TDemu+pk61+w1jSgtAixdbSOxW5atRww0pvfIVfyyLFtnxditfzkmvf73tgHfduvrXj29gh43hEGjbDV+rV0uf//zYyzdruCGNXveVVn2rNz223WmHX/yidNJJtbFlrXyFlv55rvkK4SbLe7KeZg03wvslrPlq1HCjk8pXuO599439zCenHR5yiL0PG02R3bfPHlNela8srebTwhfTDgEAaA3hq460TnKhjXs74Sut8pXW7TAZUgYGpDvuSB9j2PgLVZJ60w4l2wgLY0qrfE2ZYgEnS+VrzhwLM8k1SY00q3wddtjYylf8sUydatMr2w1fknT66XaYtj+xoBvha9Om9I3fLJWvEL4OHLD3XzK01Atf7U473LBB+vnPa69t1vA1Y8bYnSSnqVrlKzx3ra75SgbFrJWvAwcsgKWNIbxe4W9FWmfEtOvUazXfyn6+mlW+9u+315pphwAAdIbwVUda+HrsMZvuEw9fYcO53fCV/BY8ubH7xCdK69enb9zs2WMbrGGDqN60Q8ku06jyFcYYNvobca7xBl2aLNMOt24d/TiTVbxDDmmv22EQnvtGASEevsJtZA1f4TlPC19hOmlcszVfUu05qBdEZs1K73a4d2/tPdxK5SuEh/CYszbcaDV8Fb3mK2vlKy18zZ+fPu2wnTVf0th1Xzt22H2H98HAgB1mCV8zZ6ZPKwzTEkM42707fbpt1vAVn44bN3Vq+j7PAABAOsJXHWnhq96UPik9fDlnG25S9mmHyZDyzGfahs0Pfzh2jHv32kZgvJthGGOowgTNKl+S9LnPSR/+cPp5SXmHr7DmLF6VevTR2ka9ZFMj213zJdWqia2Gr/CctlP52rq1to+n5PPVSuWrXhBpVPmaNs3G3ErlK4w1hK+0MDtlioWadsLXvn32uQihKC5Z+cqz1Xy9hhvxyldaw43Jk2vhq9Nuh+FxJNd9hbVbwZIldnjnnfUfVzx8pU07DG3xw7RDKf0LnKzhK/65iAtTlal8AQCQDeGrjrQ1X62GrwULaht/Z50lHX+8racJmq35kqSnP912vPrd744d4549tkGXrHzNmWPXiYuHr3oBaMkS6cgj089LajV8Nep2KKXvaHnbttEVvMWLO5t22GrlK1QjwuueFpLi0sJXvHqRDEmtrPmqVyVrtOZr2rRsgSiuXvhKvm5hg7+dyle9MFVm5SttJ8v799vzHXZ4nQxf8ceapfJ1wgl2PFn5ChWqYP58C9+NKl/xdWJpla34WOutX5U6D19S/TVnAABgLMJXHXlUvsKUQ8kqO7/4hQWw+HWbVb7mzbMufd/5ztgxhmmH8crXo4+OnXIo2WUeecSO16t8taLdylejhhvS6KYbyeYmeYWvRlOkkhuZM2dmn3Y4dardb3wDPL4B3UrlK7yGYTztVL6mTrWfTqYdthK+sjbcqBemurHmq1nDjXgFKW3aYQhfw8O116Kdytf27bamctGi9GmH8cqXZFMPs047nDHDglf8de5W+EpW1CW7f6YdAgCQDeGrjlbDVzJEJcNXmizTDiXpWc+Sbr557EZ2CF99fTb9J4Sv+PiC2bNrG7NZmmo00074CtPV0tSbdhgPkqELXNoapyCvaYdhI7OvL/u0Q0k68UTrqBg0Cl+N1nzFq5nxMWcNX0VXvqZPz175qvc8dqPbYauVr2T4mjKltsPr+Lqn5FizdDucPdt2H9Gs8iVZFbqVaYfS6OpTkZUvph0CAJAd4auOvCtfabKGr2c+0zZEk+u+wpov52wja/v2sVP1gvjGXR6Vr3ob/fXU27lzsHChPR+NKl+h3XyYPpkmz2mHUmuVL0l6whOkH/+49nu7la8ZM+x1DeNpNO2wXsONsIuBvBtuhN9D+Jo2zaa5Zl3z1ajytW9fbfpckd0Om1W+QviaMsXGU6/hxvBw+n1t326fwaOPbr7mS7LK1913Nx93aKghdRa+mn2WmXYIAEA+CF91ZF3zFTbu2g1f9VrNxzd2n/Y0u5/kuq+w5kuqdb1rNO0wKKvy1eh+J00au6Pl5GNJC8RJeU87bLXydfrpNjUyNAZpN3w5Z2MIG9GNuh0mn48DByzAtNpwY2Sk9lgbNdyQRoev8LrmUfkKl4kfdrPhRvz9ktZwIx6+QoCS6u9kWapVD+NC2/dQ+Uquz0qGryVL7P7r7UsvrfIVf3+lNY7p5povph0CAJBNKeHLOTfknFvmnFuR9fxm18lb1sqXc2PX1XjfeeUrPtVq7lzpzDPHrvsK0w4l2ygKDTfSwld8o6mMNV979jQPfcnwlax8NeraFjQKX2Gjv1FAeOyxWsVIaq/yJUm33mqHd94pHXWUHW+l4YZUq2bGx5xl2mF8X1qtTDvctq0WClqZdhgPX52u+YqPP49uh62u+UruZDmEr0cftfdG+BylVb6cs+NpUw/j0w537RpdvU2bdtis3Xyy1bzEtEMAAHpB4eHLOTckSd77tdHvy5qd3+w63ZAWvh591DbQkhv2yfC1fbttQLYTvup1BXzWs6Qf/Wj0FLN4+AqVr2RgCYqofH3yk9LFF6dfvlnlS7Lw1WjaYdo3/Em7dtWmwSU51zyMhApF0GrlKx6+9u2zKWaPf3z6uBut+ZJqgbrRZdPCV7hsqHxlnXYYDw3J8JVcd5UWvpLVoDRZKl/hM1BGt8O0yld/v4XSBx6ofY7SdrK8eLEdT4Yv72tVsxDE4+u+6lW+pPrrvpLdDqXWw9fIiP2knZfEtEMAAPJRRuXrLEnh+9xNkpZmOL/ZdXIXNliSla9582rfcAfJDdzkDpbrmTq1NkUsqFdpeOYz7T5+8IPaaWHNl1SNaYdr10qrV6dfPkv4OuywWuVrZMQqDfHHkrXy1aiy12r4mjmzeUiKmz/fNrB//GPp17+217de+GpW+coy7XDmTAsB8XARv2wrla/4dLl4+ArrCuM6mXaYtfIV3wdXuzrdyXKofEn2xUB82mGy8nX44XY8Gb7CPrdC5Usave4rrfJ1xBE2niyVr3bDV/xvVtbwlRynxLRDAABaUUb46k/8vjDD+c2uI+fcCufceufc+s2bN3cyPklWOZk+feyar7SqUr3wFRpE1JM2Jape+Hryk+3wlltqp8XXfM2ebRvM27eXN+1wxw7bgE/b0A0b8Y0cdpi1w9+50x6H9+1VvpqFr2ZrvpLhK8gSviRb93XrrbUN53bDV9Zph8nbTk477KTyVe/5bDd8hUCTJln5evRRu2wnXxZ0upPlePi6776x0w7DFyc7d1pgksaGr3iXxBC+4pWvtIYbU6bYjtnrVb7i4y4qfIUwn8S0QwAAsisjfA1LWtDi+c2uI+/9Ku/9oPd+cFGz1JNRckpXq+Ery7RDafQGX73wNXeuBcL4DnyT0w5D1aisaYc7d9rGaFrDgSyVr1NPtcNbbqlt/KdVvpL3+8pXSmvW1M5rFL6aTY1Lm3YYZA1fT3iC9POf11rOn3JK+rhbqXzFpxLGxffxFiSnHbZa+Zo7d3TlK+11K6Ly9dhj1vI/WXVrRZbK1/Tp9tlqtOZLsi8G4pUv72uV60aVr3jVaP58e12bTTuUGu/ra+dOe29OmtR5+Jo0KVv4SptyKDHtEACAVpQRvtapVskakLQmw/nNrtMVRYWv+HXrha/QTr7emq/Zs2vhq9G0wylT6m/styJtrVEYW9iZc1yW8PXEJ9rhzTenNzdJm3a4f7/0qU9JV11lv3dj2mHQSvgaGZG++lW7v2OPrY0trtl0xrTKV9qaL2n0a9Fuw40Qvo4+uhbyWwlfne5kOa3ylfZebkVY+9eo4UZ4vzRa8xXEw1cYa3g/hn3V1Qtfs2fb5zi+r68DB+w20qbzLVnSOHyF175e+ArV+2bha+7c2hcn9TQLX0w7BAAgm8LDl/d+taSBqGlGf6yJxpp659e7Tre1G77CrMdOKl9pU/TiDRiksWu+wu00mnaYR9VLsg26fftGb9SG5yptP1xZuh0ecohtmN50U63y0mzaYTh+xx213/OcdthO5ev00+3wu9+1qWPh9up1O0xrDiKNfr2bTTuM33anDTeOPrq7la96z2Oy8pVH+HLOql+NKl/Nwlf8PRhey3hQDO/B2bMtqDWadiiN3tdXfO1W0sCA3dbw8Njz0sJX/HOxY4f9TXAuW/gaGWn8PmkUvph2CABAdjnUQFrnvb88Oro2dtryJuePOa3bklPrWql8TZnSfMOxXviaNi19qlUyfCXXfAWNKl95rPeK386uXbX77rTyJUlPepLtTDpUvpo13Gg1fLU67TC+UZy1Yvi4x9Veq4GBWrUxbdrhlCn1p9VlbbghpVe+2mm4MWOGde0LawuzhK/Q5a/TnSwnK19h2mGnGoWveIhJC199faM/82mVr3DbM2dKCxY0nnYoWUOWm26q3X+4blLoeLhpk7Q00WIoPlUx7XMRfx9nCV/h/HqvDdMOAQDIBztZbqDdyteWLbYR1mytSqgAxDf4GlWIGk07jE9barTmK8/KlzR6g65R5StLww3Jph7ee6+tmZKaV77Cfd5/vz03eU87bKfyNWlSreV82F9T2hq5/fsb32Z82mGjVvNSeuWrnYYbCxaMXvOVd8ONoitfkoWvRjtZDo+vWcMNaWz42rt3dLv6RuErvK+WLKlVtMLrljbtMLx30ppuZJl2GG5z8mR7HFnCVz1MOwQAIB+Erwbi4ct72xjMEr7SWkenqVf5qheQ0ipfaeGr0bTDblS+grwqX1KtgUazhhvx47/6VTW6HUrZwte+fY2rabNn2/N24ED9yldaw434ZVttuDF/vj3njz1mU9FanXaYx5qvvMPXlCnZK19pDTdmzBi9tlIaXfmKdx5MC1/JaYfHHWeHd9wxen9dSY12tBz/G9MsfIWx1Qtf4W9au+ErTDtstGYMAAAYwlcD8fC1fbttjGYJX7t2ZQsanYav+JqvrNMOu1X58r555SvLfZ9xhj2f3/2u/R5/vidNsg30tGmHkvTLX3ZW+RoZsY3WTitfUm3dV5g6NnNm/WmH9cSnc7Yz7bCdhhshfEn2XmsUvg4csLEld7LcaCM8S+WryGmHyTVf3td2PBxvix/eh2nTDuP7Cssy7TCEr1/9qnHla84c211Fp5UvqXH4ylL5euyxxpWvkZH61UUAAFBD+GogvsGS1n0vSIav3buzVZjqdTvMMu0wbOyEjfGs0w67Vfnau7e2gdtJ5WvGDAsuO3fatM3kBt+MGenTDiWrJHSy5ivsDDe+wd9u5es5z5GWLZOe+lT7va8vveFGo/AVXrPt29ubdthOw40w7VCyylOj8CXV1omF+wvt1+tp1Gq+W5WvVtZ8SbUQEZ8WmgxfaQ03sk47HBiw93azypdkDVvibenTxh2CYDfDV7NphxJTDwEAyILw1UC88tUofCWnKzULAPHrSe1VvsKGTrLyFVrSp11Xyq/yldzoj2/8t9vtMAhTD+fMGdsJsK+vfuUrS/hqVAkK7dXzmHZ4xBE2dTLscq6dNV+dVr7aabgRr3w9+mj9Km447cCB0eErfv9pGu1kOdnEYseOYtd8JT+PWStf8aYZCxbY8xiqZ9LofW5J9nwdeaS9Xxs13JDs9Qh/e+Li4cu5sTuEbyd8Jb8ciGs27VCi6QYAAFkQvhrIGr7KmHYYNnCTa77SAotU28DsVuUrvuYoWfnav99+soavsL+vtOc6WfkKx+fMyR6+6n1Dnxa+2p12mNTOmq945audVvOdNNyQapWveg03ksezhK+sla/wWhRZ+Qqvb3i+4uEr7OsrueYrreHGyEitYYlkr9+MGbUdPku277dm0w4l+wykha/kjpmTHQfzrHylTceNS5v2+NnP1jqQAgCAGsJXA52Er04qX/W6AsanHSYrX2FDK218kgWymTO7t+YrHr6Sla/kWJsJla+0De/kRmR4fU47rfNph2mvcbuVr6R6la8sa77i0w6Tl29W+cracGPfPnsNk5WvZtMO48eTreLr3U+98BWvJoXwlcear0YNN7pR+ZJGTz1Mqxodd1y2aYfxzpNxyaY+yYpwWtfOdsNXeHxZpx16L73yldJHP5p+eQAAJjLCVwNhg8X77qz5arXVfOh+t39/bUMnuZ+vRpWC2bO7V/kKG2iHHjq28hU2CrMGv4EB20F12nNdb9rhE54gPfSQfUvf7rTDtNc4r8pXHg03pk4du/uCKVPsMaV1O2yl8rV1qx1mDV/x5yXecCN+/2kaNdyIV75C4ChzzVe8MtnKmi9pdPhKqxodd5x9Tu67z36vF77mzasfvoqqfCXXrCUlpx3u2mXP929+k355AAAmslJ2stwrZs604LVnj+13atIk6fDDx14uuX+gTitfBx2Ufvn4xni9ylejjdVnPUs6++zm48qiXuXrqKOkdessBIXpj62GL+ekN74xfYO0XsON0No9PrY0jaYdNqp8TZqUPp0zq3oNN5rt50uqTTusVzFK7o8u2XDjwAH7iU97SwphodWGG/HjWdd8Zal8FRW+sla+6k07zFr5Sk4rPPZYO7z1VjusN+1w7ly7fvz127fPfuqFr9B5NBm+khXprK3mm4Wv5LTDcHnCFwAAYxG+GohP6brhBuvCl7Yx2Omar1a6HUqjGzAkG2402lj9wheajymrepWvo46Sbr7Zgsz8+XZaCDutTHl8xzvq329a5eu008aOLU2jaYfDw3YYNrTjt9VJ1SvcTjv7+ZJaD1/Jyle4r0bhK63ytWWLBZG81nwdOGChPEvlK89ph/Uabuzfb89L8jVuZdph2povKdu0Q6kWvuq9Z8P9PvZY7X0Zb20fxMNXqNYXVflKTjskfAEAUB/TDhsIGzdbt0o33SQ94xnpl8t7zVejaYeSbdy0uuYrb+HxhY3+eOVLGv0te6uVr0bqNdw49dTalLxG99PqtMPklLR2tbPmKx62G3UJbFT5ioevRkL4WrCgFngeesgO81rzFR9XmilT7DUsqvKVDDHJL0Piz/nChaPHk6x8hX3QZZ12GG83n2zGERfuL950I61DYjx8JbsrSsWEr3D/ITj/9rfs+wsAgCTCVwMhYHzve7ZhkTV8tbqfr07CV3I/X3lsrGYRNvzSKl/S6HVfeYavtIYbU6fa4z/yyNpl6sky7TD+HHaz8tVKw412Kl9h2mH8tHpCWJg/34LArFmth69ma77iFbk0ztXCcZ7hq17DjXjFKlxOstfF+9HTQl/+cunLX5YOPth+T+5kua/Pxh+qvc2mHYZ2897XX+8l1b4IiK/7SmvSEf9Sot3wVa/VfKtrvsLlR0akBx5Ivw4AABMV4auBsHHzzW/a4dOeln65ePg6cMA2HrvRaj5eCUlWvqZMsYpFvfVieZs2zTY209Z8SemVr6zdDhtJm3YYNp7DVK4sDTe8H3vetm22gRmvQiTbkLcrNNyI32+zNV9hg75Z+Ip3wZTGtpqPn1ZPfNqhZBvk7Va+moWveo8jnJd3t8N6la9kBSn+eQyfyfi0wxe8YPQ4pVrlK9zGtGn2Hmo27VCqvV8bha9Gla94uOqk8tXXZ89R3tMOJaYeAgCQRPhqIGwUrVlj09rC1KOkePgKG0DtdjvMWvlKrvmSpG99S3rzm5vfbx6cG71Bl6x8FTntMLxOxx9vh83WfEnp06G2bRu93it+W3lUvkZGRoegZmu+wg6zW512GK8wtVP5kiz05B2+4qGwnrAmL1R6uhm+GlW+mo01rfIVLFjQfNqhVAtf9ZptSOmVr7Rph/EvJVoNX1Onpp8fhDBV77Wo13BDInwBAJBE+GogbNwMD9efcii1H76Sla/QWbFehajRmi9JevKTa9OiihDfYEtWvro57TBe+dq5s/XKl5QeEIaHx66ZmzTJnuM8wpc0OiQ1m3Yo1Xas3eq0w0mTLHRk6UAoWeVrzpzaeObOlR58cPTY49pZ89VK5evRR+1+O33epfoNN5LhK/5lSNbwFRpuxINQMnylTTuUah0P26181VvzlVap6uuzAJoM/5I9xrRdIcTHn7y9uHrTDiXCFwAASYSvBuIbnVnDV3KDrpHkAv/9+6060sq0w0Ybst2WrHxNmWLVwSlTRle+2ul2WE9a5Ss812edZaHjsMPqX79RQNi2Lb1hycyZ+YWv+NizhK9Q+Wq11Xy4bCvTDkOzCKk70w6bNdyQapWvxx7Lb/1ivTVfyS8F4p/HZuFr8mT7CdMO61W+vG9e+Wp1zVfW8JWsfEmj33+tVr6YdggAQOcIXw3EN27OOaf+5dLCVztrvppViJpVvoqWrHzNnGlT5Q46qPuVr7B2Kl51eNrTrFqzZEn96zdqClEvfOVRgakXvprdbqh8Ndo/VlrlK1y2lWmHYcqhZMEnXKdZ+ArPaacNN6TRla88phxK9acd1gtfWSpfkj3eMO0wWfkK7/+wzi8tuITKV6Nph+1Uvu691w7j+yTsNHxNnlz/b029bodHHEH4AgAgifDVQNi4OemkxtP5pk61DawDB9qrfLUTvtLWfBUtvuPg+E5dFy7s3pqv8LyG8JmsOjRrONKoOpO25kvKp/KV7A4pNV/zJY2edthKq/lw2U4qX0Ha6xZ/33Wj8vXoo/lVvuqFr2RFttXwFYJi8j14+OHSr39tfxPSqlBBaDffqPI1c6aNP0u3w/A5+9Wv7P4OOaR2fqfha/bs2q4cktKmHU6ebOGS8AUAwGiErwbCxk2jKYdSbQNt377O1nw1CynTp9u0urRuh2WIrxMJlS+pfuUrj7GG5ybcb7Lq0EyjaYdpa76k7la+8ph2mOx2mFflKzn2uClTamNvtdthlkCT57TDemu+ku/LVtZ8heulNdxYssRej4ceajxlb8YM6YQTpMWL69+Hc/Y8xCtfoTNl/IuC+HTcX/3Kgk88LGUJX41azdebciiNbj4Sv/yRRxK+AABI6nDXsePbQQdJL3uZ9JrXNL5cPHy1Mu0w2e2wWfhyrlYJqdqarx07at/uH3SQdPvttct1o/IVbnPnTunQQ7Nfv97UOO8br/mq961/Vp003LjvPhtDo2mHu3fbesFJk0ZXyVppuFEvfNV73WbMsPdiXjtZlkZXvsJ+2zrVauUry5qv+Fjjreal2nTCX/2q9jzWCy9r1jSedijZezJe+Xr4YRtrPHz19dVa5P/qV9Ipp4y+jXrha9Ik++kkfIUdTMcrX7Nn27TD++6z537yZOnnP7f7POWUzj9PAAD0KipfDUyeLF1xhXTmmY0vlxa+ulH5ksaGr7KnHcYbboQN0IULR1e+8m64IY2ufGV5roN6YWT3bnv90sLXokWjg0k72l3zNWtWtmmH8dtuteGG961PO4yfntdOlsN4w5qvohpuxPeVJ6Xv5ytNo8qXJN15Z+Nph5IFlGbvrWTla/Nm+4IjHmDCa7Bjh7RpUy0ABvXCV3h8WaYdNjJ9enr42r+/1rjlAx+wtbMjI41vCwCA8YzKVw7ymnaYJVDFp6E1u2y3Nap8PfKIbdQ7l/9OlqXabbY77TAZEMLGbdqar1Wr0nfK3Ip6G79ZKl9hSmejypdUW3cXD2pZph3u2mXvvXYqX1Ltdc1jJ8vTp9vjLWLNV6cNN+JrvuLvwWOOsWrQnXfWqrLNwksjc+eOrnyF8BUXHsMdd9jYWw1fzVrNNxv/jBljpx0ecYT9/pvf2Pqz666TnvWs0TsxBwBgoqHylYNOK1/JfYS1UvlqtvHeTcnKV7zhxv79tQ3G3bttozqPqUbJjchks4Nm6k2NGx62w7TK16GHNm5fn0Vaw4089/Ml1aaNtVr5CmuI2ql8TZ1a25jOYyfL8TVf3e522GnDjXqVr2nTbMrkr35VW4vXSfiaN29s5WvRotGXCY/h//7PDkMb+6CTytdjj2ULX/Fuh8nwtWmTdM890nnnNb4dAADGu1LCl3NuyDm3zDm3IuW8fufc0ugyl8VO3+qcW+Ocu6TY0TYXD1HdbDUvjQ5feQWadqW1mpdq38qHjochfOWh02mH9abGhY3btPCVh04abuzebT/1gkB8/29S44Yb3/++9O53j75+mCLaSsMNyV6L+Hs13FcnO1mePt023vfs6Z2GG8nKl2SVpyzTDrNIq3w1C19VmXYoWfi67jo7TvgCAEx0hYcv59yQJHnv10a/L0tc5EJJg9771dH5IaBd4L1f7r2/vLDBZlTkmq+wBiiErzI1ajUv1Tbqd+/OZ71XuM9wmyMjdpjntMNuh69kw40s+/mSrDpVL7Qk9wWV1mo+PN7Pf156z3tGr7vZvNkO4xv08eDTaP9O8dfVObvfTlrNT5tWe98U1Wo+uearlYYbIRQlP+9LlowOX0VWvvr6xjah6Xb4Spt2eNBB9nqG8HX44dbdEQCAiayMytdZkjZFxzdJWho/03u/ynu/Kvp1IHbZfufcQDFDbE27a74mT7YN1lYrX2HNV9nhq1GreWl05Suv8BWvfLXyXAf1ph02WvOVh3bXfIVAu3Nn/dASKlZh6mS88pWcdjg8bMFry5ba9RuFr2nTbP1SmmT4kmodANNkabgxfXrttSii4caUKbVpk+1MOwzPefILgCVL7P1///32e15rvvbvtyBeL3zddpvdd/I1yxK+du5MX9uYNXzFK19z5tgYDj/cdvp8/fVW9aLLIQBgoisjfPUnfl+YdqEoaG0JFTJJCyRtcc6trHP5Fc659c659ZvD1mRB4tOVWpl2KNkGX7vTDstsMy/ZBtu+fbZh26jytWdPdypfrVQZg3rTDhut+cpDu9MO4xu99YJACIxh7VajhhvhMqEDnVQLX/EdiYfg0+h1Swtf06Z1XvkKur3mK/mlQFr4avT6TJtWe98k34Nh2t+tt9phK9XZpHnz7DO0Z0/tM5VsuBHu//77x673ip/fKHyNjIxdGxh2FN3OtEPJph6uXWtBlCmHAAB0qdthNLVwQeLkTVGQGk45L82Q9/7i8Euohjnnhp1zQ2FaYuL8VZI0ODjYYW+61iSnHU6enH2nvPHwlaUle9WmHUq2Rmfv3mIqX/GNyDCFrxemHU6ebPfdSfiqF1pC+ApBoFHDjXCZ+PcTmzdbRSKt4Uaz92JyLVOj8JW18pUcQ6fqrflKfinQTuUrhNm0ypck/eQn9p7tpMNfeB4efTS9SimNfhzJ9V5StvAVzo+/z8L03qyVr2RYO+IIW2coEb4AAJC6FL6SwShhnWrVrwFJa5IXiMLV5dHxpZIGJa333m/Meai5SIavVioxU6a03u1wx45qha/wbXzYEJ83zzY2u7HmKz7tsJ3KV6Nph2En1t0SX1fjvVVjsuznK8gavho13KhX+Vq4cHRACBv8jZ7bd7/bgndc6FaYptXKVxGt5uOfofjnOGv4CqEu+TwNRBOk7757bFBqVfhCYNu29sPXtGn2/m7Ual6y8+NfQGRdszZjhr3/kmEtNN044QSbgggAwERX+LTDKJgNRI02+mONN9ZEh8skXeac2+Cc2yCrkl0VnTcUu43KSK75ajV8JacdNgpVs2fbxu2OHdUJX6HCFTbgnLMF//fea7/n2e2wW9MOt22zjc5665vyEA9f4TXPo/I1fbrddghWjRpupIWvhx4auzE/fbpdt1FofsITpKc9bfRpeVa+8pp2WG/NVx6VryBZ+ZozR1q82I530ulQyqfy5dzYphr1Kl9xWcNXmHYYwngyfFH1AgDAlLKXqFjHwrWx05ZHh2slLUm52sbop1LBSxpb+WqlytPqmq+wIdeo+11RkuErvpF50knS7bfb8d2789uQjle+8p522K0ph0G8O2TW8BV/ThsFgf7+5g03vK8/7TCtOjNnTusVy0YNN6pe+eokfKV9AbBkifTgg51XU+OVr/BZazV8hTF2K3yFbofJyx95pB0SvgAAMOxkOQedTDucOnV0+Jo0qfEGedioeeSR6la+JOnkky18ed+daYftVr7qha/h4WLCVzcqX5J1PExruBE6au7da/cdHndy2mG82UYwd27rr1tV13y123Cj2Q6hg7QvAMK6r07DV1rla0Fi1Wx4HNOn16pNSd0OX7t3j738c58rffSj0gte0Pj6AABMFISvHOQ57XDGjMbtmKsYvpJrviSrfO3YIf361/l2O3TObiu+5qudylfamq9uh694a/7wmmfdz5fUOHzFK1/xhhvhevv21c6XslW+5s5t7b0c7qvRTpada9x8Ij7uTqfrBd1suBHUq3xJnT+O5Jqv+fPHjis8jrQ28/ExNgtf8f3QSa1POwyXD5Xu6dOl172u+ZcMAABMFPxLzEGe0w6bXTc+7bDs8BVCT9q0w5NPtsOf/SzfypdUC19hQ7HVsDtpUvq0wzBFqlviG79ZWplL2acdzp8vPfCAHY9XvsL19u6tVcakWuVr/37b51da+Hr2s1tvkd6s1Xxo/FBPeE+H/UTlIeu0wxAMW2m4EaQ9T2H6X96Vr7TXKnwG6k05DJdJhq9kY5W8px0CAIDRCF85yKvbYZYKUdio8b46a75C5Ss57VDqTvjq62t/2qGUHhC2bZNOOSWf8dXT1zd6Z7lS8/DV12ehoNnr3d9fW2OXVvmKh6+5c2vh65FH7LbTNugvu6zpQxpj+vSx1ZMgGQrThHHnNeVQatxwI7kWMXwZkmflK6/wFSpfaa9VqJi3Gr66Pe0QAACMxrTDHHQavlqpfMU3asqufDVquLFwoW0k3n57vt0Opc6mHUrpU+OKWvPVasONSZNqjy/rtMN4w41wvfi0wxNOqE07rNc9r11ZKl+NhPdJnuEra+VLai18ZV3z1em0w+nT7adR5WvyZOkzn5H+8i/r306j8BVvNR/XyrTDfftq+8sjfAEAkI7wlYNO1nzNnTt6f1jNQkp8Q65q4Su5AXryyd2tfLUz7VAa25HPe9uwLaPhRpadcYcN2WbTDoeH7bE0m3Z4/PH2ntu/vxa+0hputKNZw42sla+8umNKtfDlE7teT3tfhvCV5fVpVvk66CDpmGOkxz2urWGPMm9erdth2Il50stfLh19dP3baKfytWOHHTYLkOF5DH/LCF8AAKRj2mEOOlnz9fjHS1/5ih0fT5UvycLX5z+fb8MNaWzlq9Nphzt22MZ52Flxt8QbbmRd8yXZa/7gg80rXyMjtYDVqPJ1/PF2+Mgj3al8NdrJclmVL8men3izj7T35dSpo9d8NXp9wlidS39czkn/93/5fE7nzq2Fr3Zfq3bCV9bPWHgew98CwhcAAOmofOWgk2mHp55qG8APPth6+Krymi/JwtfwsG305l35Cg03Jk1q/XlIBoQwVaqKrealWqhtFr6k2lquRpWv446rXTZcvohph2Wu+ZLGTj1sNu1wypRszUFmzqx/uVmz8un0N2+e7bR8//5iw9fu3RZYmz2G8Fw8/HBth84AAGAswlcOOpl2eOqpdviTn7TW7VCqTuXrkUdqLeDjTjqpdrxbDTdCQ4pWJANCqAgVGb6y7PMqCIG72X6+pFqYSmu4MTxst3XYYbXLhsrXwoWZHkJTzXaynLXylfe0Q2ls+EqrfMXDV7PXJoy1iKAxd6505512vJvhK9ksJeuU4fi0w9mzW/9MAgAwURC+ctDJtMMQvm67LduGzvTptY3JssPX9Om2kbV/f/q3/6HjYbhsXuLTDtvZ8E0GhCIrXzt32tqje+6x07K0tw+Bu1EYSFa+0qYdbt1qIS2s79q82X4WLMhvP0xVrHzVC1/NKl9Zx9pqw5d2zJ1r1XGpO+Fr6lR7ntIqX1k+Y/Fph3kGZwAAxhvCVw7CBszeva0HgkWLpEMOsfCVZW2Uc7VKSNnhKz69KG1B/iGH1EJBtxputLPhmwwIIXx1e81XeK727JF++Us7HqYANtJK5StUsupNO+zvr4WvUPnKq9lGGGOjnSyXueYrvqNl73ur8hX/YqBew41mGoWvtPOl7F8mhedi82bWewEA0AjhKwehahA6g7W6MXbqqdmnHUrZ1gAVJTzWtBDkXK361a2GG+1s+Ja15ivezvuXv5QWL84WMrKErxAcQ3WkXsON+fPtZ/LkWvjKa71XuK9G0w7L6nYoja58hTE2ariRNXwVVfkKOql87dljazClsY8x3hAmaHXa4cMPE74AAGiE8JWDyZMtaIQd6LYTvn72M9unTpYNnapUvqTGlS+ptu6rGw038pp2WOSaL6kWvkLXwWbamXbYqPI1aZJVTzZvtssXFb7KqnylNdzYvXv0/cUvW/XKVyfhS6o99iyVr1bD19athC8AABohfOVk6lTpscfseKtB49RTbSPn/vuzBapeCl/dqHx1a9phVcNXlsrXvHn2BUCzhhtheuLBB3en8jV9ulVW0nZqnKXhxuLF9njjzVo6lVb5CpXPPKYdFln5mjWr/bAXHuuuXTbt8sCBbOGrlTVfIyOELwAAGmE/XzmJh69WN45OO612vJVph1UKX/U2QE85xQ7z3CDrxrTDKVO6vxEdxvrAAzY9sNXKV6PgMmmSbaCnVb6SDTckC1wPPGDd6fKufEn2/Cafz717mwfc+fOtgpxnt7y08JWl8tWsCUl4rEVWvjp5reLhP20/Znms+ZIIXwAANELlKydTp7Y/7fCkk2zjWWpt2mGV1nzVq3wtWyZdeaX09Kfne59799oau7y6HYbKUTeFsd56qx22WvlqVonp76813Ii/N6ZOtSrhY4/VpicefLD0859bBSTvhhtS+tTDLJUvKf/XIa3hRghfaWu+qlz5arfZhpQevpKVr05bzUuELwAAGiF85aSTaYd9fbWud7225itseNbbAJ00SbrwwtoGcB7i60vymnbY7SmHUm3j98c/tsOs4evEE2uNMhqZP79+w42HH65dRrIKSljr1o3KV1r4ytJqvhtanXbYasONXq585b3mS6LVPAAAjRC+ctJJ5Uuq7e+rV6cd1qt8dfM+H3mk/WmHyYYbRYSvEBRvvdWqO0uWZLve854nbdnS/Dnu77dAKo1tuBGCVnzNV5D3mi+ps8pX3rrdcKPIylc3w1e9bodZPmNMOwQAIBvCV046DV9h3VevVb6arfnq5n3u2NF+5Su55qvb+/iSauO+7TbpmGOyv35Zp+HFK2PJylcQn3YYUPmqaWcny71W+dq9O3vlK+uaL6YdAgCQDeErJ/Hw1U5nv1YqX7205qsb4s9Rnmu+ui1eecg65bAV8QCZbLgRxKcdBt1quJGUpdV8NzRa80Xla/T5eUw7JHwBAFAf4SsnU6fafrqk9gLBmWfabRxySPPLVnHaYRmVr+TxrNKmHRZZ+ZK6H76SDTeSl4lXvjpp4pDUrOFG1StfrexkeeZMC0WHHZbfWOs57DBpaEg6//z2b6Pd8MW0QwAA8kOr+ZwkN2JadeSR0l13SYce2vyyVZx2WFblK49ph+MlfGWZdphc8zV/fr6BKLwPQvOZuLIqX43WfNWbdrh/f/PnZepU6xiZZ3htdF9XX93ZbXSz8jVlioXcAwcIXwAANELlKyedhi9JOvzwWsv5RqoYvnqp8jV9um1cj4zY4WOPNe8kmIf4c1TktMP48eS0wzynHMZvL7S8jyur4Ua7+/nKEkoPPbScal474uErTMFMazXvvf0+MmJfUmSdRh0uR/gCAKA+wldO4hsx7az5asVRR9lGYhHfuDcTAkUZ3Q6Tx7MKAWDfvto6vSIqX/H3RZHTDsPxqVNrz9e8efZ73uErVNTSwtd4a7jRa7JUvkZGaufVe47qCUGW8AUAQH2lhC/n3JBzbplzbkWd87c659Y45y7Jep2y5VH5ymr5cunXv842RbHbenXaoWQbl6E1exHha9Ik20CdPt2mmeYtXr1La7gxf36tc6JzFpSKqnwdOGAb9uOp4UavydJqPpwv1Z6jrH/PwueS/XwBAFBf4eHLOTckSd77tdHvy1IudoH3frn3/vIWrlOq+EZMt6cDOpetMUcRenXaoWSVmLD/qyLCl2TjXbIk351OB80abiQf42WXSW9+c75jmD7dNr4femj06Wkb+0XpVsONXpOl8hXOl+qvi6uHaYcAADRXRuXrLEmbouObJC1NuUy/c26gxeuUKmzEzJiRfb9M40HZla9Oph2WEb5mzpSOO647t52l8hX3spdJ55yT/zgOPnhs5Sts7PdKw43xGL4mT7bHlDV8hUOmHQIAkJ8yuh32J35fmHKZBZK2OOdWeu8vznKdaDriCkk66qijOh9li8JGTBE7XK2SsitfnUw7LCN8feADVvnqhmaVryKaikg29TAZvkLr+apUvkL4SobBEL4mTRp/4UuqdTSk8gUAQDm6Er6iaYILEidviqYNDqecN4r3flV0O8PRbWW9zipJGhwc9G0NvAMTNXyddpp07LHSwEDzy+Ylr4Ybe/bUwldRweSlL+3ebTfbyXJRAXPRIumee0afFsJXVdZ87dljlZpklTqEL+cIX+2s+XJu4v0NBACgFV0JX9771Q3OXqdaJWtA0pr4mVEFa733fmPW61TBRA1fp5wi3XFHsffZ6bTDMtd8ddPMmfY+HBkZvcuCetMOu+Xgg6X160efVrU1X7t3p6/NDGu+wvHxJkv42rnTDlutfE2fbtOPs+wuAwCAiarwf5NRMBuImmb0x5pohEB1VfR7aLKxut51qiSsK+l2m3lYmAgVi06nHW7dahuL42GqlHMWIpPVpXoNN7olTDv0sfpzFSpfyYYbaZ/V8bzmS2qt8tXqmq8ZM8bH5wgAgG4qY82XQhdDSWtjpy2PDoclbYx+Vje6TpVM1MpXGZyzDb1du/KZdtjfP36apPT31zr5BUVXvhYtsgAzPFy7zyo23GgUvryfeOGrXqt5whcAAPkpJXyNR4SvYoWNyDymHY6HKYfB/Pm1fZcFZUw7lKz6Fe6zig030qYdxoPalHH417Gba75WrJB++9t8xgkAwHg1DjcvyhFvNY/u66TLYrLb4XgKX1WZdijZvr6OP96Ol1n5qtdwo17lKxivla8dO7rT7fA5z8lnjAAAjGcsjc4Jla9ihQ3CdsJuctphURWhIsyfPzbghMd32GHFjCGEr3i7+V6pfMXHN17DV7fWfAEAgOaofOWE8FWsvj7bKGyns1py2uGJJ+Y6tFL91V+NbfN+5pnSD38oPfGJxYwhPu0wqNqar4lc+erWfr4AAEBzhK+cEL6KNWNG+891stvheJp2ePbZ9hPnnPSkJxU3hvi0w6CKla+0KauEr/bXfAEAgOaYdpgT1nwVq68vn/A13tZ8VcH06dLcuenTDquy5qtZww1p4oWvqVPtuWp3P18AAKA5wldOqHwVq6+vvWYbUi0AbN9uG5qEr/yFfX0FVdvJcr1phxN5zZdkn6n4mq/Jk8dn10cAAMrCv9WcEL6KtXChtG1be9cNFY8wLY7wlb9Fi9KnHVZlJ8tUvuz38Nwkz5fq7wsNAAC0j/CVE6YdFutDH6ptJLYqBIAQDsZTt8OqOPhg6e67a7/TcKMa+vrsedi1yx5fcufiyfDFl0kAAOSL8JUTKl/FOvTQ9q8bAsCDD9ohla/8LVokrVtX+72KDTcmaviSpEcfTX98VL4AAOgu1nzlhPDVOwhf3RfWfHlvv1dtJ8sTedqhlC187dpF+AIAIG+Er5wQvnrHpEm2kc2ar+45+GALO8PD9nvVKl8TueGGROULAICyEL5ywpqv3jJtGuGrm8K+vkLHwyqt+RoZsfFQ+Uo/P95qni+TAADIF+ErJ1S+esv06bYBKhG+uiG5o+UqVb727LFD1nyNPT/eap7KFwAA+SN85YTw1VtCBWbq1Pb3F4b6Dj7YDkPlq8xW887ZTwhfjXYePNHDF2u+AADoLsJXTph22FtCCOjvH9tuG52rN+0wuV+pokyeXGu4ESpfTDtMP5/KFwAA3UP4ykmYurZwYanDQEbx8IX8pU07nDatvKA7eXK2ytdEabixbVu28EUlHwCAfBG+cvKMZ0g//KF06qlljwRZhKoH4as7pk+X5s4dXfkqY8phMGXK2PA1kStfBw5Q+QIAoAyEr5w4Jz3pSWWPAllR+eq+sK8vySpfZYaZeOWLhhumUfjynjVfAAB0A+ELExLhq/sWLapNOyy78hVf85W14Ub8+HiRJXwdOGCvF5UvAADyR/jChBSmnM2fX+44xrODD6525Stt2uFEWfMl1W81L1nVizVfAADkj/CFCYnKV/cdeqj061/bFLYqVL5oNZ+t8iXZjpb37KHyBQBA3ghfmJAIX913xhnS1q3SXXeVX/mi4YaJd5xsFL6Gh+2Q8AUAQL4IX5iQ6HbYfWefbYc/+lGt1XxZaLhhnKsFrEbha8sWOyR8AQCQr1LCl3NuyDm3zDm3IuW8pc65O51zG6Kfy6LTtzrn1jjnLil+xBhvqHx13ymn2Mb8j35k0w7LXvPVasON8Ri+pGzha+vW0b8DAIB8FN7Pyzk3JEne+7XOuRXOuWXe+7Wxiyzw3i+JLrtU0nB0+gWJywFtI3x139Sp0tKl0s03S7NmVa/yNREbbkhUvgAAKFMZla+zJG2Kjm+StDR+ZiJgDXjvw2X7nXMDBYwPEwDhqxhnny1t3GgNHKq2k+Vmla/Jk7s/rjIQvgAAKE8Z4as/8fvCtAs551Z471fHTlogaYtzbmW9yzvn1jvn1m8O/a2BOmg1X4yzz7awc+ut5U87bKXhxtSptcYU402j8BVazYdph4QvAADy1ZVph9HUwgWJkzdFVa3hlPPSLJe0KvzivV8V3fawc24oEczC+askaXBw0Lc/ekwEVL6KEZpubN9e/rTDsOYrS8ON8TrlUGLNFwAAZepK+EoGo4R1qlW/BiStSV7AOdef+H2FpPXe+405DRETHOGrGI97nHTQQdLDD1er8jVp0ugphgHhyw6pfAEA0B2FTzuMgtmAc26ZpP6wxss5Fw9hCyRtif1+VXSZodhtAG075RTbDxUbl93lXK36VXblK95wY/r09GmFYZ3XRA9frPkCAKA7Smk1772/3Hu/1nt/eey05bHjm7z3F8d+H/beb/Ter/beX1r0eDH+/MmfWCMIdF8IX1XayXK9UOGcXXaihy8qXwAAdAc7WQbQVVWrfO3end5sIyB8seYLAIBuIXwB6KqzzrLDstd8xRtuNKroTOTwNXWqPX6mHQIA0B2ELwBdddBB0tCQ9JSnlDeGZOWrUaiYOnXihq9wPuELAIDu6Eq3QwCIu/rqcu9/yhRp1y47HhpuNLrsRA9fjz1mxwlfAADki8oXgHGvlcoX4Sv9OAAA6BzhC8C4F1/zRcMNO2wWviZPTt8XGgAAaB/hC8C4l9zPV7PK13gOHVnDF1MOAQDIH+ELwLhHw42a8NgJXwAAFI/wBWDci+9kmYYbdljvMc6cOfpyAAAgP4QvAOMeDTdqmHYIAEB5xvHKBgAwoeHGrbdK99wjvfzl9S/7qldJCxcWN7aiEb4AACgP4QvAuBcqX296kzR/vvTmN9e/7BvfWNiwSkH4AgCgPIQvAOPe5MnSvffaz7//uwWwiWrJEmnuXGlgIP38EL5Y8wUAQP4IXwDGvdA6/rTTpIsuKncsZTv6aGnbtvrnU/kCAKB7aLgBYNwL4evDH7YqGOojfAEA0D1UvgCMe696lXTKKdKznlX2SP5/e/eX28QVxQH4HMECrNC+UslIXUAwOwgSC4hYQtgAqtQVVJQVkCWU7CDpCpKwAyz1uRXyDm4ffCc4xuFf4hnP5fukKDPXBp2frIzmeO7c2X3dUvOaLwC4e5ovoHmz2fKHL3PPFwBsj2mHAFwx7RAAtkfzBcAVzRcAbI/mC4Armi8A2B7NFwBX3PMFANuj+QLgiitfALA9mi8ArlhqHgC2Z5DmKzMPM/P0C68fZObR58YAuFuufAHA9gzSfJVSTm56LTMP63vO6v7BprE+6gT40Wi+AGB7dnHa4ZOImNfteUTs3zAGwB379deIly8jnj0buhIAaM/9oQvYYLK2/+CGsWvqdMSjiIiHDx9uoy6A5t27F/H69dBVAECbttJ81WmCe2vD827a4BcsNvzbTWPXlFKOI+I4ImI2m5WvKhQAAKAnW2m+PndP11c4j49XuqYRcVr318cAAABGY6jVDg8iYtYtpFHHTiOuGrdpfc+klHK2aWyIugEAAL5XltLeDL3ZbFYuLi6GLgOAO5aZl6WU2dB1AMD32MXVDgEAAJqj+QIAAOiB5gsAAKAHmi8AAIAeaL4AAAB6oPkCAADogeYLAACgB00+5ysz/42If2753/wUEf/dQTm7Rq5xkWs8WswUsXu5fiml/Dx0EQDwPZpsvu5CZl60+CBPucZFrvFoMVNEu7kAYAimHQIAAPRA8wUAANADzdfNjocuYEvkGhe5xqPFTBHt5gKA3rnnCwAAoAeufAEAAPRA8wUAANADzdeazDzMzIPMPBq6ltvKzElm7tdMr1bGW8rYVK6Vz+twZayFXJ9kGGuuWvfphrFR51vP9SMcPwCgb5qvFd0JbynlrO4fDFvRrT2PiFkp5SQiIjOPWspYa5/W7VZy/V4/r73MnLaQq9Y8rxnm3Ql9xDhzdX9PnU1ZxphvPVc0fvwAgCFovq57EhHzuj2PiP0Ba7m1UspxKaVbqWway0xNZMzMLk9n9LnqlYTzzJzWz66Vz+siIt5m5n5ETEsp76KNXJ1NWUafr+XjBwAMRfN13WRt/8EQRdy12qh8qN9WT9ZeHmvGaW1OOpO118eY61Es6/6QmW8ycxIN5CqlLCLiTUS8jYjHdXiy9rbR5VoxWdt/cMPYKDV6/ACAQWi+rltExN7QRWzBYSnlRd1exMgzZuZBN+1pxSJGnqt6X5uVy4g4igZy1alpZ6WURxGxqFPXFjHyXCsW8WmWTWNj1dTxAwCGpPm67jw+frM7jYjTm986Dpl5WEr5s27vRxsZP6zcVzNtKNf5yvYklie6LeTar1MNIyL+iOXJewu5OpuyNJGv0eMHAAxG87Wi3lg+rd/UTzZcXRmVmuNVZl5m5mVE7LWQsZTyrta9F/VEsJFcJxEx6RYxqPfcjD5XRBzXxRoOIuL52HPVmmcri098kmWM+dZztXr8AIAhZSll6BoAAACa58oXAABADzRfAAAAPdB8AQAA9EDzBQAA0APNF+ywzJxkZsnM9/XnslsNMTMPM/O3oWsEAODrWO0QdlhmTiLi71LK47rfPVvpcX0YMwAAI3F/6AKAr1dKmWfmSUQ8z8x5LB90O4+IF7F85tk0Il6t7D8tpcyHqRYAgFWmHcL4nEfE41g2V5M6Ni2lPI3aeNUrZV0TBgDADtB8wTgt1vbP6u/52vakp3oAAPgCzReMz5OIeD90EQAAfBvNF4xIXXDjMCL+GroWAAC+jQU3YPftZ+ZlLKcQLmJ5T9ciMwctCgCAb2OpeQAAgB6YdggAANADzRcAAEAPNF8AAAA90HwBAAD0QPMFAADQA80XAABADzRfAAAAPdB8AQAA9OB/GpyfvVDxlKAAAAAASUVORK5CYII=",
+ "text/plain": [
+ "<Figure size 864x1296 with 8 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# LACE animation\n",
+ "\n",
+ "fig, axs = plt.subplots(4, 2)\n",
+ "frame_duration=40\n",
+ "fig.set_size_inches(12, 18)\n",
+ "axs[0, 0].set_xlabel('Tap')\n",
+ "axs[0, 0].set_ylabel('Amplitude')\n",
+ "axs[0, 1].set_xlabel('Frequency (kHz)')\n",
+ "axs[0, 1].set_ylabel('Amplitude (dB)')\n",
+ "axs[1, 0].set_xlabel('Tap')\n",
+ "axs[1, 0].set_ylabel('Amplitude')\n",
+ "axs[1, 1].set_xlabel('Frequency (kHz)')\n",
+ "axs[1, 1].set_ylabel('Amplitude (dB)')\n",
+ "axs[2, 0].set_xlabel('Tap')\n",
+ "axs[2, 0].set_ylabel('Amplitude')\n",
+ "axs[2, 1].set_xlabel('Frequency (kHz)')\n",
+ "axs[2, 1].set_ylabel('Amplitude (dB)')\n",
+ "axs[3, 0].set_xlabel('Dim')\n",
+ "axs[3, 1].set_visible(False)\n",
+ "fig.tight_layout()\n",
+ "\n",
+ "frames = []\n",
+ "for i in range(12800, 14000, 4):\n",
+ " f = impz_comb1[i]\n",
+ " w, h = scipy.signal.freqz(f, fs=16000)\n",
+ " frame = axs[0, 0].plot(f, 'b') + axs[0, 1].plot(w/1000, 10 * np.log10(np.abs(h)), 'b')\n",
+ " \n",
+ " f = impz_comb2[i]\n",
+ " w, h = scipy.signal.freqz(f, fs=16000)\n",
+ " frame += axs[1, 0].plot(f, 'b') + axs[1, 1].plot(w/1000, 10 * np.log10(np.abs(h)), 'b')\n",
+ " \n",
+ " f = impz_conv1[i]\n",
+ " w, h = scipy.signal.freqz(f, fs=16000)\n",
+ " frame += axs[2, 0].plot(f, 'b') + axs[2, 1].plot(w/1000, 10 * np.log10(np.abs(h)), 'b')\n",
+ " \n",
+ " frame += axs[3, 0].plot(phi[i//80], 'b')\n",
+ " \n",
+ " frames.append(frame)\n",
+ " \n",
+ "ani = matplotlib.animation.ArtistAnimation(fig, frames, blit=True, interval=frame_duration)\n",
+ "ani.save('lace_demo/responses2.mp4', dpi=720)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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Ek1E2mHzkNqcErtK91MooIosp/nCQtiFS78WhE08ZnKIiIiIKIrc1tGVzg1y5xj5Or+mipphjRcY4JSAQQ4RERERRYqzUGYKlrWqqETLeX4H/OkWV1VOw4SpyD+3ivJgkENEREREkeBMvFdYa2nAp5N7d45pgfbcUTULFugxi1OiAWEPgIhC0w6r8eUkRiKieOiGnfQn+e92d8atLx3eMpyMEtbMiYiFYEe1srDxABERERERJQrL1YiIiIiIKFEY5BARERERUaIwyCEiIiIiokRhkENERERERInCIIeIiIiIiBKFQQ4RERERESUKgxwiIiIiIkoUBjlERERERJQoDHKIiIiIiChRGOQQEREREVGiMMghIiIiIqJEYZBDRERERESJwiCHiIiIiIgShUEOERERERElCoMcIiIiIiJKFAY5RERERESUKAxyiIiIiIgoURjkEBERERFRojDIISIiIiKiRGGQQ0REREREicIgh4iIiIiIEoVBDhERERERJQqDHCIiIiIiShQGOURERERElCgMcoiIiIiIKFEY5BARERERUaIwyCEiIiIiokRhkENERERERInCIIeIiIiIiBKFQQ4RERERESUKgxwiIiIiIkoUBjlERERERJQoDHKIiIiIiChRGOQQEREREVGiDAh7AGR23XVXHTNmTNjDICLyZOnSpW+r6uiwx0H+4+cREcVFoc8iBjkRMWbMGCxZsiTsYRAReSIiL4c9BgoGP4+IKC4KfRaxXI2IiIiIiBKFQQ4RERERESUKgxwiIiIiIkoUzslxiEgTgBSAHgDjACxW1fll7KcFwCgADc5d7eXsh4iIiIiIysMgB4CIzAKwWlXbMu8TkfrM+zzuZ6aq9jg/NwBYJCLTVbXR73ETEREREVFfNR/kiEg9gCZVHZl5v6q2ishaEelwg5Yi+2mCZW3e31ZVu0SkEcBCEZnKjA4RERERUfA4JwdoBtCZ57FuAE0e9zNOVbuz71TVTlgJXHNZoyMiIiIiopIwyAEmw4KZXLoBTPG4nxYRmZfnsSUAJpU6sLCtXw9ceCGwdm3YIyEioiR7+mngu98FzjoLOPVU4OKLgblzgS1bwh4ZEcUVgxxrELA6z2Pd8B6cdAJYk+exugKPRdaSJcDPfw489ljYIyEioiTq7gbOPBM47DDge98DnnsOWLMGuOMO4Nxzgf33B373u7BH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OHWI/sWpuph7Tyq8xRiKDHrm5Ik5YGGgrLDwABERERERRQqnqxERERERUaQwySEiIiIiokhhkkNERERERJHCJIeIiIiIiCKFSQ4REREREUUKkxwiIiIiIooUJjlERERERBQpTHKIiIiIiChSmOQQEREREVGkMMkhIiIiIqJIYZJDRERERESRwiSHiIiIiIgihUkOERERERFFCpMcIiIiIiKKFCY5REREREQUKUxyiIiIiIgoUpjkEBERERFRpDDJISIiIiKiSGGSQ0REREREkcIkh4iIiIiIIoVJDhERERERRQqTHCIiIiIiihQmOUREREREFClMcoiIiIiIKFKY5BARERERUaQwySEiIiIiokhhkkNERERERJHCJIeIiIiIiCKFSQ4REREREUUKkxwiIiIiIooUJjlERERERBQpTHKIiIiIiChSmOQQEREREVGktAg7ADIdOnTQLl26hB0GEVFaampqPlfVjmHHQd7j8YiICkWyYxGTnDzRpUsXLFq0KOwwiIjSIiLvhR0D+YPHIyIqFMmORZyuRkREREREkcIkh4iIiIiIIoVJDhERERERRQqTHCIiIiIiihQmOUREREREFClMcoiIiIiIKFKY5BARERERUaQwySlgq1cDS5eGHQUREREReWnDBuDtt4H33w87ksLFJKeAnXwycOGFYUdBRERERF75+9+B3XYD9trL7vfbD3j99bCjKjxMcgrYwIHAokXApk1hR0JEREREubr8cuCcc4Du3YG77gImTwbq6uyc77nnwo6usDDJKWADBwJffw289VbYkRARERFRLu65Bxg/HjjvPODZZ4EzzwQuuQRYsgTo2hU49lhg+fKwoywcTHIK2KBBdv/qq+HGQURERETZ++QT4IILgIMPBv76V6B584bnOnUCnn4aaNECOOMMYOPG8OIsJExyClj37kBJCZMcIiIiokJWVQV89x0wbRrQsuWWz++yC3DrrXbOd+utwcdXiJjkFDARm7L2yithR0JERERE2Xj7beCf/wR++1tg990TbzdqFDBkCPCnPwH19YGFV7CY5BS4QYOAN96wtTlEREREVFgmTABatwYuvjj5diJWiGDtWuDmm4OJrZAxySlwAwdadbXFi8OOhIiIiIgy8emnwP33W7GBjh1Tb19RAYwYAdxyCy9wp8Ikp8ANHGj3nLJGREREVFjuvNMKCfziF+m/ZuxYG825+27/4ooCJjkF7ic/Abp0YfEBIiIiokKiCtxxB3DQQUB5efqv239/G9GZOtX2QfExyYmAgQOZ5BAREREVkpoaYOVK64eTqfPOA5YutX1QfExyImDgQGD1auCzz8KOhIiIiIjSMXOm9b457rjMX3vKKcDWW9tIEMXHJCcC2BSUiIgK0X//aydpV1xhDRA/+ijsiIiCoQrMmmUloUtLM39927ZWUvq++4BvvvE+vihgkhMBfftaZ1wmOUREVAi+/Ra49FJgt91s2s0111i39y5drAfIpk1hR0jkr9deA959Fxg5Mvt9nHEG8NVXwJNPehdXlDDJiYBttwX23psV1oiIKP998oktnL7uOuC004DXX7ekZvlyuzL9+98D554LbN4cdqRE/pk1C2jWLLupaq6DDrICVNOnexZWpLQIOwDyxqBB9kuuas2iiIiI8s2qVTY9Z80a4NFHrd+Ha489rOv7HnsAf/gD0L07cPnloYVK5Kunnwb23Te93jiJtGgBnHAC8I9/2JS1bbbxLr4o4EhORAwcCNTX29AnERFRvlmzBjjsMGD9euC55xonOLGuugo4+WQb0Vm0KNAQiQKxbp39bg8blvu+KistweGUtS0xyYkINgUlIqJ89fXXltR88IGN4PTvn3hbEeC22+wK929+wz4gFD3PPmvTMYcPz31f7pS1mTNz31fUMMmJiL32srU5LD5ARET5ZONGW2uzaBHwwAO2HieVkhLg6quBl17iyRtFz9y5wHbbNVTHzUWLFnYB4amngA0bct9flDDJiYjmze3KGJMcIiLKF6rAr38NPPEEcOutwLHHpv/as84CevQAxo/naA5Fy5w5wCGHAC1berO/o46yJQsLFnizv6hgkhMhAwcCixcD338fdiRERBQWEakSkYkiMse55VCkNjfXXQfcfjtQXQ2MGZPZa5s3By65BFiyBJg3z5fwiAL33nu2ftqL9Tiu4cMtYXr8ce/2GQVMciJk0CDghx+s9joRERUfEZkIYKqqVqvqcADVAKaJyIygY3nwQaCqyqaqjR+f3T5OP93WG/z1r97GRtHy3/9an6XddgPat7eT/jlzwo4qvhdftPuDD/Zun9tvb/t77DHv9hkFTHIixC0+wClrRETFR0RGA5iiqvXuY6paC6ASwMggR3SmT7ceOAccANx1l/UDyUbr1tbw8LHH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BM6Q+TAAw724WkdERP6KmYJc61zAijQRmQJbhzTJmWKX636SJVR5yym+MAd24dKrhrCRx5EcIiIiIspHbunoXNcGuWuMPF1fE6AxsMILTHAywCSHiIiIiPKK0yizBDZqldMULef1U+FNtbhAOQUTRiLx2ipKgEkOEREREeUFZ+G9wkpLAx6d3LtrTJOU585XE2GJHkdxMtQi7ACIKDRTYHN8uYiRiKgw1MFO+qP8uV0Xc+9JhbcYQ5FBz5w8MQesqJYVFh4gIiIiIqJI4XQ1IiIiIiKKFCY5REREREQUKUxyiIiIiIgoUpjkEBERERFRpDDJISIiIiKiSGGSQ0REREREkcIkh4iIiIiIIoVJDhERERERRQqTHCIiIiIiihQmOUREREREFClMcoiIiIiIKFKY5BARERERUaQwySEiIiIiokhhkkNERERERJHCJIeIiIiIiCKFSQ4REREREUUKkxwiIiIiIooUJjlERERERBQpTHKIiIiIiChSmOQQEREREVGkMMkhIiIiIqJIYZJDRERERESRwiSHiIiIiIgihUkOERERERFFCpMcIiIiIiKKFCY5REREREQUKUxyiIiIiIgoUpjkEBERERFRpDDJISIiIiKiSGGSQ0REREREkcIkh4iIiIiIIoVJDhERERERRQqTHCIiIiIiihQmOUREREREFCktwg6ATIcOHbRLly5hh0FElJaamprPVbVj2HGQ93g8IqJCkexYxCQnT3Tp0gWLFi0KOwwiorSIyHthx0D+4PGIiApFsmMRp6sREREREVGkMMkhIiIiIqJIYZJDRERERESRwiSHiIiIiIgihUkOERERERFFCpMcIiIiIiKKFCY5REREREQUKeyTQ43cfTew3XbAiSeGHQkREUXJxx8DDz0E1NQA69cDnToBQ4YAxxwDtGoVdnREFDVMcqiRs86ye9VQwyAiooh46y3gyiuBRx4BNm2y5KZ9e2DePOC224CyMmDKFGDYsLAjJaIo4XQ1iuujj8KOgIiICtmHHwLnnAP06mUJzSWXAMuWAZ98ArzxBrB2LfDoo8BWWwGHHQbcemvYERNRlDDJoR/Fjt48+2x4cRARUeFatw4YOxbYfXfg3nuBiy4CVq4Err0W6NGjYbsWLYARI4CFC+3+V7+yKdNERF7gdDX60ZdfNny9dClw+unhxUJERIVh7VrgvfeAujrg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",
+ "text/plain": [
+ "<Figure size 864x1296 with 8 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# static plot for Jean-Marc's on-site presentation\n",
+ "\n",
+ "fig, axs = plt.subplots(4, 2)\n",
+ "frame_duration=40\n",
+ "fig.set_size_inches(12, 18)\n",
+ "axs[0, 0].set_xlabel('Tap')\n",
+ "axs[0, 0].set_ylabel('Amplitude')\n",
+ "axs[0, 1].set_xlabel('Frequency (kHz)')\n",
+ "axs[0, 1].set_ylabel('Amplitude (dB)')\n",
+ "axs[1, 0].set_xlabel('Tap')\n",
+ "axs[1, 0].set_ylabel('Amplitude')\n",
+ "axs[1, 1].set_xlabel('Frequency (kHz)')\n",
+ "axs[1, 1].set_ylabel('Amplitude (dB)')\n",
+ "axs[2, 0].set_xlabel('Tap')\n",
+ "axs[2, 0].set_ylabel('Amplitude')\n",
+ "axs[2, 1].set_xlabel('Frequency (kHz)')\n",
+ "axs[2, 1].set_ylabel('Amplitude (dB)')\n",
+ "axs[3, 0].set_xlabel('Tap')\n",
+ "axs[3, 0].set_ylabel('Amplitude')\n",
+ "axs[3, 1].set_xlabel('Frequency (kHz)')\n",
+ "axs[3, 1].set_ylabel('Amplitude (dB)')\n",
+ "fig.tight_layout()\n",
+ "i=10*80\n",
+ "\n",
+ "f = impz_comb1[i]\n",
+ "w, h = scipy.signal.freqz(f, fs=16000)\n",
+ "axs[0, 0].plot(f, 'b')\n",
+ "axs[0, 1].plot(w/1000, 10 * np.log10(np.abs(h)), 'b')\n",
+ "\n",
+ "f = impz_comb2[i]\n",
+ "w, h = scipy.signal.freqz(f, fs=16000)\n",
+ "axs[1, 0].plot(f, 'b')\n",
+ "axs[1, 1].plot(w/1000, 10 * np.log10(np.abs(h)), 'b')\n",
+ "\n",
+ "f = impz_conv1[i]\n",
+ "w, h = scipy.signal.freqz(f, fs=16000)\n",
+ "axs[2, 0].plot(f, 'b')\n",
+ "axs[2, 1].plot(w/1000, 10 * np.log10(np.abs(h)), 'b')\n",
+ "\n",
+ "f = np.convolve(np.convolve(impz_comb1[i], impz_comb2[i], mode='full'), impz_conv1[i])\n",
+ "w, h = scipy.signal.freqz(f, fs=16000)\n",
+ "axs[3, 0].plot(f, 'b')\n",
+ "axs[3, 1].plot(w/1000, 10 * np.log10(np.abs(h)), 'b')\n",
+ "\n",
+ "fig.savefig('plots/lace_snapshot_unvoiced.png')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "torch",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/dnn/torch/osce/stndrd/presentation/linear_prediction.ipynb b/dnn/torch/osce/stndrd/presentation/linear_prediction.ipynb
new file mode 100644
index 00000000..a465fb9e
--- /dev/null
+++ b/dnn/torch/osce/stndrd/presentation/linear_prediction.ipynb
@@ -0,0 +1,320 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "import matplotlib.animation\n",
+ "from scipy.io import wavfile\n",
+ "import scipy.signal\n",
+ "import torch\n",
+ "\n",
+ "from playback import make_playback_animation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def load_lpcnet_features(feature_file, version=2):\n",
+ " if version == 2 or version == 3:\n",
+ " layout = {\n",
+ " 'cepstrum': [0,18],\n",
+ " 'periods': [18, 19],\n",
+ " 'pitch_corr': [19, 20],\n",
+ " 'lpc': [20, 36]\n",
+ " }\n",
+ " frame_length = 36\n",
+ "\n",
+ " elif version == 1:\n",
+ " layout = {\n",
+ " 'cepstrum': [0,18],\n",
+ " 'periods': [36, 37],\n",
+ " 'pitch_corr': [37, 38],\n",
+ " 'lpc': [39, 55],\n",
+ " }\n",
+ " frame_length = 55\n",
+ " else:\n",
+ " raise ValueError(f'unknown feature version: {version}')\n",
+ "\n",
+ "\n",
+ " raw_features = torch.from_numpy(np.fromfile(feature_file, dtype='float32'))\n",
+ " raw_features = raw_features.reshape((-1, frame_length))\n",
+ "\n",
+ " features = torch.cat(\n",
+ " [\n",
+ " raw_features[:, layout['cepstrum'][0] : layout['cepstrum'][1]],\n",
+ " raw_features[:, layout['pitch_corr'][0] : layout['pitch_corr'][1]]\n",
+ " ],\n",
+ " dim=1\n",
+ " )\n",
+ "\n",
+ " lpcs = raw_features[:, layout['lpc'][0] : layout['lpc'][1]]\n",
+ " if version < 3:\n",
+ " periods = (0.1 + 50 * raw_features[:, layout['periods'][0] : layout['periods'][1]] + 100).long()\n",
+ " else:\n",
+ " periods = torch.round(torch.clip(256./2**(raw_features[:, layout['periods'][0] : layout['periods'][1]] + 1.5), 32, 256)).long()\n",
+ "\n",
+ " return {'features' : features, 'periods' : periods, 'lpcs' : lpcs}\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def run_lpc(signal, lpcs, frame_length=160):\n",
+ " num_frames, lpc_order = lpcs.shape\n",
+ "\n",
+ " prediction = np.concatenate(\n",
+ " [- np.convolve(signal[i * frame_length : (i + 1) * frame_length + lpc_order - 1], lpcs[i], mode='valid') for i in range(num_frames)]\n",
+ " )\n",
+ " error = signal[lpc_order :] - prediction\n",
+ "\n",
+ " return prediction, error"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "lpcnet_features = load_lpcnet_features('lp/features.f32')\n",
+ "\n",
+ "features = lpcnet_features['features'].numpy()\n",
+ "periods = lpcnet_features['periods'].squeeze(-1).numpy()\n",
+ "lpcs = lpcnet_features['lpcs'].numpy()\n",
+ "\n",
+ "x = np.fromfile('data/a3_short.pcm', dtype=np.int16).astype(np.float32) / 2**15\n",
+ "x = np.concatenate((np.zeros(80), x, np.zeros(320)))\n",
+ "x_preemph = x.copy()\n",
+ "x_preemph[1:] -= 0.85 * x_preemph[:-1]\n",
+ "\n",
+ "num_frames = features.shape[0]\n",
+ "x = x[:160 * num_frames]\n",
+ "x_preemph = x_preemph[:160 * num_frames]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# short-term prediction\n",
+ "pred, error = run_lpc(np.concatenate((np.zeros(16), x_preemph)), lpcs)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# long-term prediction\n",
+ "offset = 256\n",
+ "padded_error = np.concatenate((np.zeros(offset), error))\n",
+ "ltp_error = padded_error.copy()\n",
+ "for i, p in enumerate(list(periods)):\n",
+ " t0 = i * 160 + offset\n",
+ " t1 = t0 + 160\n",
+ " \n",
+ " past = padded_error[t0 - p : t1 - p]\n",
+ " current = padded_error[t0 : t1]\n",
+ " \n",
+ " gain = np.dot(past, current) / (np.dot(past, past) + 1e-6)\n",
+ " ltp_error[t0 : t1] -= gain * past\n",
+ " \n",
+ " \n",
+ "ltp_error = ltp_error[offset:]\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAAD8CAYAAABn919SAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAAsTAAALEwEAmpwYAAEAAElEQVR4nOz9Taxl25YeCH1j/qy19t7nRMS9972XmZW2VUXJSCDogFQWqo4lKMnQcQ/K1QKhyg5FA9EBCYFFqzo0ypIplJQKAw0sWsgNC2iWVCpQ0gGBG8i4oJz+yffy5o0bEWfvvdb8GTTGGHPOtc6JuPGKZyJtxXy6LyL22Wf9zjnmGN/4xjeImfF1fB1fx9fxdfzTP9yXvoCv4+v4Or6Or+M3M74a9K/j6/g6vo5/RsZXg/51fB1fx9fxz8j4atC/jq/j6/g6/hkZXw361/F1fB1fxz8j46tB/zq+jq/j6/hnZPykQSeif5eIfklE//eP/JyI6K8R0d8lov8bEf3nfvOX+XV8HV/H1/F1/NT4HA/9bwD4S5/4+X8ZwJ/X/34PwL/9//tlfR1fx9fxdXwdv+74SYPOzP8egD/5xFf+MoD/Fcv4PwF4Q0S/85u6wK/j6/g6vo6v4/NG+A0c43cB/P3h33+on/2j4xeJ6PcgXjwA95/3bpHPARA8HDw8ezgQiKj/3uE4DKByRaGKioKKot9z8Ajw7OGJdrsVH/4cP7dqWR7ORUR6Xf/xBg/Hlv+N93I4tp7LDeft98kozHqvFQDgQHDs4MjJ73zGPcpf+Plnz77Lu+dqV+7g4BDgWf5Gu+smOAKI5FqYgcws/1FGRda7dvD6jj0RxjtlQO6OeXdtdh4cP3vh75/67Pl9ju/HrsDezvNRwWCq8qe+BwLJc2H3bM5+9LztfHIVpL/nATii/gz1Ivt19itvn/WDtnv6nGH3y2Rzs+pP+uzcPwk7Z8X4dkhnArHbz2tde/b3l+5/HOP37N6Z5aoq61np43cnP3r+7hho9yhrx+ayh2MH/8L6GefF8ZkBz6/9eB/2DNywjp9/Zzzmfh5+zvjAf/zHzPzzl372mzDonz2Y+fcB/D4AvPG/4L94+a/1CyHC7IHJEaIDPAGVgczAWoCtMlL96ZsmQIz58OQKy+8mZiSuqPqyChgFFRkFZVikEwImeJycR3Ru9xLsuMfXdbyy8ef2O1UN3a0U3LkgIaOCEeCxUMDZeZy9w+RlQWS2a5c/i163TRZHalT+Y+w6qTKeSsFbvuGD+4AbXQEAMy94rI94wIyLj5iHg9s9jefz1J+318+ZgcSs76226zazUQ+Tt+q/KvPuOUbyWJzD5BxmL+dnMEqV46fan4kZEU/07P3LRrm/7spAYfuzG8RUK1auSFzb+ymoeHJPeE8/4Fp/wFo+AABO4Q1+i/95/C6+w8/niMco58hV3l17b3rjiRlrZWy1IrHMOLlmBw9CIGr3Ic/54xvMS+/8pfczPoNxZJY5W/n5RmBzaxz1IwvvpTlo3x3fpZ0n6zyuw1z2w7uzY3oiTE5sQtB3V9UW3AvjWirWKi6HHJ9R2pbTz5tR2mcVFQ4OMyLOFHH2Hicv52fIfH0qBdeasSKhoMLDISLgRAGLc4iO2jw0m1KYkbm2+3GDUf/YOxg3uvFzW9uzIyyeMHkgHl7e//SP/tr/5+W38Zsx6P8AwJ8d/v1n9LNPDgaw1tr+Xcl8VPGUit5vqkCurAa9NqNQdOcu3D/zIMzOY3YOExG8TgTPMjkAgNnvrkO8gD75KpuxqLjWjFxlMhAIAYRIz73/8Vg27CUnVN0uxHBHOJxdwM/ChMnNbaIyy+9nBlLuR7LF4ogwgwajaIaIkdm++/ICs2sdjVp0hMUHfIdHMD/281FfUGYI2zt7YVHbprPV/aJ3JBNx9q4ZGjPG9jv2dTOiGwpWJGQqcEyIHJBKxJkDCjs49dLEEKthsPMxyxw6eKv2nIpGDPa5Gf6of7b7dw7EhMgOhT0qGA6EX+CCiX4bMRL8tH/OXv8rPPynz8OeSR2uZWW5T9vIxBsV7x+MZngCe0SJaSQKQt/konNYvMxxZjFy73PBj3XDE92RsInx4hkLJpwoIA4bht3/4vucKBVtw7FNzTacSE7Wletz3zsgEiG4YTOHrFlZr4RU9+/DQQ3daNAOhs+e07VUlK1v2u3nhy3IQe7l1DZz0uPI/Ei1agQovxfJYdHnNzvSS5FZGshjqQ6JI46jQp6P3U0gwjLMH5sL0REmh+aYMoBrBt4nxvuc8cQJWVGFAN9siyfXrs+RwwR5v+HXcNh+Ewb9bwH4N4jobwL4CwB+ZOZncMtxEGQyjGOrFff68vcBnYRA82wyAyAHGl74vWZc9Ri2GB2AicTbjuq9NUMJmZh9EgCuAsyExC341eMBmSsq9ruwJ/GuPA1eJAO3Iou16O/bS7zWjHSAhSy8xHBGT4TZOSxEmHTxAmIwUmWg0m6ys/6M9PyALlayqKcfA+ibyLihEXqE5AkIrnuzqfYNtm2iOoHP+jsvhZQ2ChMSyUWaUbb7PJPHAg9gHq5Pw24A11J2xxy9euh7iHDiIREGIy3OwMoVKxcUfQczBywuYHb92bRnWKk9F3uGDkBwhEAyXwC0SOGa95GIJ0J0ulk48cbEG5b3Lf9Rm58RETN5zOSeRYQvDXMCrpnb3+3cEzyIT2As+pwk0lg5tYfnQAi6TURqAFpzkDK4OSEAEDlgvCp7tH2j3r9rR7K2aTD0Nmcr5FnZsT0I0TnMTpwu21zWIt78WgvuGivZ6BCK3Y943RN7zM4jtusgRALOPuycE5sf0e2jkeiAyoSH4V55eM+p9rVCkLlAQ4Tar0eiNFuDFrEWZrkmdvBwbYOe9P5He0gkz+vt9tIM+Pj4SYNORP8bAH8RwM+I6A8B/I8AeWbM/D8D8LcB/FcA/F0AVwD/jc89+bgz++Ehm6EbvRoLjz81MjOupeADr+KlkDyNmRdceMGlRkTv2+Ktw4Me9xEHYHIOy+Bd2ngJWolOjJoZQYBQGHioDlsNKDy1kLMO3mJRY1/BL4aMXr3EzA4z024DIILCEG63qFOV52DRjOHyoRI88bOw/OiFA4xS8OKwveO451bzSIcIgIAWIbXP9IIKAxEEp8ez927ent3PVsVo3UrByhJeO9hmTM0zs1EUUkPdG3oHggfhTAGOYj/n4F0Z/GCGpwzzwr5L1N8zAJCD3LRnBHK76AkQhyMXfaY6N3/kO966H/FEPyLxHZEWvK7f4Tt+gwcf8BDcRyE0MybdA67DObnBDwGEqu/Zf4T3EBXmic7tvlHhdSPde6gO4ulmfg4VjOvYDKRtZvZ8bW3MTKje7WCZwuL53sv+fliPvai3zGAkVGzIyFTa+nbskFGwccBWQzOWBGqRRXQyBwB9x2DcX5jnz9dDh87MVuy/Sz3qODyD0XbY71dmeHVC986gbv4/uZ1/evykQWfmv/ITP2cA/61f98SOCKfBXTxi02bMtyqh17VkbCjIKHAgzAiYyLeXZYmcqBPAs0di8fYMA2svdtiRLbX1/Pr2/64snulasXuxngiFCczA5A3WkesXrLDjh/Z9u94jtr5WxW4Vj/M4vHD9fqrAViru1bzOjv9HOARyLXrILHjOXTeN0RMPJBHL6BXK+dAW4/HJHCf7CBWx3u/4Ll96jpXxbBFUXWSp7L8bCLgEjxP7ZnQTGywgeYj2zjRuCseFZMdDfxERDifn5bcc7Td0knSlvIM+UpV7vB+inBGPHo8xDkcQzBYX/IzPYP5tPQAQvUPQTe34rOQr/cAG5ciGSbv3ZrHN0bjY+cfNfHSgHA2JWOwdJ1ZjeyuMDyXjWjdkVI0sAs4UcPK+QTEG2Y0RMGDzo0M6eTSMY94AYiRjm/P9OLamBLs+t3Vibldw/XfsPsVJ6PdZGzxV8WPZ8IFuWOku7xcbEq3Y+IaiYGnEgkd8i2/qN/jGnXDxfndfI4w5vrej8+OI4FkgnjHys+uzXIFOiQbbvTS3PjX+/5oUHYcn4DKAQ8fQxhbKUyn4od7wg/sBH/AWGSs8ImZ6wGN9hXM5YS6yg4/rIYAQEJvhmjS5ZhPZxscSSH1iyihmC3RRdwyfUEjgmVD3++tL70C8dMCRhODyGWsixMlL5/3KTrUiDRbHvIKz97hgnxM43pN4PxX3WnDDhpU2MCo8Ak48Ne/HjDqDxQurABw3T++lmyrMuBdJrn7gFTeSLZfgMPOEWaEErx61ebqS7NlDSPciRmNV6IIgGOWkm7BFI+KhyiYXKyGzfwbDjKMytyfkMb53EhimVDxpwmY8pyTnX/aWbHGZNy9Jzp4Ys+MD2EMVenwajI7BSVdOuGHFSqtea8CJZ5wx4exDgwpTlWdkSfU8ME8yFUXiBZH3CLjwgkea8RgCFi+b9RhJpPpC8nKA4mx4Ah59wMNgMvxgkAx+APraFa9UPksVuFdxzK7Y2kbs4TFxwEJBooaDx2DGkwhwDFTFlc2U2zsGNOk8kB5s3S/swNgnXc/eIdCM1zyB+ZVg7Vxx14ToRmm3Th5oxuJcu0+DbSzysM0iV+CuTuhdk9+jDYrq0Nk92aYaSOBNewdFn2HWxOvnji9m0GWn7P9uGXfeh/SLc/gZnfG6Lij4bf3dTrYyFgIgL9s8VEsAAd0bLCxwgiXXRs95n3UXts3sLAzqu+klEBb1yIFPJxGPwzyUe2F8yAUbl76DY/SgzDvs+KKFsDaO0ImF42PSUb4nG8XsJnyDaXj+nfXzoWZUTf5GuJ544250/WESA4BjQqpyDnkPGZkygmKuM3k8+IClJZ76c9hKn6QCZQCLdy0xBHSDmRR6aYaLGXxIctrzcrSHYYJ6sRaZ2cKz49tzG70m+Rmjln6c0bCM33OOEJ1/lli3YwDPIarjZ2fv8Ugenk7P5tAY7TAYkyNM1eHEHpljP4dBdsPasNzRyfvGFAG6twvs771w94ANFjTvOTqJIkbDbVDJtdRnsKWd5zjOPuCMsINrLDo5YtHHZ+qdREwGM47fk+iZG6HBBjPjXiu22s8nz1NzRNQhS4bDAwdUnnfQCjm5RkA2DbBBrUM4im5LDJqrkDUEGGtH2FoG3zWnNTPefsJw/zr6LF/MoG+14h/c7+3ffsD0bBHaiESYw3NP9DhGhsqtdg71CC8IRUmOnrlTyDI6m2Umj8V5pIEuB8i7C2RelpxzhFe2YUYbA8BoRxbS+mKT1sNVepYIHb1MRsfFq3ooI3f7pQVqi2Cc8JbAGTG6jre75hXLd+X+xiQx0HHlkfrpiJRZ5PC77gJPD5+EaAoLhLbWirWWhvlGcphJchaWGLPrjI4wu07zMkbHvTJyKc/of+DnUJE7vCv7rEMD/SINJhuNvdx/z4GMm7CF+GLohsWt3uSn6H42LLHcczD9eR2plebp3mvBnTM25JbkjHCYyO/oj+agVH2Hdu4OiUjy0s7XIsFq74/UkNOLxsiubUzq2yZA9BKYuR/mLPX8WY/Wi+YLjK5sG/Pk7Jn1dVWYsFRSiMLpsS2/1RknFpXclP54HzYj2xQtMdzeFag5XKOzNQ+UWktodsbZPhqKFsWAmhNYdCO6lor3JeGGDQyGh8MZEx582NGYP2d8MYMeiPBtnHafvTT5R6/wJSM1YlipKkcVQBl2PNnV0UJd+/7kHB6DQ3TxJyl63RPfZ8blnH1yJO7wydk7PWf/vcXLvZ88oSi0YosjV/4kZmYGZq0VH2rCB7rhSh+w0YbAAWd+wBt+wGsfJaTUixy9nLFgwpE8m8W5F+EhcaIHD9AW7EtGsD6nV9XDQzQKWdIEp8UnjoEMobh1Q8NiOABlFXVPZVLI5lV0qAd62Xivo+EaQ+P989z/20xEOTyQjuXSzks/ht14drwjhXJvoEdjNobduR4pgPL9Tem0H7DiyT0N9MQFZz6BmBCop0IlN2OeuzgMozFaPCFgP/dJnwE1I6VJe6YW3dpznT3wEHyLHlveK0sSOLFAQl43bTvnCH+MEOdxHjr0qIoBPOWCP1bmizDFKzw8TpiwtGjE3jkjMDVv3NbgBokQf0wZ73nFitTyLxOCcs/dzniPfHm57gEyrEDV52JT3pHAvjzMwaMTCIiTtbBDoAkPVcyxbaI9Z/ZPAeQSHeEXi2Fgwmi4ZcaHIt5HGvDIY7EAgEa5ilbtRbSrLgPE8wP2BojRF3t0wMkTLgFYPLcdVTwV0uw22jXuOMbDtTgCHoNsDuMQAyYTvF33sDmNSbAJo/e9v9b2px4ms8Mb9kh1QeE3YhxIwn8bq4agdpzjMPzzHNyueME8mM08o3ZOasyPkWoJQLP3HbtkcKO+ZeXhyzvzDVc/KY10vBbD1Ucv9TgYwhzZaq9NAHoibXZynOkFl8beH7PRDcXobBqhOZBylI3rvU8wHoel018y5HYe26Dt+4BFB/p3WAJ6n7Nx6nqOxT+VWe/T44FnzDU2koBXD312Xjzj4fgjVbcdW3/W6gG4b0COxNnpz5zbJpTUKTDvO9XnXOkKWd+PFNq926Y1Fg4Cfa5tWearUfsIPUFvUVtlYHIesTqcqm+Qm0WVUXNk4/teq+SEbmUPXUye8HMX8bODCfxYIv8Ip9nz7JGvjFy7c2ckB/m+vY/n88lYbjaEbimIAtw+9/NT48th6ECDKCqLMf8xF3xfr/jgPmClOwICHuojvqMHvIkBj1EmT2Hhqd5KbV5x5do8jyX4NnGAvjtHt/ekPDGiY3hi/bw/1Mr7h2hGPlcgMTVPq+qitYSuvXCbjNF3NoIZZrt+WyhHL83gAPPS1tK9d6BHCpLk6h7j5CQCWLzdl3zOLCHpS9xqTxXe9ftOlXAvhFuRPy0En9C90xG3tw3rJRodI7REYefuSuKLiIZJLE+nDouOSKrlZi/3NHtuITPasyQYD6Uonn8v8txsE41EOAXg7FmiIyeb1L0SnrLD++RwzUENSd9YlgEqsxD9fap4KgUrl8Yhn8nL7zi3M5r2jAEMG+DeKNh3FhCCl/c3e4nwCgs8Z+ytXHtC0JNHdHE3l48b/0u4vY1mnKvNK7kg8wojUZu3wADFVJlMdYSWYNWf/d+3UnDljCvuLck784zXOONn04Q3U99wDXqQpKTMIaebsxS/CWxp736rQKzAWnyLHMZhUSWxPRuLKj5uFEdm1vHf9qwsv7CP6vr57XkbXDy1CF2/yR2aPb7/IyOI6Mg2++ilPxtfzKDnyvh+7dPOJtUbOuGhzi1MW7zHg3d4iIRz6JiZGD4xDiPG1ichw5x8KyE+B8bJiwE/7sIj6CCLRgydPeR7cfiTzeNtIvzqXvChCOp+8RE/mzx+68T4bs44+zGyAFgNKesGYS9z9xnEkG6VkCo1745899rGF25RRFHvYzSLcq1yrPHejsm8qg+MAJCG0pUJzELDzBUNv2y/R8odH441e2D2pMa1Q0jGVhrL88dh9gHAznOyDYMgnPT+3mqDYj42DLPuxh5wVIfnx+17STeAp1zxY85YuTQDfa4eqfYqQrv8cyDMLqAi7BKfRXMxdj+zcxr5ycZguPC9SLXgvVRca0Hhikger1gYKItnPMYKB8ZanW6uwA9bxocqrJAxgjDWir3bEVd2w2fiQfMuqV5tk29MK7m+txvjl9uGH+gJV/oAUjjnFV/w2k94UChvhEqOG8tWHe4l4F6mBntGR7gEh1eR8BBkcwXEGZrUcL+qHkUruUd4cwx8O9XYoqCXPN7x2qhBWXsjbe9PNxVlp7zLG97jjif3hDJQYo8j8oQHvuAVLbh4j7Me/LjOxmuyqXucwW54hqNDUAHUika7/ZzxRTH0b4a4eDRcH9uQVmXFMNCqtiwxZSGRFSrYCyP0MP7kGa9jweIVAiDu/7nakovMhFSdUBJblpox+YrvZsK/+ECo+ug8Mc5+wylkLL7ANyytG/Kqf5fwXM4XXIUjhgMjs9NF4HEvHlvpz8U2FdmExOgWJqyVcC8OW+3X2Lmr1JJ4Xnf5xbN47sQgEjx002PcMpAGqiQBOAf5T54JYVPv95oZ99RD46MGj224VNXrHjYcoCcVRyw/Q6MdQpN88CQbRSTGYyh4iAVRrzsxYSsO90qtqlMilopLqFhcRXR9Y6WDsBMz4Vo8Tj7gMTi8z1OjypoGzVOueKseoNUBTArDTAphCDOHcPKMs2cEN8zB2qln9m4qkyY/tchFw+rZAycPPMaKVzEjkBh0Rx6OPGYfcC++Ryf6Z67cTM7iCXMALoGxuH4thiV72kNlWd+9bWwAEAl4DMDvnmcAMyp/K+dh01CRc94LD3mJ7mmMEYkleh1LJLZ4h0sgvJ4Yb2LB4mUebsXhWoSaes39WsbjNSeIe57sXiRHZvCPefTt/aBvNsfI3I5TdFNjBrwXHvvJT/i2TmA87qJnu9djnuiIyFnEahTcBv+6LjcwVsSa02PFS0Zhtvv/dQuNvijk8pT7QhsTI2Mlp1XGHT09S0p0zOt5YYN8D7gE4DGIMX89bVh8gQNj8gVLyDiFhDkmEDGYCWuKeEoTbjliK/6ZYTZjDRhsUxFJjAgNBj2zQ64ORAzHtoPzzsBUDQkX3zeaEe4RuKRXIZpHktWbHj19+10e/v6pUZhQQtWij71nYCwNew+56iYSZCN4KeE3hpJWZ3AJ8m4wfM+ggfEeK/r7rmw0L+AUGJdQ8RgyJl9QmHAvHkBAYo9jsV97H/ouPDEWn7Ho7wdXkKvH97cz7uWMP6oe368VH6Skc4eZd5aIGPOzetyT645HdKwsnH4NtummYYNlqDcIqOCZR2HG5MTQmTF/HRO8GfT2Dl2jzQm+LeeNztYNw7gddh1jArPdz2Bwg+c238Z3099/v6HCUMMv/43vXPJWezYPo3v/9nl0wGNkvIoFr2PGrOe+UUBiwodESi7oHqtg7tD7lPMsDDxG0uOP790MNx+gU7z4XT+Q5gyft+cmf6XdRlKGecvcN4tjNXSuQKyEWIGp+oE9Rj0SOESszIY4UHs/wKcp0B8bX8ygy2IY/71nrZinlyqhEqNQx9qAftNjyGkP9birORLsdPEFl5BxDkkXe8ePixpeZgIRYwkZnipKcG1y02Gy2Kt3NEIaBl3YwnZqePdGGhhhAfl9gUb6OUp1SNUp1tmNOoCdB2qjDFHFuKAtIogahdiGYlCMRQoAkKrDViVSWPV4/bq7gQdk8ncsWhakGa3xndgR9obgkFi27xqzgAT3PvuKU8i4xIToCkp1LQpyAFYnG5tFUIsviK5KBASGd9z+HX2G1+c2+4zZMSZnNQeWoO1MqF5RLIb8EoBLqJgcN+y3VIkYboWG96nzm4AliLGVnJEkK4kIVOT5TY5w8nLcS8h4iBu8Y/gUsBav70SwY2aBoKJjvIoVD6Fi0khP5mOfgzZSdc0Yr1W9UZJjBFcxuwp/cDKOI7fo0WE9RITiuPBuk2bdkEe8OZJEhyI/0efTVh3eJYd/eAP+aF3xAaswuWjFE33ADe+w8gcUTojuhFf4BX6n/jZ+Hha8jq5BKV7Xj92bHT+jbzp2LfYdyRf0tWtzcnTYCgPXTFgT8CELp52Z2yYvOZ5BDgKS8zmrK0963mzJ39o1YQDsqrLHnBewd3I+d3zRwqJ1cBDE4xCcfNKd1pJX1ywTsRCAj4T6wIA10x6+OQcxDAZzVHQs2LuK4AumkDGCA0fjLd6GEyNb/M6Dse+Oi6JUh1odCjuBBdTAEgHRFQRf2u9VJpTqkKtHLk69bNkEUvW45YBb8UjV9fOheyPHxUjECIPRM691hHns3LaY7V6n6hCLfHepbhcJZP130c8AMYgUKibXvSbbiM0QWSA2MnXGBC0ALYrpPw+KqwKy6CZfsPiEwg6hVvW8C1J1z95FZkIuQt2LXOGpYhqiEO8qTiHhF8sNnhg/n2Xz6slSwlOS0L4C8GyQDuMxVJxCafBcYYF9ymAEbPR77Z4XBYEbz0GeVXTAm6niMYizYY6EA0tkxoToAm5Z3v3kK06+4iEUnHTzOo7jfBhzKHZdW5V73orHdYAVLVKcXEV0Kog2RJtjtAr0ZLt9xgDW6nDLAqWYRz85xilUnH3FOWRMeuwK4JtKWEuAownXHDUKeYVL+AXOQaBSY6BJtE5K80XLnRV9d54lhwQYk4xxCjL/zSZ8bBQGqkJ4rQ5B3/0lCjOmHNRa7XC7ucwvn0dgX9nAgQ4dp8pYCzfGkV3nyIj7XOjlixn04xgNsbFOmIRdgSA3Z4UD48MqjCYmZRQwS4LYQ94K8J4cCk94ykEMHDEuMeFnyxUP8x2X0xUxCCLJTGKMD8aiqCGPfu+193uQv5vx33JAyYOBZvGsnW4iwZVm0HP1cEUSwebVB1cxuYxLtM/QEpcVZkjcC4Zkb+A8MWZXELwYt2MoPg7vKiYAwdd2PnnOTr1ReS5mzDb13ACHrQoTZsSNTaHRWC4tnB48dwDt+cj1y/sz3H/xGYtPmHwBUDAj4xK3ntBjub6teNxzwFZl8yuANo4QT30KGdHnZnweqkcFYfJBjJVBJZWaV7ubn+pp37JvzzaQGNmgzxnocINFSp1GS5hYNirL8UQnm8RjTFhCxhyynEfvLTHhlh0+ZDWMep1S6CLnomHzduoBWyQWXcXkCyaXEb3AUEUN+ZoD1urbvXv11qMa8wbf1B49Yogqq57bsUTRXo/9sSKYSIyTRiGnIKm+JWTMrmLxE76bfUvm23VMrmoU3r1uQCE+mztMfXMd5qxd59GQW07BjmvXmyohOSMm0MBM2eenjuO4mj5lesfvCg0TjQhhDtGYcP5UTvGl8UW1XB5j30ml3B6YHWNMYJBihSe/93jsIY/hC6NjVMYVdwAeolCfTqHiu3nFWUPv2Wc8Lncs04bgC7wvYHZIyeO+zlhzRC6+edfeVfHoXQG94BnZcDCursA6hbqxKpUABDHgA+tCPHTXYJNPGW6jGxIBi9NuQM1bxzOjTeg/a+G5eupOvXZbMBKBhGYYrfjJruEZawdohmvEVyVZLOGoFex0j6QvEmCPndsiigyca98cgq9tw23P+YXIRBZwvx+7NyKGcwyiilK8PH/iBidYItqRJKIj2BiRAwvJYcsdciBiTI6V+153iUd7BuZBZ9YkdnZ4nx2uWe5/9kCZCCcf8Gpa2zuZQsHDtKECmF3VvAEQXMXZF5wUOgx6fIMPoytw+tn4rsd/VybM1eESHVL1z4ro5HfkT2agOId7DihQT52pbR6TbgKBZLOwiBKIAnPofIlONtWgm6tBnYErJifRCfQ587BJybND20hYjbblJcZN/QiXYPz57r1A31tFcJ1D39YX6XyEJOUnyHUP+V8kJrxPstG+12gO2LNrRmbaOMbrM/qo5QjNZu0rkD8dWYzjixl0S1baMGqVYWL2IApsMfUQC+h0pjFBZceVBKlNZsEnv50yfrbc8Wa+Y9awdo4J53nF+XTDNK/wmqiZisfpfEMt3RNndijFiYGvDqyGrtY+0cY/iRiTbRzNqxAoYM0BW7GEq1yrec/yO33h5SqLLhfXQvfoCiZfMIeMOaSGC/dnezB0L2BwpBsUEcMr/AMAKUU8rQtScSgcG6RhRjEQIwANEpDiFGrc/Aa7sEz6tXTDbRv3Oeyxy+YV115OLps4C87tixqsfp+fijIYtPPkmoEv3bMs5pWSLOzd8zs+q/ahqAw2aVpiLF4M7OJLM64WuUg+wrUN4V3y+OXd4R/fCn4oG1YkPGDGnzvNmHzEmyngzSLvRfICdcfEqpA1El3FrHPAzskMbMXjlqM6DXKvwTx0nxG8RITMhFQCbinimiOyQnnBVZxDwsO0Ypk2TF6M7FYC1hRxSxFrCSh1YEQdoMnoCgyVmFzFFiw3wQrllHatcmyv0Z/MszG6lIh7z87ZCmGtrlFzu0fLjbM9Ms1Ae5bMxwxjZqEmP2k0dC/iHHqt7bhocn7S63GQuVlUGsSUY4tCQaLZtC9IYnQdIrufUYNnlMJYnMfsCKfwcpHcx8afGshlHC9dPx3+PBYCjJ/1QiE5lhUPsWHVxaGQYNZrinh3PTdjYV518AUh5ObBynm6sSZS6qNn9e4rvM8gM8bFoVaPnP1ustrG0D7TOzri94Bil1V+vygeb6Fv8AVRYZvRu87VC9QzGGL7bvQZ3lcQ1eE+9gsyq6EfE6UG2wQ1qiO0AEzYqsMNssm1wiqhaID8c568vafRM3QKk7nBoEvSUpK5QY26QDOELQfZXEHN+FkuZLxPZodaqT1/i3xIcfkW+uvPElNLAOZKO277ESufnNJAA2NSA2vvLVIVQzXAL7NjROdxCR7v0wmFT1g88PO54ufzhktMcp9q0KPrCd6JbTPvSczjRu3ouWSBfe5dRfQFhL6RB1dwiqltAN7JZ1PIu3kVXAEHeWax1F0yf5yvdu9b8VhKxj2HRod1gCbmy46J4l3fdORcvRYiVcI1uQZ/yLnkvJfAeDTPmnvtwa6sXmEVqztpJAAITBZ1czQYKjpWho4AWlltSFCHsZrTYu/fMya/f+BWgGg1JZZXMENvRYjmiVutheUNjQmzVmp6M08fp8M/G1+O5UJ7losZYXsB5r0FiIcN7NkStrgS75Nt3evryYlJX7J5pcFX9YoL5pCwzCtiyDtDagbREpujEez3oMcLGXFKiFOCCxmohJIDtm1CrbQzrs5lTFFwdIsI5Jzd8IzXkFLEliPuKQKMltDNxSMX/2wxyWJwzxadI4YjS6rKfeXqkYpHyqH9DjMkIlAvLLraPl/ZowwemnhMfoADGH4IfckBpBogNowClnhPf+saJ/IuGWiMCvOIBcMFSvXiNebQIANPjJNST4Prz9X7jHmSzdkHgSNqdbhdT+B3r/FhmwRysXvSa118Bf9EnBtdxex7MtOSiMaYkurKTls96Rw7+YrvZqcbF+N1zPhmXnGKSTdMObdBKNH1JLxh4kvImEJp9zrCgWawx3EswIn+ZSshkYvNjdA+M4eiDsbVKLc2HBilUV2pQy5Mkjei2vIEdt12jqyQo2MCzIFwwOlw7Uf6bvvcokPsbYHlEoxFheHfEjkNtTCAJL193W0iBvFWJmwmtUzy7sTh6FHEU3Z4qg7fr4S3m1SyExEW5/AYCY9xn+Q1bD5xLwY0CrBRf+v+VX5yfDGDzryXzxUVNW6YUaOm0T6JAcgut5HDLfdQXXbo54aBCLhHr3SignPwDX92OvGdq4gxNexRDLnfeXbMhJQj1i0glYBUrKKte7mjtxtcQYwJMWTMUwIpxkjEIKffNY9u9OAHo16rF++6JCxTaPCOcwyvcI5zFc6igkoKB1Ezun3TKfA+w2lirFaHbZ2wbvJfrnI/tlFsRYxmo6hVh7X65j2Z0U4KsRx1042FkLknADudj+H9cYMecVHZ7C9BjFdUI+WIAWLMlBB9wXnah+j2/Et1qEk+N4/X3rEPBbU45BRlMw8ZS/5IiyYdLUFn16Lv0Z5FqYIx3zigghouf1VP32AoqcDt/PQKmfOeGA9DrsbmiUEukfrmMvuCh7jicbnjMt9l3uoctk3Lh9zps9Wh5A4T2pxO6nCsOba5bM6AGHDSNDa3pLKnihDSLpG/ZclHHNlGADC7gnnYcAT6kU0oWs1F7VHnOBz1IrgRQpGiul5QZ56uJIB5l+Qccx+9ApuwVuBJse9bqUhVaIjnQHgzEd5ExiWURiMEy0ZlEhqARNRchIXmh2svLG3vvpkYlyCb42ibnjLwPlGzTcafN849hu+mwaP/3PHlDDrwbOcx420QCaBc5+L2+Cz6jtwaT0Aeyll39tlx2xjOvuK7ecN3yw1vTlfMxkPXRR40GcrMO1jEmC6AGNd1i7ilCfcUkdQALiHj9ekJb169x+XxA8IsbbFq8sgpYlsnpBRRa1S8uiK4BOcLQjBDUnQR79+cQTapHUPxSC+bRZgSYswgX4Aqm9C2TsgpIOcgi0UXeohZFv8ARUzThlN1qMWjFrtPh22bkHLElgKqemVbDriniFuOLZRuRU5VvM08JDEdAZMHlhegpJGnLr9PKOgViYW7ProxMI6/L+aQwNQXmBnuMERbgC7idca2Te3flvSuCtdM3uh5Jn3gGqNBQvSimCc1T3wcgargteiQDrMZgR6u2wK1zYtxYCWZAdUcD2nkkYo0yQ6+4hQ3nKYVy7y2OURUu0OiDB9mQskeKUWkLHOiRX4l4JamFuXYcAbJQK4BkIT0HBLmkDDFDDfc/5Gyyyx5opRigwtfKnDbQ18ZFxCiq81jFshF3v96OIaDRFA2turwIQuf/Z3WQ1RG0wG6qOTH4uWJX8D4dtrPQYMXDfY4bjDLC/cg97H/d2FhyoRKmGun526VcM3WbYwbW8+a17ciJXCDeaL79bxz4AsXFh1x8KQdf2TXpbbD3ou8JEuYAfvqQhuBpFz8HEy33CRDu77EdZsaVBF8wVQ86pTU4/UdZzeMdiiJr0yK0doiksl4Wu4Iuvja/fmK4DaEmFoCVX6nAq576ja4Elhxd67dK85b7NcyeN3kGNO0Ybrc4WIGV0JNAS5kZN0A7DjmlTMTanEgt48E7DvMBIeKadrgfcEUO9Z/uy8SmVSPawktqVWY2qa56DEMUrkXh2vpEZTJMFhRh73/wmaee+Wd9Bg13Z0X+PaHBcYgbCWAM6Guy+5nhiEbyMMg3FPE+3XBj+uCpxyaIRm55SOrKKmRl4KluLuWY87HohdhgPRo5qreuuGjpQKrJ5y80Ag7VKB5j5BxWe67jd7YWNO8wYdeKMVMsokXh5Tjs6S9RSpmGE8AXuE5dGHnP44xB1TrnotdD44IszgSkbhJOJsBtxyIHbPqf7lIVGOevklhfMjdG7eaiuBY2XDd6QMsqhNPV+abzKdSgRuo8dOjk/lqORqvdkKSrQ63ZAVUOhepY+5NUsHe/eHxJbVjT1mKzUzKoKm0qmduksVh56VzK6hcC2khUsfcP2f8qVBbJABwwAxWXKoXxiSnzSmUr8no4YhpSzQBICJ43dWoGQM5R6oOTyk2D8tpUuwcNzAIUyDUltRyzXjasIKiVVkBzUP3Cedtxqss3niMCeSGEHhKCGFrNEdbLEdjXouXiVn3hRs+FDhfMfEGrtTgGiJGThH1fedE22YRlxXT+b47PjMBjZHjQJ5Ri7BUmHqFLHmG8xUhdk4+V0JU4xFcwSVNSNWjVMK9BDzlgJqDMmPk+W/FYSsmw7vfeHutoM6F8WfD35PBO+YpO+6wEwk2T+jqF1I0ZkVpasicGEaDv7wvKMXjvi6o1WHNopOS9Pc9hCZIgXf4qz/IO3Rdnc5ft3NKW0L5TmLDkZWH7cXIP+pns2O8mTIuISH4Dsu1Z1MJgG+wmqFD2zrB555D2W0wrgKoO2gvhPLMUD/DoYsTSmcKqNWjaNS2zyntaYV2XftjVwB+N4/tO4RuFNscHyo7O6TRo+vonuPZ1+KaUyeRVYdbrZR/ZM6NnPNJvfWL6i8FJ5i5z0ESmYBuIpoMdYwKh0IMz53X7knorQb1yD05bGTbSZ/bJjcwt7vbOy6iKMmNNVaIkZXunPev7ZPjC2PoOhnUd4pOtBqg2WjWByoLqGsiB3040QE17EuuzcOzLLMjoHpZOI4YsxdNDwJjCpLRn0LCPG0tYZayJIXuKSKVsMM2hTLYE0pWabqlgFpPcOsCUsZFjBkxbc3Ii8EsgnM6gUrIcTfUrqL62uAP4OMbADOBtdCJq2R/iMTT9kGwchgdzzD6MelqC7Q4+AFDNYjndl+waTQDoCVQc/U9HHcOvu4Xo8ELAmkRJsg7MUGkpwx8yBUfiigcAhCFQ+dxCaKXEoaJHjRJSBBjLgZENq/pAKsUxf8rd8kChuQVahVj1XIMxaknXJuo2iicZgwTwGAUjZi0wOql0d4V5Nrt+ZsG0FZ60wTz2AL1d+IUbrGNy+5lrEy2RP40bXBOkuyAvHcz3A0ScQwfCsKU4GKGi8LCqtmBi0fZIkoKjZ7rHMn3Q94Z45JDg24s0hvv+RlTqnRjbpBdlyfYJ8mT0nifcsSPKeKuToawnirOobN6OvtF3q9pEI0RIbNsosIlF2irVmgHIlHPtLwPEFqEaffy7ZTwncKmjZKr9FNL0o/yCcbCMlopM6Ek8dB/WKWzFtAVJZfGaBkh5g4164TThvMMCnvBsp8aX7Sw6Jupv1gLOcYwyimhPwbG4+H37SUa2wXoEAxjn0gQiV3FR6tDdVXpQbJYfNYm07qQkuLFq4pzmUdi/G9jFFjiaplXzNPWwmBADCagNC7FbolYFkspsoC860lSXzAtK8hXwcQBcPHI10Uw9NuMnFXh0YvX7oLR87pR5UpIt/k5zAM0qMeG81UWu1/bZ+k+gauDW7nRIBtvu+7ZM8an/SmarHk0hg1OjrCwh2/etvx5L0K7EzqXyNfeshRhHb360ZNtsgUxNaqpbYCsieLRmHfPUCpxT4EwsfGla0sAWpFSYYe1BFxL0CIkr9+VKG/WhJ9hzsyE7BymwXMXbD7itjn80Z3wdhMd8pMnrDXg5Gd8s9za+3LOIaCgVoaj2owoDcYtZ983Js5wXoy5n3JLuJOTjYVZIDlytW3m5ItU0v6E4HbJAXmdWn6mGCFgcDJsWARVBqM+et7HwUz6HgouQTR1jph7ZWoQG0H47Z/jtCaFUJ6yKHOmSi0B54nwIUuytEdhUpNgMEzbpNr5XEvwdwRAPXP0iCc4oVVWJpzaHFfnFF3oC9yjlOM9j4Jf8acW2DC+aFLUWC7jhfsDPma72Ji9tkTptYgw1FZ7iCWFKMApdshlchWXIAuvh0aEW474sE07OpbgWrVl440fC/TijXsOjQEQfcVWAi5qMCS0VVpiKLtEJBwjTAlh2RCWDRRl4dXiwdmhbhH5PoF1wdTiUFJASXEnQ5BSVJHkueHkx/DavZC44+J3C4E/kj6PU8LrNz/i4dV7+V51yClgXWfc1xlJqYulOky6WQktNDSxqqfssBbCU0YrsLAmHt8Fwu84j9BolH3Ct7lAFa9iwTfzinnQ2anNO/ZtozJKqtQYdPYRgAZ9jZhzqQ63p7PCZx4f0tTUDSdfpJI4CP9bOOFCt3vkLro2nuOoijdSP0UmgRTqEwGuXyxBhbKk0fDracN3yx1LFE58KQKxhNCZTC/h2uOo1WO7z0jrtNvgx1oIY7qMjKuR4uh9RZw2zMuKMG9tU3C+IC4rfEzdO9ckfC2uYffyM4+UrYAuSl7DIjarYxjPqZvhJSZ4x5q7kFqADyniffa4ZTckEXu+ZhQme/GZtIiLpVmz680l7qVj6sD+2KcgomXjayUIZXRseFh1o97lXyohkBj02XW4RPeRBkmOn23VyADcbJBJAY9tCT9nfLlKUUjFICAPa/ZSiXXxFecguNaOfzr83SoTb5nwdpNuR5klq30JhEeIaJSV61q14SkkPExbYylUSDLGqFrjKywsNEUbz/BGrfCcOTdObwgFcdJwbTCsLu7xy7JG8ZYOkEEtwjgxI224ty28MQHWEktj1d64WFr41jnx2zqJpzpASEZ7PBYbjVj96B16X1Fq94ClnLtvDEYji85YBSKidvRAtgpsreKyR2iuXUMvDAP21Z9CuyS4oU1aKXtuvX13Chmn+Y7z6dY3VzWWc0g4xYSteASVYtjRE53UK6AZ7v09mFxDKlLJm00mgamxM0zKwZ7NiLU3uAIdbiGFXAAx0swElJ6ENOqhRUvNWFo9hBXDDVRW+7kYP5mLMaZnzCc7PgDkdd/vd0zmy0uT62Y9PjkBtEkjm5dE48Z7tiIxeY7U6hlMrvqWA95nj3fJ4SlLwxU9QqunADrMevKMs9qPyffeBpMXnjhiFwPb1GsfJRiMcbIFoUWKCmW/ZoOB9kyr/nNAlSKcGVXpvGRRqGkDmfE2Y24a/Jb4tB4PXKSOYeyB/Dnjy1WKUpcn1X82rHEk+9vPxqo0r3DMSxoHkkQgTbg6rXh0iE7KoiUZ6mBl9WsRmdKkJdpijCQEXEJupdVAF6gyj76C4KvDmiPuaYK7MaIaauM9T/MGz65RC5k7dj0aOa8wgXOlGWMuQkNMW8S2TY1CKXTLjDhtAjH4KlBLirh+uAj+nWMrcAmuYIoZMSShPA6hshgH8azG4Q7f2VLE7b7g/XrCLcXmfV5LwLttwrvkG4vDRiDgIfSFPTKW1oLexJuO/SZJdcZJjWI/JjPhnqZGudtGrR3b+AZZZPmZ5DScq/AKdxmFz3D0dq8gXLOUxO8TcbSTJ/7UsETumBCV46BhsK0ClRmb78/NkttdaiI22AvojB1nkJBVfaoxN659865tk/C2Ye/htzE3U4tDWX2j2hq0MtJkzUmxeTxuTtBCMqd1D7GW5nzYdfiDV12ZGnPqQ4qqeinPZnIV302Mbya0ZzZWYhqX3yLxyioHcAABX2LyXELFQyzPvmeMq8xSL0AwL7lLMADYC6ENjo9VGRuF0hqnfKyTkdFYrZ+qQDdC9EhckFCRn6n+f3x8OYPOPQNsCQzAHj4/C2uBYSck8f5exQpHDg9BAvIuuiMluVaYYNi8bBZ+OB5wDgmXuH0ydGuXzKxQnmh62DGYgS0HOGJsKbRCj5gDUoo7XrQxYLxi8faZJUzHRVYhiylTkITtYdH3vwvToxaHTbVYrtuMXJ1g/Fp8MsWeoB2f50uRQikBpfRFu20TntYFP94XvNtmbGrcPmSPH7eAt0kKNQp3aMUSQIYBFqUv2tlaExL9txVUAJJPbLKzimV7X1GKQ3CSGASgkZzS4iBGd+Nu5Cfvlb9dWmWuQBGmWtjlhFlzMiYtO3Lgx8WLdr29aGWrvaI1Utd4mRpsI4v9KQf1DAWPFTVJj6RG27XISAxnHGEOCByTcmjCcYVd46w/nK6YlxXzskrFss03zbmI0a2AFhvlLUq0aJLN+rkZ8+ZwaMRTSsBG+5yN/Ol2mkZbili3iC2HHeRi6qKwKFA579GVJoXsqa99RnfybIyG1N6LJS5XLSIybXrZ5Ef6K9p8N0bJWBT0sWHJVYu87BiOe0LVMH4rHjPHZRTtOuqet3l0OB8RYSLCBIexs9fnjC/XU5SB7zUX50hV+aIyXfD8pY0YOlxFYEmacaiI5BpdzWiPkbgJ+1gWuhsOMyRaqvzCDj56zz2s7ayLVhHqRVBrDp0WZzzieV4HPLK/NvOqxuPnLbbEUylhwIr7wrENwDn14HJP2Np3TovIGLwZS5rJNhFh8UhoLF6bbS52jNv1jF/98A3+0bvX+Ie3M56yldb3pgjRQmqY3nRFhcPinsuLiu59Twx5ku5RNICRO49Vf2dsREykEQxVxFiwzGu7H9vYcg5YtxnXdcZ1m7GpxO0USjPmznVoQLxApxWxo9Y8cAkJD9QVAW2uULtv+TxXL7TNFIX6yN2gW7I0aMm7GftUnWjHOzFEk2Odn1Xx8mGjf2HDraoxNCutsCXi9D5rkQpgSqHlPrZtwpYmbCm86K3aOazQLoaMeV7bnM0p4r5OeP8kxViluhYpRJ9bsZHNoZQEO99KwJYloW0a7/AFbhBaE4Ps2mZ3N9VLdIjDYDhmwsamhd51dmxu2Yht09jruYxwydgLwEaprtUbGINmRyIYHxhLMVxCh0QqA0/F4cfk8HYD3qaCe9VKWRCic61Vnh0rOMJjlP4O5vgw9rovz2iLTy++Qjnex3/0T3ZYKANYEqBT1XrHmT0PeEyKlip8z6PwO6EbdqOdWbceKaXuxrh1BBo0vm0DWHxvWTYmvwxXHRcb6aSEFgA5YrBKCRiPHFAqWA67as62Wai4lw9SNNKSfAq3WKWfU1hmignec0v0GYxQimpvDB62sVUskSkUtSqUTZ9btSwA3NcZWw7wTtq+Rb1PeSaShzBGR64eb7cZzAue8r6IaFXvZKsVSXtzWl/FcxCR/+a5815zusJKoR2+nXxrENLwfYUYgiaVHbE80+IBzMjFYasBppNu1Yv3dUbw0vVozRHv7wvebQs+JFGVNHVCY6047prgQpVzLWkHdKpmtfk5GEvTJrFh0cwPW8DbTaKZXBmnIBrgsy/4Jt/23viQyHy2fqgihL23nHLAlqbdd8y5WOY7Luei+QePlALWbcI2lP47YqQSmsE9cs2nmDFrjghAYw6tWwRjapj+loPCYaEVChmsJUJxh01KPeytuKapY2NsCj5qs0zu0D1scNiOEUTV/yzfZFrqzF37xYy/OS3L4FCOkcKRsinXiFaXAACYgUAOl+Cx1TH/8dwb7zYNram9XSNBvXo8r0j92PiiLBfDUImpaWS/dMOm8Nf4okwqwu8QKqO4nh0GZPdmloo8+X2H4iqypxaiGi4qE8nvdCGiFhqkKti7eWYjRmuqb5W7Xrlphzsw5pDxuNzwmANO6Y4QxaDnJDDM6DGZZzRFhwmyYMhV1NKLhryroCH8XrdJi2P2gl5Ar8rrz5paUnUPIejGVnwzGkSMx+WG07ThF/V95yIbl3vowNTeD0lENDa/PXmjI/rdlmseuP1drkX+JKD1ehTIRiv53J6N0Tcvv9M4B4A5JLw5867k3HDcW5rAW//MO8ar6Y5L3NrGakY9utI2j/H5Wmm/jZ1YVQu75TlZotQ2FmvWsHiHx+hQqtzjz+eM13HDrE6A1UOUErCl59pBpjxpz8QSqc4VOLevHk26cY2Mk+AKLvMdD+crvv32B8yLFKGNUhMmHWHDnI6xatUM+qgmWopv529qk7AIB7th70VyWZKDecrPN69jc4poEfiYtGQCBpGs9n7aefrf10pNJtfK8QMBpwC80SbWjzF3x497A40xZxDb9QybH4kt2bxUjbZnxcDGpnveC6AmRwiAdmrraovWvNsqRT93fFEe+hvloROglVtCR7KwHtgbc5N0LSwh8vvs8f3q8ZTlAc2q2/AqVlxUo/qYlU7VIR1e++TL7kWZFyDJEakjdsS9xFmTP7auvWNEGhpNqJKjI0YpoemI2OKbpg3L6d60NwC82DBj9JCYBfs0xgUAVK67Zre2qI7Kjb29nW/G3quH7lzFrKX+gCxSKfP3uKepca7N+PcmGtICoEU/TgsjBt7tcRF/LFtPw897KNorKCefm7G1UvGS+9Q1Q7LliNs2Ne9Q3m3GZV7xarmJRINiy9smxVPXbVaBKtfvbTSUJIlHMfR7JUOj5l03aShu+Rnr7GQFMObJm453ql3QrII/WtZtjBG7DpsTzIQC17wfIQoIXDPKPjMT5nnF5dyx7nZcjR69L62Un6vgziFKpNiL0J7DfwAajOVqZ6RZtbWpM1olr6e6F1lDd1TOMeHbeUVw0sijFxD1zlG7CuJK2EzaAHjmmTdodbjm8RlbJGja/GZcA8nfr8UhcdzNXzccHwACBHKx6uTWBB7aONzLlS2+0xWF4SKMHbtuk8/trebEK6/DjZWPrJuXxhdtcLEMGOVs1VPQF2Avh6kZsRHqsNZc7xLwdquobN1xCIsj1LB/AYUFK3/WnooO2esWdvciIpuUqSremmJPgDnpT3lRD8t0pI1GZuyK0YuyarwRVwfwTF+lQBOm49sFdp7nzhM/aH+Pw1M9ElkAiFGq92Xn/ZbqZKFNKxbuEqi19g43VmxkjA6BugAenrkcDwN+jKZPIRGZwjlEOAVpCt1KoNvG2o+187gHNUy7j9s24cM24ylNLXRffBBIzxfMdcO+4GpQNhzmYqlODOb4/AaorRn0qlWOacJN9WCq/v5di1q2IZoZQ+7ZAQA3jZDx+Vfl09/WBR/WBdetb6zWaesUU9Mtt3dGCuGl3CmxY1HVSHMMvuzkEF6aP8c59BINsb/nHh3adQZXgKDREEnHKVsfBuNIgZpAVNvQOcpqCyKk58Dz8+3/ffT+gW6ErWjIRtEktiS+e1tLizZ7sRDBsfLYtejopSQqM7X8iVWUbtX0p/aq9dHt2X0GTwqVsW8Klm8ySu/nji9m0AsD73O/gcxa0emkE3qbTPoAA9WGw81ccPYFr6eMzAEnL4yByQlN7hT2D77BNgMuL9fQFd3MMJlB38hjqr0k3PQ6TIXPrm8jbp1i4tqlVSdfpFfpcsc8rQ0a2CVDteTf/m6Gs2F9VRJcWxIc3SRuCZ0C5o+bwnBtvQrNuNK9pZx9d/weMCag9wkj8Zrc7vuk4eZJi24CcZvApXlXaIymrQLvEuPtVvBj3XCDZMVnRDzSjNcx4BKo0RcrEy4h4pvSk3m1ika80BZjS7qZrkpWjY/x/W/F48MqUUccPMhUvMIZri0mM3pW7t++W11jv5TquviTRpNecwyAeKlTdbgEat81Rsy1SAPltVooL7jxVqQxSc4B7AtyDo3fLgwOhVx0U11LeJbUe8kAT6b5HxOiRjq5eLy7nfH044z324x14LmLc1OVUCDHX0LGZVrx6nTdRTkGHa5bh3N6ZahBMYo/OxJP3tvc0cR+dbjliHcp4ldrxM0EtAiYtYBoGQoNP3avo0TxM9iWDh62g0pSVBTXHQ55/3a89hAByPmtzN/yW2Y/Gj0V++YWo3S0PFuJAl6iWgMj+6sXVPoXvv+p8UULi6z7tVVpedepRXufdPy9Hq69NCzJAfSXLm3snNCvxu/y80RH4xi7ChTfEqs2SPHihrkCjfkyLrBSCddtFmmB22kXOlvSyQ/cYGcbzlDoY95kVE//YwyFdi/VgVl0zA1ekeerm6IvCAOndVyAPYGketlDU+qPjREDTerxWOk+IO90GbyrVAlnT7iEgDfZYy0nuS4S7QqjOHbv5LlOiL0Dy2VEjcrKQTRq3Ky6xnefUSlL39SbyjuUg+cO7BNyFaTvmYFBbdPC8rG4KrPQ73J1SOjYrbXpMz1uk4S+BNkscvUqeSzzq+mQO8bMBRVSgPN+k76d1yLPXSoTpV/ut8sVr0+3Vuw26qu35KYTZsqby9MOE5dNJOC2TTKHqmvV0N6ZzlHH1iWv0guX7LNcJAG/ld6AOirU0jz38V2qEyVqna49r1sRWdyPibf1KJmxKNvqPFSQmhPWmnVXm+N7zrkNa2M3evYAGlvOIjAbtln337cIV2iLTxmtij1XlrZ0tWKtBQWMSA4PPuBNdHg1CVxswnKmmX+r/7Q0uMBAx2EAILjimrceSJKRi6s4h9wMq01O6avI2s3I7XjoRBLCjVluZlNkG1/gvgrMjg/0ZEc4FEK8NKw4Yhs8negqplpQKokYmHnuISO4XgjS+c8OW5qQ8x7nbo0ZXO9wZAvLGlOYocoqnpWKarAMlXhHhoYnESqz8D3oNWaNNm454q6L2r5vDQqiKw1T36rD+xTwx6uwN5Jik9ZWa9Jw1RbH4kXt7s2+EBE94LTnZxKnz3nPQC+wESNOMD2YCkLk2lINQelyzGiGqHmQg6a7vXt570XzArW9X6M3boqNj78TiLFRP/ZTDviTLeJXd48fNykakXvq4kxnb1XMUCPUtWDacV3BEjfxJA8dpXovThGcsl6gD8tNdNLVAZCmLLE3PFc8+zRtOM13obkqc6Vkj22bEP2Ce5paxS0z4Wmd8fZ2bgba1ErnkHGKG6KXJGIh1xqkSDeogApRsJRNsaCG7qXbvY7Om4PKEJDAHaaU2Odyb6xikZIlSgFNXkILDBX2Mt2n/nzH2oLjXNQ5YnMCWhPy/Gs7AoLBRJNnPKJi9r34qTCpJK5D4dDqNSYnOP6p1c0A1qC6drT5s8eXk89l4Dp0wZLkNmmpqwOTJd9GRkXXxgbkAeZqSQbD3WVxM7+sgRCHJ+Q1lLOQWc7RhyW1gH2ydEyW9I73bjfZT0Foj0tMeJjvTePFvJkRVzd6Yc6+dZAZvU13fx5a52FB7zjzZrBdVboTAU6yLJU8HHObYFYR+cN62h3bcGDbCIk6JasyoTi5R9tIFl/xJhZMziop0XRcfpV7Eki0dgjTQXHuGHKKrKywQY6dcCxMvydpy7eV0OCR7o2P0YZr0sljJ/snDfPfbh7vs2sb0WOs+G7O+G5e8Ri3XW5Fwm08awb9PgdsA3fZ5tk/d8r45/TRjuH4VmVxM8tx8+ApOuXbm8yAwT/tntSAmDhaJcKI+VbVK7elVWtPgBvLySpNAYgDUecmTMdMUnxG3PInpTqE3MXqrAIZAHJxuGHCqkqMhR2u24T3acIH5edXAIs6R5fo230CKm+s+Srx0KHrTemAw3rcJWTJTLnMl3txeOIOvdh4BrfYsQbnxo6x+3k7j0WaEkFEffZjdH+M8qHXbQlOYt2wPCE4a3ZPuhF1TL3JQXOP3ozO+7njsww6Ef0lAP8WZJP6d5j53zz8/M8B+F8CeKPf+e8x89/+1DEdiRi9/V2q63rjXa/hZjCY4hP6agpzAei4k3mGNsaH3juFdw7p+NIty74NIZU8/Nq9dlbpATBAMkHjsCkIjz0PjImPc4+MhnfdZly3CfciZefCz+9tu8aw1rw0K8EHjJ2BHdfeFj4rLJDh4FXC9F48VtXQSLWHrycVS3r0ufVfpRFb1WijVAfauMmLZubdBCxMqB6i66z3mhnIGXgCt/dgnsqiMsfkjIfeMUugV8iagTLq4ZiIc+Bnjb3Nyxwphw854CFNeD2J9ENmGmoQpMLzCDcFXxGGoPuu0gNS6u1xN6E0fY6tkQf6JpoqteYFordNjWbbE+cCPewSpXotLXFGBv91+KJUh5tKRNhckesRmG9MwjMTnu6n3bFtHCPSDqPsm1NgvD4CnG5owcn8GTsQzU56D5ynFVPMu+vrhUUe79OQ48HzMeLLI5vFNsnMaHKzntBsykxoOuz2/2OympwVG5kN2efgHClsBrRCxAbJELdnQUywSnLpzSAb91oJuYgxt3fvgKbjLjK+codj96T4a7rcP/l1IvIA/jqAfwXAHwL4AyL6W8z8d4av/Q8A/G+Z+d8mov80gL8N4J//1HELA++kehsOgqGKF0etc4fj3tGktt24QwaLEz0Gr5KbxlE9diJJVRJRT9m34hfD7S9B+gcuviIMnqgNm3aBWMVz+oLq9zIwPobPqjJj7mlq4bR1pg+uNK89aym3cYVNn0T6Vw7iS773Z7TPLPH0qdGx+v7Z6NEy772V46ggeFhvSePhyyZrFZFSuk2NubJ4wiV0nrbxeLcC3HXxmeE3iGZ2vRlBaAaxn89GKgFP64J364xbjjBN60Xho7EJsdw37wzIyBe3ZxeGRWoJzF2Z94CVd0Or1+8EGjAUaWTnjP1W7fujbn/U37V7NKpiVcncrOqeI5w35jvkfNwS9t5JsnfMF7xc9Txg2oPp5MM6eylv09Zi9UjwcB9xIQ2iA8QxOWli1vtOqxzrIcYxetajl26N4bdCrSq5sLKnisBbZtCjA86BcAkdEmnHqOh9RmEFiUo5dJa/0ciMnndJGo93hHbt/UcwKqmDo8Jc7xLjKUuxnTgQrlWJxob9jIqMvx7m8jn2/18C8HeZ+e8BABH9TQB/GcBo0BnS0QoAXgP4hz91UE/Aq6H8OyreCnScW9gKvWuNGRb52fMJx+gvC+gezlZck9u9aqLCE1C9ao4csFmgc6P98LlNLMPXjudvx0DHXQHB882bD65gKV6ElKpg0bl6XLcZ79YZ77YZNzXoIuVakKvDOTpMVQJpY+wsMTXGi5zHGjwH5IP4V3tAw/1I1/htp+RoyUM5VucSG0d7vOdjkQ2RBJLMFrlo6xKSU3vWTH+VhHjhbpC8696Xvftnz1U3VWuwEV1FclWO66SYyzBdM+iFHbYs/TMNd2YW7/opRzxpctGupUNweyGmXlTW54FQZwNu2ibNohxH0A2w309VL+1dEi30p1xRmHEJHswBr+OEnxuLyTYMKHNnoIvKz9Gi1h69oIXrR971+M7sOUZfWmOXUeMmF49Swi6XY2svKU3TVCWdRm2mPzN69h7YGeroa2/YTdbEA0oGsMK0isXvr9PmhIMwqEqhnSyzORDM4u0yukSuU1sgMt0Cde3nE1pnNEDgEYvs4/Bcxz7HrWE1sCMEjGSM3uy8OzMM470LpGzFQt5Zp7U9nbGPjztaL43PMei/C+DvD//+QwB/4fCdvwrg/0hE/20AFwD/pRcvjej3APweALzyj3gdh4epyQQJs7t4jsEcgDywAjE4axWP+13y+JC145HCNi4ULNRL/0Vbo+IhUtOAAHrhiwN2GhImG3AUuj96CzYcALh96ffkCk4x46TNdaMvjbudq8fTOrdCFOZeseqIMWtCy6RcTZe7Fbocr2mILCQ8dtiKdGOxjcQgk3HRMVNjVuQhfLb2cmNi1Z7/XsZYsOl3qlv9pIJT1hT3XsRrktL/UVWxF1MAsjHkssfQoyNMXrRSRgaK3bP9F6iiErV725eK88txO8aFWjEzIQ87SIVgz2l37zR4ZM+Pd/Z7nn+PKPV3IBHIqyjRS2GvtROMXywZr+LWCqjkHLYRRbzbZlxz2JWgj9ckEF/BQ0x4jBtOEW1Da4wg1xt2dMqrzEkaksXyOxXOEUgN+oSe7IqutPcxUvfGKmnb+Ew2w2BAY3YJk6uASPD687Tim0WUUK9Z5u377PHHd49frYS3W8FaWSObikCk/+3fQVDIopJRlWWOHU2iwbJd40U+tw3EZLctQgjUJUnMoFcAVKV36tg3NrP8cOS4y1UB58BNMlyen0SrTwl4nxj3UodqdaPvPhfz+tT4TSVF/wqAv8HM/xMi+i8A+F8T0X+GmXeOAjP/PoDfB4Dfir/FH3K/0ug65ioZYDGeAsMwJlcarJLZ4ZYDftgC/vGd8HarKAycPOFVJABew1hqXp3h4iPRv7eA6jvyUVXPYJYxWWgLy8YxKULEWLxrjIIx3HfE2kF9OAf3jkBj4Q7QQ+BbimCemmGegrTCszLw8dxBtWPs2MbTNn2TTw1j7Bg337DlJn0wyB6MjZQB3djIkp+MV3FfHMTM+4kP83QMT+whMDAYw0OkkatEIbccpU+snvspR0Sad7i7dR+ykN+BRa41R/yj24z/14eAP7wmPHFChMOjj/j54vHdLBXHnizfIOXiT1k2KTm2NSZX2u1h4X2MP2yRKCDPafG1F7Ed8GtrjmFwXp/HXYrXk865KnNuV+VKnUgwRnOlOrAmMndFSFWdgdr76kpStktHd8MvDthYRTzOW4ku9N8eCMU3mmMv6KqIypwK6sAUJsTiMHk0KKJCG5A411q5GcXV2htKwZpMoAqBtX8dscJk/PHaReaCOokPseAS0FhXrHakUSLZsHyH90lkBZ6SODME6QmweGGzLF7mCjtgYmBxwEOVdTfO/aLX/5tOiv4DAH92+Pef0c/G8d8E8JcAgJn/AyJaAPwMwC8/dWC7TkIPmUwQnkm9KDYeqQNzbaHKooVFQMCrKJ5hJOGiXkJteBdDII/32eHdJg96Uwz95IHXkfEqFsQw9JDkzlz5qeFIaE2NtWL3RFJw9IN2ljcc1rzuaUg4jmM0/tIJPeCWAu4ltGq02VU8xA0P04YTNjhtd2ULTbwk+UwMmGva7yMu3K7F5VZJVyph4tLC/NomdlUOcRc4S9WDkhTueLKiI3m2RD3BZGFt17MGHDqG7kgmwYjlRsd4CL1zlA2hhQmswizJZ9s4rYhmGjB0i0J2wmTEmF3BQyz4xeJBiLiXCGvau3hZ3O+S0GgZxlCxd6R/qjG3RRod76I+k2w1A5Aq4V6FSrdWmfPWU/Sl5KTx6xN3bXV7xoAl4/r3U3W4pQArrDJjbsnskV8/Gn17rsdCs7a56HWsJbToreHLrjQ4ymArK8QyqiUghi+o2ifzUFCnzowU5wVcVVfpqTi8T4T3ifFjlv6zDoS5einEqq5pB5k3fgnYebSWoLdetmY7xkYlRnclyHs/B8araV/M5AfEoDLJ8+Ce5LZm5jYuoQrhY9HHh85wuhfC+7TPGthv7jVrgDi8688dn2PQ/wDAnyeifwFiyP9VAP/a4Tv/EYD/IoC/QUT/Kb2VX33qoETY4VSmpdCSSVp2W6pUmVVIwUZA3RULJO7kfThC0GRHImovztrTLV520VnXwewHnXSmHU/5+fXyzlO1z2y4w3eBPVvAmACZHVA0q33Yeo/Js7FCMenEB4AVQN1m3EtAXJcdlmoh7rEoBoDyqwuM4248YvP07ZyGw29l38vyyHKxje999vjlPeD7VTwkT4R5aIhrSW65vs4uGsuxj8/dvM7dfQzXEpQSasfw2sTCZIytmjHngJJcSy4a/zxVh0CMx1BQZ9+pr2oE7gW45r7wRiXIUbKAAFhXGWEm9eR8pM6YMKnVVKnlcqzI6BKkEnR81pYcNXw5EqOSFbQBqN1jH1lAo8QzIJ6yMKLGGgRpqbfETTjk2vqO2SFnv9sAdQJh0ohy4rJLmLbmH0Mh2zHCk+cnhVnnKWAuHtFK/C06Pay5ybFCsoTHOKkT0KMiU2e18RILzhhUgfbIm9BqhXFyr9ItiEhgwlshvE8eUXM6gUSO4hIqFte7IYk2j7TCXItrQlzyO/ukatUI7+2m+ZNSkFiYMItzOHsnfW3d86ju16kSBT7DoDNzJqJ/A8D/AYIS/rvM/P8gov8xgP8LM/8tAP9dAP9zIvrvQJ7df51/QpldPHKLxRW3cpI4G72OniTb45m9ASzhx02SIZO6elEbDPMwme6F8GOy6i0xOidPoAlYuCfDAICp80zH0WGHIXTVCXnXBIntqNKCTQTCvCutlNq83Kif2UiagBxpiIAYvZPLmHf3j/Zda9nVrvFwzVKaXhvjoDU/Hp6rVZYChme7ofBGvldUM9ykhg0CeMoBt+LgwFLqzjRQy/R3Dw+yO0j9/ZgnZRj87AnMAZcQ8e1Q+m/erJTu97J9YwMxepMIAAghY4Fgymf1GIXHPmmeZMYl9B6fV51TT7lTC23UtmiPnrSGyBh5xISkuK/RFi2kt5ZnDNu4sNuU5RlJEdriEy7RtyIqKZbx2CDJuLGXjWyUfeO1Z8+qemjzih1aZa2dS+8ETtX/xs+JGCgAO0Ith2puXa/WOajXAPQ6jqqw3VY81hQxb1NzdlIa9NVJ9FKKRgTCdJL3UMmYaVCKay9YS9wrM58yqy5KL9wxeMaYRYGAKQ4RlN6vMUqOPPYRN7eiJRNdG1EGG3m0bcMcnz3weiIsJeySt1IXwU3m3JKnJ40YphdUJD82PgtDV0753z589j8c/v53APzLn3lOAM8bXEyOcA4DQ2Ao3gHwbLLbyz95AiZJdkYn4e/k9kZ38hWPkF1/00qzUUJgrQ7r6hp+btTHRROSwJggMa0TGYWVm1odrLs60JtqEO3vQ+5XPjMPtEL41Fz75+27/NxzJ5Kk69h0QbyAgKccWqOAwqQwVMHrmPCKVsTQNb5LJWzsd4b7JbqanZ8ImKiCyQprXIeYACzFNWzcqvs87SOVajg594XvmIFKKISmp27FSON9A7KxfFgX/PHTA351P+FdCrDOP69iwjfzHa+XO05xG/BjhYuotAIa7ypS6YqInvomtVXfFv/4zF/S1SgM3Apaw2FHfR7KYqyq4scAXGNaGBxgWPixatWGGXsHSOHc8NzsP8c9xyOINsF80ikUnCjtkuoADsVFob1z8ZbdM/lhuRaJAMxXa0nt4bo3BLzbAv7xfcYv7wHvkmwqlwj8zmnBn8sRv1M9XhWpBL+nCdc0icBZ8b0FHQRafDMxHsO+iKewetfq5TN6FH5RQwgQTNhKjH83zFuVDfuaqfUjNhsksEuXsiV0uq3w5Q/vB2JfjLJaqnnu1ohDPjc67jdT12exdnr30gXrKittlyS/Emkf4f7U+GKVotEBv3seIAlYxRfhmn3zXi6h4lVEy5wDHQe1F5Yq4JU1IcaAcNXFYy3iLBlqhR42bIIAaPjyU5YXkTi2n4mONeMxVu06riwCPY4j4cXbMOx45K1mdlizx7tt3nsvliAaYB07htHzprBPfo78XTPop5Dwatrzyq0B77UE/KPrpS2YyUkH+jfzHY/z2lq6WfMH69eZB4bJWKwEyMZbASzVgUMX55Lv6O8coJ+qxty0KsbndfK92CwS8CYKbdNE2ZjF+15iwpvTDcFXfKOsiFHKAEDTDx+LiuzflQlrDni3zXifIp6yV4xa5omE+3ItzXflQ1SJnjA7aTjuD57UsbrX9P8LS2I1qxEy2tsRu25a4dW3isux6bQ8X/l+hXjAN/WSLSIMvop2S5BchNmjNUWMlcYmcWtJZJOCsAjOqj9NVdKRODyXkHCOGxaV7F1qFkjHZ3w3xyaHO7mKS0x4NclGu2oDdqu7sLXQ80BSiyB72LiGuvhbxb6B+XG0TU6jv5G1dPIy3zDrc6Q9ZXWsKB+7aTH2G9j+fNB6AHm394IG5xQG4GXzlfX64iH0fPL71yLrxA81CD81vqja4g/b4AXqAyuDZ+ZJdsXoGOfiMbsKuI6hA/riSbxcqXKUQqGzMgcAgQXebgHfryK3ey+sVC/CJYoozslbsYVRlHq7tZcGD39m1n6MOwy4a8GM3OUR/zfVxvG7s2b8d+digUHyYC46i2H8npXs9wSbladv6v1sRUr6Lb8gdFFpoedIOPGshoSq24GPtvmMw+vCFpVKN5zTCSWr+NYoF9gzWY6hLQ1/Vo2m0pDMBIRO2Ljy2uGGmVCIUVg0yX3usJJx/21D9L7qphWwVocftoA/2XzzsC06GNk2BEl+Lk687jg8d3seWyWYFrwn7TYPNMbNiHV7cjh5mQOLF933s8+I+hwbVNhYJ2LQ7XzRSUedkVKb9Z39mCIKT+18ixrSsxeOvn2/NbzW+QL0quRTqM9qE9omg17vcdcK22sJiOpsVNgm5HZ6Su09o0OPAOCrzPvJFSzeNeckMWHLok55Lz2qNiqirSly3KPLtg6GKIY7W+QoxDWOqu/ZWFr7BvbGuCONBIYI+uAgCnHBqYPWE/82t6t65e283OGgxffv2rEJnWjwOeOLtqAbkxrjYraL98Q4e1FS20nYMrXQ3cJMwzFzlSq/QL1wpVQxmo/RHrJ8LrCKyHTOrkMpI27WNo6PGHZrHrsVpyG0LQ7GYvflKqw0ODpZ6MeEqnlH0inHPt8zFcZxpDW24T+vQzgpEyYqHdQ8pqQKhO+3WbyxYeM0NoN5hQ0rZsLYzCGz8muzw1Wx6HHDFpihL8SXxuQJzB4PMTT9kBE/FyMXcB+qIkfGhl1zY/O42pg6Rs10AL6ZMk6h9uvWrjk3bfJrobwoQgInX3diY1ZQMhoLI5+ZiqBVIFeFwKJjXdS82+ztvRKL3ntvkbgXlxo76Iyjw4h6HSSJW0uCj9i65VNGnLxFioekKmtE8yFFvN0i7pU0ei544ypeTRsuWswlhVxd4M0UCqOrOKschjHCxmHJ1TzMt1OoWHz3ZitkfVujEDPUVhC0eNnATPJDWEXyPu9De0R7zv2Zd3weMFZLz0c1Oq4WyR3f+0iFHqm8hL4xEMTxtAI6G4IEPNdssd8bMf7PGX8qeooC/Wb9YEyFz1x3GNJYCWc0uNbSianpiSStLqgsD8woQ3cNdQ0bkwSGQ3Y9Kz12Cm/SmcPmAOy9gfHldqhhuNcBqrBNYywnt585KI9XXyyT1qpQX6i2sXUvYQzrnxv6UbBqVOzzTsr2iyNMYdAD4X1S9sV3p9daYMlp0eG466ZmkMpa9px/grAlBEPsqUXx+PZJ0ZbMGrzQsU1eo+OR5C7G0TxcoMFeBsdELwb9liOyXrtRAyVclpB5Lf16bKFHYoXb9vKsQl3DzlMUg+3avGGWDe7H5PF2kwReYcY5ODgEvJl6b8+x8vdexJC+z243947VoJMT5+cUNBFPFoVm8dCnrWkLVaauzJldq1OwhHnw9Zmxf3O64XFed3PMhtUklCosIosIG0cbgKsO1T+nZtqcuxePq+riQJ9fNsrmEOEZIw5Qo0c9AW9JTrvmyUtB4atdQ3JJfP+wyXt4t4msrSfCORDeTKRFPX2tGm1xhFAtQrkW2rFcgB55jlWoZvhT6d9pHYoImMKQbB4iiuYIfcL5GccXM+j3yvi7761tG+HsHd5MhDeTwB/mMQdC1zeGFb44fEgev7p7/PHK+DEVWRze4c3k8DM4LNOQ0KwOxTMKyyI0gx6dyJiah24URqMkCdMDDRYxPYfJV0xt0cnuP4aGgOLtDESthh37PBZIEYctSMPNGzxjBlO/S4pZyrPqtDPjXFtyyxpZWFHIp/RZrE2ecbZNhz1nj3OOeEhdJ/yYHBvxeUDYDMcNbCLBwY9jXCQ2RJrB3i2a1o4lDHfVqYrxv1tn/GpdWlLUE1RqueDkS4M6bNOsLDxq45Ln4nTRa6tA9Q4/QDanrUr1XubOnpo94cSEeIDWvGecPxLB2chAmz/n0JkSJ4/WdrFVc5IkbnNx6hV7/Mkm/vfkJK/0EAVWtNqJsQGDST63qk3V/MnokU5h15hVnX4omwZvtIPtRi77x/I45pgQi2ibJeivqiS6aB5k8gUnTv19cuegv0ti0Mfq2now5FHhrGUQ31sr4e3m8YfXgO/XindZjn/2AT+bPH5xYnwzFVyC5AUiSdFYJIfHIA6dReWz0mylKl3vrgLOAaOWbebem/QpU0uWGsJgsI3BLCMn3iILEd+Td+pcv0dPdt+facWH8QWTooTfPvXTG7e0MnArDmtBM6KLB876PSsWEsyJ8RAIovDCiMo53SrhbZJSdNFG7x6jhf+OlCIJ2UlNbgAAqh7r6AmxevtrGoSS0A1bdIAfEl9/snn8ag2oLJkXR7KBXIIUtZgnZfdlnuReAIjU+xED7cBwvmurn+Y7gpZwb9uELQfck4S7hn16x5iceKddBEoMenDSiiwMUI1J0lrXduhzsIbHzaCrl3JXWMBU4ywCsQhn54lrSJqGhVu4R0BW3DOWZr8Ed43FO1DK3EinK0yAbv7X4nHLXjVb5DuLvodLyLgESeQVdogU9fgek1PsVq9pLYTvV4+35Pu1Um9RZu9NxMVEyz0oRFUhGv8OxlKixs6RudCVOQGhW56nDd/N0sD5VQx6DLTczlgRyyxQo+kAjc7CcViXqYu2TjS9/luK+JPtjF+tM35YA65lcE5CxZsp4RJyd5QatDXSeMXb3uq+k4/9x9yjbAANPrMG0U9aPW4l+3Fgm9i7TZVa4heQzdoYJA/Boeh6M7Gtxm5LoV2vgzz30Xsf13mFRMpNm7wChX2/zyrz/OSr2IpoawQqkSzkCtuUxJgztspIlVt1s5X4z452cEyLNF54f58aX1gP3aAItLZjYqhrY6OMKmdGAxxVFU3Mh5WmtPjudTf5XBWCqmGPO5pwzlPWRtOH6xs9BNtAFi+VqJYs3aqE0e83SbhuVUp9LwH4dmb89pTwakqS0EU3dFbNacPwz5dCUkcMkFSkGq2RmbDlgMqnHd5JJCwQSwgbrzxVj9sam8dtLf0WL981fepaHe45YC3yXxkW5bFdnxnOVEmrYFVhjgeFxbI3KaajEd0+fhg3RsFcpYL31dQNzqfmUlFII9XQSre7ombF61jw3by1huCWRBS4KBzwaVI2U9dKsXlohrtFGg1zPlBZDc7JAWNZ+L0Qblm8O7uj++Apj8NkHILCjjY/c3W4M8Dcl68ZyxHPNYM4UnDFiAie/TBteJjvmIPon19mhzfnK/7c0K7QqlW3Kh2etuqwKuXQ6hsWNfLeVeTi4La5STJftSUgMzA7L5WmA3MquoJX04oK4CHElou5FylYe58IN4W/RBZbONpnz/DoMh1BPdzZ9XVsa9hkG8Y5GB0ao2V0OMa5a09xhHP8YR5GxztoJTNhNqhoEKCTazFG0R5yOeYO93pE+LXGF6Ut/vaJhn9zC6eMRWAVhWOGvUvqdsphSygohXElgXHEu9q/gJ0inOuecRgMpVWAbZUatS4zcANhqx439fzH8WaqrQuPo6rqcdxCTUv28AC1HPVf3PCnXetRP8WBZYOqnfJouOhYfGNKiA1Dt/+qhdcea/V4SoOLYudFzyeMrdUocLsuQCa/JSdHDrE9r1HnAlD2SyVsGa1dHdC1XybfGUadydA9UNscgis4h4Q3k8PkNXnIhJsKhOU6JpkIUQ0LIAaJIBCYCVpFTQI2kbSqRVS16/ZIQs6p3rZrhl5gQb97byP2bItdnksVjrcnAMbMwiAZoM/XMarKLv9wX/BHtxlvk++R5RAZGMticowlCBVVvH1utEfTqzf1T6N5hlwbo4qId0qbR1nm6AriVHYOx5GNY8de9fdl8+AGiY3vsx7mviWQAZH7kKdb4MlhKb2/q6gTdsMq8Vk30g61rU3b4GQe8rP1VoHWls6u3fJvYzckkzm2ROkeW8duM7dIxGSijYduyptNLIw6Nn5tNGmGCdOdPPAYlRRy6Bv8qfHFDHquQ2ERgOCkkupU3U6P2Lxs51XNcDDuQH9QNvcMm0zVajDMu+wvTBoLdMneUYcD6MZXKH3jDivHuXOHC4J6B6Ll3Rdww88ghuDIPdltLCQe1CkknEJqSUoLR+2/uzIzFmRQwA5yAaQFndMqvC2HFrVMvqvl7dg1g+GxYQt6sxLw4VkcmRKlEhxFJHaoOWgVJLV3KpO+T8asCo6ZNTmtP/KuMzeqE21yT4zspTLVeNLyHhRD32b88r7gx+RbnsMKOdLgpRpN7N6aRwsMtXgJmSfXK2iN6mcJLEt8OjV2xqFvDTwAgEll0LpheKkTjkUgluTrEqt45pnbSEUgFEukjhIWRnELjtWN69o8ywCLHPMex/FuW4Btaf+2pOexsMwcgVHhUWi2uSmCOmLB5avD5IvCLtwM+5GXDwhEc8uxMWjWQYLYQaCeV4Mqq7HQRiYT0I2sFQfaMzePf2w/OTqJoyHOuvG3ZtPcr6MRJZ7JWBwcRo1UEUQmepyH7fmOjCiNOqo6H3Zc26AqxPn53PHlmkTTvkm0DWOiGAti8QxE9eDH34dhqD1TbIcpimPlwUBvVaqxrCzYFNAugQasttOntkpa/NELkgSzr5qM4j11rxqNkgYvRCfuLiqoLWM+enVmUERAa19IMEoIWyLvPK1Y4oYppqZbQuZtS4vItjDNSFsSTJ45q7pd3olZZRI5XVYGi0n8OmKg7PFSYQwEPKWoSeHOxR8ZH7Z4E4s+ylMGbpmbJGokQg0W2u75t+Ox+HBsu64jfW8c5gXlKq0JK4Bjx5nCrsFWtthmxcABWVCpOjwVEXi7FlnsMn9ED+YShBop9zn2srS8g8zJa+6VgYUZJ09YvBR+ZS3CMa9Rch8C8Ul0QM2BiG4P2SWmJmx1y2Fn4Mz7/Rj1tj1LTaKOEhEA2oZ6LGqyTd7glnbNvuBcpaNUdL1J9FnlJ/YOhLbxSwFvk8dVMXSRsBb4NdIAPVCHX3csEluHw2ZkUfzIRAP6RkyEoa8BWl0IcEhuOpPmrhIVweQGHG6lUyIrS0VoN/5yHHMytyrFRkdpgnMQ22Il/nbNWZ2RX0cx8osZ9HGMxPkxARIUCgGsSKAXZ0QnyooAlIVgOyY0FOWWLb4Vh5wI98L4MVVsVZbtWcN1VR9Bsey27s4G+wCWSOnXYpolgCZvQ90loEYZ3pd40aNYvn0f0MIg8/I1mbloQ+nYqlN7YcaWIjY15MxuB0s0OpqroMLw5JqXZSNXj7ypjouG6LcUcS2hVQXK8677RB91hUdFuzRSEgrbUxbtnA+Jca+MyqyhJOExAt9OEpUBPfu/VeDtJhtj1ITkOUS8mcLu3RvTR5KDBHJy3sAkOiXDAvBk/SA7C8TulQFcFUM/jjHINaVEAnAKAg2Nx1+rzC9nydKB+XH2tR3PZYfkxKCnyo0SKcJfzwWqrIgoKhw1JtyLYvF2nsXL/Jxtbg1FQak6pBQbfGSFbJMviLR3Fux+mTpUZnN4Dnm4t86gsfaIRIxUAt7fT3jSfqJP2et9SmR0UljumBNxw1qTeyRwkR6l3R5ASRLCSnNuTx+t2PPALZoy2GXsMEUQdc9WQERGh62YuePpY5ONdl1Q/jtJtWmkPW4PvFA4B2FLBbJqaIt8O9w83v8Ybf7Ge4r+kxiVJQvcLoQM0+5eWmHJZM/DxHKANpDuLIoItEmynxTyZyTGJQCeCA/Bo/DeAxZRHwLQGQ2icy0JJQDtXE0KlgGHru1sjabHxNhaReXPSrvdcBzjubfFolWis8/NYFqnoMIOyF1AK/iCmYSyGENuLeZqpaZpbcqRpXjcthn3FPFhm3FVrWvpnZlxjhtOynKxxfE43wU31mOYHrZBP0kxZsPoBb5iVEiUUR0jOGn9tc4Gw1ALJS0xaoylqt5LrL0tmOihaJORg3cpbCOperVuQ0LbExxZkpcv447mYTZstQ5caRglTvuK+qL0Ual8XIvHVWUCbBh+uk9wyxg38lbkppuDREC952V0QxMTx3CuYIkbXk0rUnVYfBhYFOYVdqGvzuiQ92r3n0hgKxoiGdNBWrR6dFfM9gIMN44GF7BYMbL5aX+vQodMLLj9vVpBlLVL7C3wZC73aurgGPOwNM2QHtf0LTvcIEyk9swHwztuqAJp1E/CNOPcWAf++8dG2zD0ejJ3A26euKk5mk6M0RNFKKw7mwILEe7cnQzosY7EjM8ZX9RDN3UzGnYrKyaxcGVkFgA47L6qF137JHMa4/R9HS10MQ7ouOMdM8wG38xaoTomnloD2eF6PsZ0EIoZD71eepSxZcJt+NwTcAoFF/UijBvdVPhq5//agjvFDTFknM63nVRs2iKQoupOOzjHuCx3PJyucn8vLNTRM6vVY0vS47TWDtH0fpiSKHXUmS5Ok4SjF+TUmxoTVKaKJ8wFbu9NjBG1jdQRpOEw9p4y6SYnG6IU+JwrNUaLJ9MdR9u0XzKAwJg/UQog9eQcQ+WUD308g6t4jL2Jg8EZoxAbgFb6ftcm3pmNqidVyd9S30QWX/GzOeFh2hAPcIRJ/r5XjrZVR49NGCzhKGuFsHiHOHSaEs9QWC0v5U+yMpdeqk4282LKnqanX2GRT8arecVlvqtjUeBzRSoer6o4DpMTptTkKh5jwikmKXCy+Yzu1IzDjJjlWQzKFHli3cQwRr7m6fZiQBzmz3EwU4uGzZ4YoaCzXaiz0qgnYzeF1SQKlV6mAttI7UKq0je0gmEyuY/RAZDcjV2g6ezbBtAcPze818+XcvlyBl16inaoRHZSIfYbZGKe3+z2O6mFWFul1nDYpsO4S49MFK9euo2PaSN07Ex+v+2Q2h/TMuxjAsYiB/sMAM6uwofUPK/j+JjAz/hz01lZYsISE4LTwoiYMMWEeVkRpwQXMqByqMXt377hsUZx2yW7Giatf+pE3nJoidGx4pQVd/SuwkOTtnBaEETKChrpjD0Rbd6GLdBIBD+2bGPR2LG/z44wOYdvpn3DYyLRZjnFjEuRjcyMY1LZh2vpnXIsRP9uLg2OGBlFYy7DqJmrwjCpUvMoZR6a7ogZI1PV3BfdFOcwVcLZlwEPFaz4KXs8wWHNXWQqm/Ewg1IJOQd82Gb88n7Cf/Q04fsVMN2PcxDs/mGg5orXLeJo1xyakYxqeL3fQ2VW0NMiLaBtTtY9yNaIRWLj6Dj188+rRnO3AXIpXnrkptrfJ8N6lUq0dSvUeOgmhrcE1i5BzyMfewc9ibiHMo2Q0IXw5HeSXSf2SUrzsFsfUJY1X5hQyBhz8l0z5m834G0quFeBQ2dyOHmPN3PAKXR5k0597GyZCsD0hwkA+76RjdBN+tSudBhfrvSfeqYe6PxkAA3HJvXUMjMCFNNCD43u2tXklo27jKYhfFKcDcDuxR0FocYOM3Jdz8t9PzUsuThiy8AY9vFu0luBzsjvjlr5+TBtOE8rou8iSkBfNNYtJd8XXO8n4P1oaK2aryKEAucKnMtwToxDKgFbDrtKwW3oKJ/UExfDYI0vRMt9vPbGFWdCca4tyLk65MrwXiOgChTa43+eAO/3EdFhVuzmw7dTwaM24Dh2hrdFN8514QRX1IGLbBCH6dIYw8kKix60shSDkZ6UIz+74dkDzetutQmOtTo1Y0KBs5xAFXrrvYwqidSSqpZbyAycg0PhCY9hwTen627Dja6oomHFpiwbo3h26dr+7O4FoOrbZmXPZC4eU447iG9ShsolbhirP1/SCDKc/ELbDpoR0TPTZ+nz3Sk+f7I+uuqhz64XttnxLdF6r6L7c9WQVjSWrAK2J4AtIh7ZLM8lNWySdJhrNWhtwMetEG6cc5Kv6Bo7cm2Ea3at4LHDK/J7D16aVMjzRmuRt7iONBh01K9T5rCDdG8SA9+vzyqmDbb53PHFDDqzdeOWYQ2E4fYG1pgiuTpYI2bT3bDMctXvlQokMm1qMTDAHqbZGQBSyqG+yFa0RNww1Nn1Ah1TLFxVZQ6QlyVdTYR2aLxe84IslLPPsmKLVqRjDACbsNFnLPO645bX6lFK53gXJiQt/shDS7DoCmLImDkheIL3BaZ/ElwBhb6QUvEgig0bb4bkEJY3xg5ot7GYaFOqTtQsk8f71FkQpoxnutLtXQKqqNm9IrmnvlCMi7sqv7xBHI6BIoncq1LdfkweWasGp1ZE05v5SiTnsFaCr31DAqygzHfcGKb9AowFRFLo4vQ/at2NpIJS3rePjIjOlLHConvpVbTXrL0mM+NDqZDOR16SpMPG7BzDe1M+zHgMseHnwLjge9QkG5Rcz+J7O0Ub47szgxhcbR2rAKPJhmfSydZqbvIFfsfz7gyqmsSDtabjgGwEUddsi2QOInNeI55LKHgzScWkfC7JxsJCJXTV4CORWjAnyu7JWD7ikds66Ywjk2s2XH12ostznJ8WXVqEl1g1eDbCu8S4FUnweyIsXvRfXk+9YfURw7frMIgscxcSHLnpTTtKfycSVPsGv9b4opDLm6lfrbX4uhXCh0w7b+QSpNrPU8e87BjBAVEf0F44Hqi73oF9dwfQvPDoulSuQSaNz6vJJfN2TiE94wwLa0Qm1ZoD1uGRjqJc9ueiHXTkGrpXY/0wKztsmzaD9hXeZ8zTiqB608zica/bhPs6Y92V+NfWTi6ErLRKwR3b9er1TyoXPIeEx8GgW/HRVgI+bNMO7hixRfuu6YmbQmGq5pEQXN2XMo+aFmPCR/B1VojG2B+yKb+OUZpbo8NDW/F4t034x/eI71c5p/UCfQzCfhphEaA3Yx7HkY88Rn5vk8dW+sYzXrd5+ZKMdViKVzpeDzlPITfNfHl2wL31zewUz8VX/Hze8DvnJ1zmuyoRVjA7gVxuC/7fTwG/uosWkeR8aPD4xBm6BAIWNH2XqRUM9X6wY2ek1k4QfOiQVXGKCXPI7T1bS7n329wwdKfr4WHaVBOowPsCyozgw66AyHItvflGn4cOElWdfcE9OHgamq3AjKFr66SiV2xW3ZgMWrEkeR6YLsZ6m4ZNW2xKEUw/dDw/ab/S0dkSvR2BUR6COBkGvPSNvwsNjuyUVPb5ujYXBifGkej8JKJd1Gpe/aeB2efjC0IufYFV7jS379eKH3PChoIIhzdhwi8Wh98+dY3pAgwTphsF6G4nmuh7mdPOy93/+1iQwBD99D/Zor5cMWDBCQf9VUy4hIQl5OYVz0GaFs8hIYSCWgkpBzytizBLVNnPGhmfgiSH5phe5AYbD73UijB4lMZmYSY4VzFPqXnyjhghFMzTimnaEPRnOQdcn864rQveXs/4sM0qRtS9rpFHXKooEd5zEEbMR7BTe2/3ap2RJMQMJJ7kjxvww5bxQ73jie4oyJh5xhkLHlzExUvfxp6MljBVmENioB8jq9BWk0mCdxWXacXvXJ6w+IKnU2gYunmxo4c1RgIWao/5GQuzzfQK9U/oayffC0NGNowtyNmxdoMX4z1Gc5VFWK1LODuVGhBhuavKv26VcfbiFdehJP6lYca8M8KoJdCik/sXwaiA5DqEFl1FYJEP7k6GUxJ23OWaPqamGFzFKdxgzaYdFFpRkTg/JHRNQO+uOLq9k0jSOH2uHr52I7pWyVnYc7Hrtnxa1Gbq1od4nK8Gw06ugoIkmcc5O0bnNsJAXy2VAOd2jorT7zATbIuWfEm3B3bsUXsdsG5IwLtNorCk9zk7h0twOAeZ514dz7UIUnEvjK1WJVMQgkYAs9+r0v7U+KKQyyj0PjvgtxbGzxdCZamht514ckUWXRV+rFX+WRHSLcsuHVmwP0ciSW/MheaNa0hDxHBM8F5Kpc9euN4jU2GkDQLKSScrw+/fMy5uK0wZEmmLei8PpbMObBR2uG6zPosByhgWVPQV0Wec8ibGW0NjK/eP4Q536hsCUTf4aZ2QgKarTdCCj7i15Gdh4ZxLKXu/PrseC2nl2JZT6N5eqYQPaYLDpOGtswwyHiMQXcCrckGql+F9dkbL6IVY+Gl/F6ppL3Kx0YuleqOQUS+jJabb7XA79ziSJtQFRhOD4yB870kToJMf2BJ+XyUK9AgS6F6kvFtqcgRlkA8wAaoPWfRJir5rYeDsG3kQiaf83bzhXgWjrXDNCGSNQhMzcjHqKMEnibZGsbJxjBo0wkPfa9zvk6Kd6WLPvrZWewyqHqkE3DWiNB76u3XG9+uC79cJT7lHIuLEBamGHjpICJ+74CFSa5JiSd4xerbPR3qwXYt3Vb3tvmalYCniffb4kPyuCnWkzlr0OI6jm3Xc5IjQpK33Vd8itnUJkkfJ1Q/zXuaWyYwUJjwRaUMVxr1WZBYVyLP3gG7es/98T/3L8dDxXDBn5NN2z1r+NDnLFvq235MCFau8AqBhv3SxMZrTDEnIBarNwB/P0a+ltpZdIwf6SOcCxDBvlnQcmi2MzaCdq59MrjYjpVrkx3JihuiYECmm6QtCKIiTdGx33qAYj7RO2LYJKYeGn9dKCL7g1emKB52YuUrD3psqM5rOBxHgdIM6dkR6dl1kYTAafdSkbycnImmjfvWqjXzfJcav1oJVmQFRmQFSNUnN6J8KqRhYnyfMhFuKeLvN+Ie3GT9uYjgJQjWdGs31gGXyns8MyOKaXMHDR96LyRebAYwKZ8imM8wJqHAZ90R3M+j2GaNFDdGxbqq9QnCvnGgdb+R8i6vagNu0/LX5BgFGgxsptUQ9iThWTdpLs/dWqoP3BU7vkwDAag/4uQ7SOI7wzVhVGl3Ft9OKS8jt/gXzLjjFBO86zMO7DXJUrOwRuL0HQFU52+92b37S2oFlIBTY84vO6YbScwLteQ1J1LFxyJ5lQo32ankM2UCthgAY9qfdKhkLJkeI1en3IjFmT7iwg6duxxZv0spdcfJzxp+awqKGC5L9ywwD4+yBGESH3LDOCpGhbKJXUN0SJ4vEwjWgZ6+No2ovUZKetTU/sEUgHkBfEBXdOzd8elQnnFLEVgJiDi0htLvX2gW5xt6W++dhn+8/q9W1xgdNAEmxWac0Rs/yb1YsPcbU8PZaHe7rLPDPOouC4oBJAh3bt3N2FkyHLQqbEJd/1j2nsGyQj2HwpIdnZyM60QJ3RFh8aFzxERM2XR5Lhn9MfdKhN0WpmjAz3ZZeZCad4x98xUNMrfS8aqL0SfF/gY2gcwWNc3wsPJENq+PkNpdMytYB0CaYYOK2uBhiAGl4x4bFd7ZOv09qQmHyvN9nj7eps1zkOaKL2FEvqhm5+GNkapQ+ucehUtT1vgFjc4q1RpgUsnnzllPyTtDw6CUHM4eEGHJrwJ1ywC1NWBVq7DBX32iaDjuL8udZ59+izkmbP8M7Hceet94jtcSd4pRYq2SrYN/WoDsS4JXJMvZB6DIeslm2ylKN2uxdAkM0jb4GgA4BW2Xo6Plbg4tbGd41hIZ6DuZw2M/2ztTnJke/MOQifzfPHLCO192zi9Sr8ARyMSW//nDNAACmkUG4UTcU1s3Fx4KFujcUNUyUkndpeWyF7E6986OHDkj1Ze+eUzFNG2ZaG33KJmqtTr/blQ9Zz1WGENvC1WfnG4s8hmvwg9zt/pnqBpAicvGog/GZQ2qNDsbraN1lBtjlGYaq0FMMEtaO5zPWz71ISGv0UEtMY5jsot8j9QA7Q8mszcG1sS4zZkdYNblU1EuvtSfV5Fn063upDdkILwBdk6UCuOWAtyng7SbU1632wrbHKHDPog7AURvERq2EAkIm4Fa6VvbIcY8kWPO4YctGJrNt8kZD3GvV23s/Dkddn/wUSpNxPkYMGK7BMOOjJw2YNECfJ13bvyLo3BxlcsfWdVPIoic0ba2wqBSPdZskAqlOBdzGOQotEurDotnJF1VafJnnzui9U0ftpEKMqlTgVN2umQxD9WCG99jyCodiwBHCy8N1jJvLiMkbe8nVgf5I6NfF3S4ZnDhGjvKOxjzSfnymDd+NL8pDP/YUBbrsahNocgKbRGJU9eJYQ2x7QIEk/Dcpyk2F5LMqvb2ZPH73DDyEgt85P+GsychoRTvThhjSi0bSRsoB1/sJPz5d8G6dcS9Sir34jMd5xevTFZfTFXPsBq8X9dDwmcgBlNLpXbahbDk01op1NDni9tIirGBsx9aOXRyySgTkHBqtbM0R123GNUmy06rfLDJ5PSg2GnVtUwrYSxCShfW5OLxdT2IcN/EiDXIJ1AWWTHRIjsONHtieLRMiScg5e4EmWjepAYqoLbLQ1mohwESVAokxP/k9bc8SeKP8cAVpa8OKsw+4T73aNJJEgmNzij5nX15iR++xeYtDQUuFVKzesoh7WaMVQLXRWTYr43PHkPFqueLPVMIlJDzl2AyLyeHesscTlDvvhSly9r1ZxjjqwUBaReQ4HBhTLLtmKIBCcK5qE+u+KYRQ4H1uxtycmfs64/unB/zyesG7FBsP/RQyXscN57jBj9GcGulNo79jonF8vgavnDzvHCh73rfiUbNvTBxgnz9puSCFaKbBgSoqpmbc+PF5JxgV+blYFqkTyoxWrW48ckvGy7zokef4u8dq9SMD7NeoK/qyHvpmbZto7HwtcIl5GtYNZh6SI0SExKbtLJuAhaNvpi4vaom3s094MyV8M9/xMG1N0D/6gikkxJAQY4ZTloJ41B1/BnSBXT7g8fyE3zpgeGacb/cF19tJ70k9bl9bsrLpvoSMedrgvZ3PIaWAlCO2JFWazKSLKLeOQiEYFa0Kvc2x/lnBA9RjEUGpwmY5TyseTzf4AV9sXrrCRKPXbtRAE3YCpE1XqRXFu8ZQsJLwCi2y8T2PYV7IsXijZCut7hPd8EzxPFmpeIzXyiCxZh02TBHwbJvQAA0A6tUrTGUUvsaj1s5ERqe7Fod3yWEtpuPDWJzD5HsEJ/cj44hlHn9u1/OSHrwjaXx8Cd0iLJ7x3bziQQvKxnkSvDCoLoo7W5hfeKh+hDEw5D7Ni7Y8z5gAHw0RuY59H7+bq8dWu2mwZ8cs89AcjFp7te8YWdj86t6uwE2TGsdcPbw+FOubes1BW/+RHu+5To6xWc4hN92jY/5rvEeDrG45tIYbYPEKXXXYgJZAHo13f28dVrS1LrCaHD9QbQlOB6OmOrxL1sJQnEv7vZGZ9BKcYpHsVkVE8I/LFd+77/GEt6jPBLhfHl/UQw+6CgxycYpbZRb6GkMgyThoNjiSzj2Lqyqra2JHfUcrLHohDHn2t9ALXgo7LD7Cu4pz3BB9xjRtOD9cWwl9rR5piyjZg0g0UWoVD9GMbn2p1+ZguKTIguHLSBUTA8wh7x78yGwYw0Wp+szNmBtm3iCc7FDgMY4QZAMYhxUm1cHjbufwZbexhFL6xqNd3M0g5OJwS9IIuNMExdv9+ZzwzdSTgluRYp5t0NoxLNlZ+DGMwoRSgDss5Be44BIkUhjxdKN/XkKCJ4aJcxkuPMq/tuIsiBeYYU0cXGO2RJJI0OC54EyZsVcLrpVwy65JTdg1LkqRHft7xmEuHIvaTDPeDL1of+xzBfZ+LZeR1HsdudUmHGaPcQkZp5BwntYdvJaK4Nm3FLGNjoLSbeeQmopnKtJU46aR3Mj/HqMcewcP04bXpysez0+Y5g1e4bh5Elrug85Dez8WOZF5s3rs2RXdtOouIhzL+ccE98i0kucxwE1ONijWKFQqmgXyIupGmfTd2LM3LHzT9nmmv9Pfn66lMaphMqXqYR7LRvQYpWLUBLeMnbTVrvsCCKHDhLuCOqAXEL6ZPP4T9Ajg8RkU89ee/n18bHxRPfTHQ7OcvVenoQhb1llbcI1GUzPCTFAtCvsJ77yixyDtx37r/ITvLh+wxE2r8aQqM05qAI3W5ArmZb8jlhyk4OfGYhyLa95NCNKX07vakpFFseykDBgr/mkSuINHwyxd2O/rjFuakIq8Fk8Vc04Npwze7Qz6SHMD0Ay/Dwk+5GbQ8hZRa1Rc3zdqpeDpbifd2nBGV3GetvZ5KgE3RGRt7lFqN+rjIOL2jhgdxxy7Fo1l0/a9UV0OsF6LVlXrm2G2c5hRB9AgpKge6qzFIrtnNWzAUlnLmihzWJVqKcc1x0K6VdntSevB0mmRQDMEFvJbVND0OIbn0tgvrEwtnWtZhbqs8nZ8jiFIsdklusFLPRQGGfyhZfhWC+Fcga89mZ5LbwZt0sOTz60QTeA8m7vUKqNtjM9OLkQ2xVaxnCK4ElKKXcJZWSbEpBW43bmxYxZ2uJaAd9uE9+qhE3oh2CnkpsFj53/ioTWiblCmVrqoeqQNpzClH6JY68lqleBtDbLD5kvXuNFrtGpTU2K0dzvqFbUq6MFwb8Ua28vGfSsFdy5YkVDBmBDw2k34bg6au6mNni0J3q4++qc+KVoq8IN2LDLIRag6mnkesNYwaDkAz40I0EPeRt1CpwpdQlXvoKiX3I+VtRlAS0460ULxMcHHDB9E54OLR7hPmKYNy+mOkkclvj080yZtTC1R1AylVn8GX1qhEFCVJSDFQq3BgZdS/hgTQswIISsX2COnAOiGUYqHc4ykG4NPoeUDmuGugq+bB1RZFuOaI7bc+zwaZXMOqoqn3s2aK0qlljvo+tNCzzuWXBur4F76hByxwmMfxo07t7qwNOk+eUm6loHlYIbdDKAxcOSHHshom6fdj3daRTssXpMlLvrejUlh134zLw0GBVZcfMU5CO7aKYZ7Txz6TExuIJv4FqCVjJL8NS75JQjd8RIyvjk/tfkjkZMYaGZC0F6InipmVSw0NpPMPUnA5+Jx36Z2LZWNsnp7tnYqk+ZL9sJtJgZnn5kcwDXH1p7PvF+73/s6I/iCXDyu29ykmu/adKPodUqSubb5Vtpz39dgSMWwSgvoszaBs5s2lb4X+d3JVzyGitfTpptI6YQCdFbbvkq8NsaOQC6EnN1OsqEM75Z1/hpzzq7X8PJjfcKk8Ar0dxMDgQLm6pA4oDJr8ZA8B2kq7VqOp3A/7j8VeuiZGX+89Z10dlJiew7UGkY7MBYvuGN0vQhCvEMHKKvCFh4AEeHJru2YnoC1yk5f2MFRRQwdkmAWryJpkwjvK0pMmKqK5vgKqsb/rvBTwuIrqnohOQfkFBr3uwxejXMMoto7Cun1O4VS3FCKzpVU8rYb9H6NDmmbkHSh1sHAhYHx8jEta2O+rDkijbotEANxinW/0EG4pwnXbe6hJgu2Gl2BCzrpKuFJq0rfZymaOYqfTeNGjC5jPErZilyxKAhKLkUW4WMo+HbatCq369CYhxp8RQW1TlY9wiOYJLPcV9FisyEh2OAblcwVXiEmYpwOaaiRtULt+N0z7w0URmMpfwbH8O0+Rb7CYMLKIiT3OkqPVFHT/HQKrCXGQ1GnQJteA3sRNnbdmzeq4YBx27W+RA3lw71MurG8oesOPrTrkXdLLRIFgMUnYEKTP7BuSOHAtpl8wQM2eIXXxnzLeK2mT+5dxYWk01N7P9S1XSoId1WbrENEcgojy6Wq7EdphUrEhNmL3IFoM3WYr+j5zXMGgHsFrsXhhxX4k63gqjUVHtKlaXYkiqLO3r0U23nyzzjrojQq/4ljeXR+PjklduMLarkQXo80F8iu/D4N/E8nLeK+Yaea1dzEud4njz9ePb5fGU9ZGC2RCJfg8BjFOFhydXbSwuvdOgN4hR9vl+bpPJyuuJyvWE53+JDB7FCyR0oR99uyo/4Zhu1899BK9s3zNQ8JkEkmucNOZyNicMgt2WYJzXEcqWoNCjnw249RCjO1697ShJz9TuMlhIJp2p4Z++PiLcUjZfHarPO7jQk9aQxIz8sKwrsU8ZQd3m7Sti84SUpfAuMc6qG1lt4XnitfjkO4z2OoLRBXqQ73FPFuW/Bum1rbNRNUu4SEU8yYXGcbFXbYUth5+tYMuigcANe1fFrDhRc2yOdVBs+HYO6DHjo0KjB6JTqrqzW3oKEq2TFQewWkdxWBS8O/x8K2cWP3yx3LLGFvw/CHeWnSAiO8tq9OtUR+aRuHebq2eRg8M9JwRxivd6FyiFya123a8Z4q4rB+yOCtIcFtY4S07Ppmek7DtA5Wx2PnIv1KU42t+5Zdx6I5CMsfjeMl6QPHpPCbwCiTA95MBYt3+G52jaTx8TnRlRPX4ZTSThA4u05pHFtmSgPpz3fRv6iH/nbw0KNzWJxUSFlfvqDJAumGo00HdAI+aGean83UsC4JU7iF+k8ZcEQokVTbRXRAzpNwxqdpw2m5Y15W+CmpxyxeknkztVaw0cpYWSHrHrs2qGSZ153xHlkogE4MX+GCQDnOF5DjdvxavHj+je7oADXSJfvm/VtHG9tc7BrcVLGc7u3cANqxjYljkYUcfziPjpRCC7OtD6ldu9EVgxsjK6EJnrzH6unAVtIqSNor2gFAHYxSVikHofPJewwEvI4Ogbg1z64K/wRfm+zrw9DzNDphPhjljkjU/7gQtqLMnSGhmwZj1hLu1JtWxKHPaKML8r5Yyrx3+7thqxa2b0PREnSeOnTJAKNHmgc9zqmoFNgQ8q6eIVePd9dz4+cD5nXW1nnKEoNGhTWZZNbndwoJD/MdD8tNoBvzaA+1EzaXzZg713MTsORy6TUVhV2rQH5KE9biG869+IIFVuwkTzEzduqUxqoy3Po+cM6jE1nsN1PCm2nbJb+bTv+A0xvH3hpQ33VdTc76wDpckHa6MklhOJNsyExCD80OT9lkeA9UyGE+2Bs86ppb16KZnn/fcHe1HO13CBK5zn6/wXxqfDGDvjjCv/i4P73tUF1zuReLtJZtgCgu2u6NrgljTRaEi47GiSYQLsHjfZpw2ubm6QDAabkjzBuWV0/CcmFCWSdsN9FZSSm2lfpSmOrMW6n7hg1mzA1aaaG0G4qPioexkWpxqDmglj0TBQB8yI1BYOMleMWHjLBs8FOCn5JsFsUhXResT2fcnk5Y17lX7ul/Rq2UDkdyzXNLIPl2z0nZMoUF5CjskNlhchXfThmLP1Dj9D0mpjbTe1K083mJ5L/HqPROQA25aO14ZxBPN16m1z6+B9tsxuKswAXRO5zjBqtOBCS6WEvAWgK2Q3LZ7nfj3mvVZHiN1mbjJefJ2p1JYq+/N8szGLZuFaIv5YQAiQhjRINVgF7HEFxsBWpyPx63NGG7+2a4ibixX37+8B5TSA0GHOsa2hzSjd8iNIuKiBixBASVseja5wIfLvPaYD+5NmFHWS0DoEtIIRBJrMqzTVU0+U07ftOocnYVj/OGS0yNV98F2ng3/2095iIsJpsTuXo4MB5iQnBdtOu4Ybcohyqc551KZqmENLnGfrENZ1TmtDzRWFRnif+Rkri9MF+s5aYxXMYioyYb8GuUGH1R2uKIrxp10Yx2L6oRiuKkrcIkmSWYoYl0mcxp5aGKlC0p2kuirTos+NKw7FI88jphe6rwIaMWj7IFrPcF2zojpfDMaLjGh+edkc9Ds2HKjBCk87mrQ5MITVBWZR2M3vEROzdP3PnaPP1miPVncHvDXosH3x3yfe7HLbJIltMdcd5EImCAekaYpxSPmKQj0km9elbIQnD1CatuOiYdvPiiGOi+GMMSgx9rLLDzXogB2pfeN2gK/OKkHhXyrPrQio94MFh+YL2YJ3pPE3ilZkxylaaFXhkQNlfk3crmZUUto779qP8xerTHsWMjEYOdmDSLCAjc5gPXXrcQDhRUroRSAmLIz1hOo4EeI8UxohuvZ4RKoPfuVB/o9JFNZhzBC4xnCXtyjLQJJXjN4qXfiXceaamEDKnKtoKio8ia0WGTRjqjrszIb++YeJcmmHzGZBr1VZ+D7qnjRsxMuNcAlJ7QHhlMI35v0I9XaA4APAsNUuCbfu3GYLIqUqNSixZ6Lzg6MnfNgBO6127FZ3yAJz/5Tj7va7/5sRbGf/jBggxqLBdJitpEJy2LJkSWpJUN6RUpsrv3YvxP+W/UufAEvI4FP583fLPc8Ob0hNNyb7igMUfKFlGTbxi6GQanCRPzXEPMve0bAFaYxAp02JguA9zitQiIq4oM6ULcbQC68GgIGZmpJVtT6rrnU8w4n244X54wne8I07DodcKNkAuqQ8myWYVRF0M59zx8Rq4iRIkqpgFqmnPAklectwlb6VWoxoC4pYhVec4VQKgOvngEt198I7/3yO016mIFNYjNNg4z2CPrwuhlFSIIdQoJl7i1Ljo9Aimt2lFgNPE8txLg09Rwcdbji0rl3hMnNRpjBDnq28i96aKuo5Hv92f9QMeEsBRTSSGPcKVlrtTipB7iHpSlZHCbQB8xSq6g4cUpYF1n3O7LzrsObmBKDZRZM/Lei3Pw0iY0jp/6eakO0FoNi4BnFec61meMFM97Dg0SeZ9doy0Kv9/hMTip23C1OQqJDT6zjl+MU8hwkPu1ZDCcKLAiAL52jvsocTG+n2NjCsnZaPVo8djY2gV2x+UYXdXmuVNDCQDBz1etYBceuti1s3d4iISzk3u2uWi66saQ+tzxRZtEj8MT4RyAV1HwLaMHNc1q6p3cbYe+ZcK7DXifq1aKyoZw8oAP1LBbT2jSsafljvOpt2gmYuQtIm8CI5ghnqYNp/MVNDJRihYZFYe8KuOEO/a9wx2546oFWkwDKGc9gzS5ZIa+5oCUAkoOjRJpqnuCpyYEw0t9Rq1i7JkJSb1xrnsPxO7P+QryRXD7KOe1SKQWYcDY9Td+vGOhbOqxisoJfFgXXLdJOtNAwlxLMKa2YDr/XCrrGNCFzLUXHI2cdJNCNi2X6AjfzR7lRLjEJAVAvqBUSbYV10vXxaCXrmw5QAM0eOajUcrV4/0645e3E361RtwK7eiJF81xjIVexwIgMyzXYnz5HsmNkUYgYxERKgGFehRpxsFYIE7/A9CMuBWG9cWi9zdcXwgZ87Li4dV7nQvqNCgD677OeCqyaQRXME8bTsuK+XRrWH1KAbenM56uZzytS6MzSl9bzTepjPMYmZpxFq/fYR0aowjUYZXVaGtmNISLL1rhvWe5AL2rFCBQy+wqTr7g1XJ/VinqIDmTD5tVkZPCatJpzKIwKUqT9nsn7dVruQnTNrLaBxt1uO5R28Xuy+iGjoSOaIqaBpt4ArwjxEpYmBuNd3Iy75r2EWTT/5CBt5v0hrhzRv1MAYDPMuhE9JcA/FuQKPHfYeZ/84Xv/FcB/FWIo/N/ZeZ/7VPH9I7wZtJKNHRWxEnbQsUBSggWKtu5IAvvm5kweYfvinlwDLSKNvlu65JTnXh129QWzrysOD9+wOnNe4TLDW7KshCyQ90m1ORRkj4iC+WZdobTjHlVQ3z0RsYEpd3PaMztmCV75C1iXWck9dwbQ8UXhCHhar+TU0ROEbV2DRD5vWETYoecheVgXn5bjOgGyoz55DNOyx2n802q/6YErqQKk7rJ+oKkcIx4WDNyocY5Fn56bxVo3qiFlVGLRsZMwVG3QyCcgjfTileTdpVXD9u8T/k9pZZVh3uR4qlUQoNYrJgrNu5/Qc4Bt23CNYva4odEuKpBt01Jng9AxTeu8uzqrsPNOEYDZW0GG8vFDFhx2i0euFdrZSf8dItiyHGL6mJMuAysDZlv0tFqWydcbydUjdqCL5jnFfOyIsxbm1u1uBZFHjsTtdyOfnciYUOdLld8O9BbjVJbimwsOc+76Gek4OYUkbLUR3zYJnxIU4NMjJ0SXWnXR6QiZQ2yoDZX7LmPUIjpsIRjKzvqOkMvvZ9ncNNAYU260TV2na5RERD0uCaHd1vE2+Rxy133xzTVrfzfrn+UuyB0+OQpM97ngg81oaDAw+NMAY8h4DF26WhA7OFvnxx+GxOEF9XHf3DDR8dPGnSSvlp/HcC/AuAPAfwBEf0tZv47w3f+PID/PoB/mZl/IKJf/NRxKwNXFReWtnFaLAKZ3KaPLp65lWLv4QgL0S3UhWJa9pC9wi7BWDJVEkeWdQeAeVlRi5cJRrKYBMyqAMbiIam4TLe5sU7klMpcUW8p6AI0j9irN26a5TUFlCTGv6yxQzTEmOZtB5+MeLt4afvEmAlxNWMcM6a4YZ5z61i0e5eD0bdzyp/777FJmxaHcl0kjLyd8P7DBT9eL3i3LcilN4i+F491MOYjc4TRwULzWK34yw/vss0L9IVrZfzBizBUcKUnAavHLUlXJdb5MtWC4gmFS6sWJEITPjNPvVRp72aVkMFB2BckqpxWpHJWSWUL4Y1NMeL5Pck3/Fs3mFxdK4yysfgK7wgPbPmHildTUm+TlYUk1wpfQKpI59xekM370mFBjeLsWUok2Q0mhQw/cePrc5Xv1OpRctx52Pbej3RdHwqmZUUIZb8Gi9sJzQHQxLlrPXhL7QqIvbhHDSC6ITc4xeYOMb1ozAHAeR66CfW6BEuK97nc4ZXd4N4opb07JpBGSnbsUZ/9EiSBbhIO5gz8kKlJTcsc2A/Rwhe4pTIjwsGhK05uteJ9criXTuIwtthRa/2nxud46P8SgL/LzH8PAIjobwL4ywD+zvCdfx3AX2fmHwCAmX/5UwdNteIfr8KZJRBehQhHDg/aRsra05lnJLuyLKxCrJSirsxoIYxpn48h71k7E13ihvO0NnqhaZ7k+wT68QEu5gZ/lFVgGINZAOxgCYMj5H7V2A8whw8F0yxKD5IE4wFfr3CeAORGUfzYqNW3RdNoa8o1f1oXXLXPo6eKy7zijRdjfnq4wuv9lBxQttggHUByA9O8IZ7uiKcVTsPuskXc311we/eA6+2ElLRBcfEtBF98QnVCUXOtoTBpdV3P9I/NeeW5yASVNmJ70f4xaWot/5gJiy94pQvNZGiDGvqm4a6l/8boGPVJDOe/pklYOpoXuGsVo0QCFXOVRtMnLyG9dCwquwrldq3oVY3CfvA79kvHeQVeGu/LsHW7XyLTlenGekxwlxx2EaHz4rmfHp4QGtX25dGMWXENgpH5xwCq4ui0g9bs+x+LNLuAneSLrLDO5qc5GNFnvJpXFfUa4D/qOYgRuzbSghs820ZCANrvj82mzdjuNpgX6jXMI7frsJ65Yw6mXccgWtbnj+RJ7lX6lo4SDkLueE5FHGUsCECMUjA5e9rVZQgrT+BGi9jG8U+iSfTvAvj7w7//EMBfOHznPwkARPTvQ9zav8rM//vjgYjo9wD8HgCc6RG/Nc36uVSKLrotCr7qNckxZOvRCzuCqzh7adFkiTbBYqVi8V5695w3UYSNZpX6BKBUPUbMG+I4CZjACn/kdWoGrSVF5w2zet6ATP6SAkqKyNm3AqCo1MEwJfhl7UnRDahbxHZdGnZtizTEJLTDYYHZ8b3K4XIlxJgwzSseH943Foo8I4GSavG4vpM+PBa+O18xn+4tAUYWcpswl2K0XAlhSjg/iOCSLVQL9S1Ba0nR6zYjrBV+mxApNvnTe3Go2h6tLRjFDiv1SQ/s2S+GUaLuGxtYIi8oz9MpPJRrZ0EYI4ZZ+NfjsN6pZoiJRMvlNlT2lkq4QeCSdymAaGlqf4uruMSEs1f1R3UeHDEmLs0gN4pj8di8eOh2T9bEeKvYSSIYNGPD+fIsOmuqn0hA1FyPJq/JVY0uFc5zDDgGZ4dyn1FvszC5Vsm5WMcrHxPCaWubudxAhxZtPhjFtibfkuj2HWHi5DZnvVZdGxz3pHLQHbZSD5qsiEbyL7ficTUtl8Exs9wZETda5R0enDoMMem7eUV3nJfU1FSzSltctwn3EltUOTJljjAUaoVTEalGu60O1+Lxdgu4FdrJWEjyttMojTptOSGDG00VFkU0gqSgSs9LQuQAumNq49dwzgH85pKiAcCfB/AXAfwZAP8eEf1nmfnt+CVm/n0Avw8Av4i/xd/M/XKNoSKa5q552xNLD75CXdiqQji90qNRJoE9COtF+Sb2rjOPoeAXpyt+dvmANw/vO8tlSpimDc6MWqVmkJ2vjfu9oyauE/La76lBLiFjjqkZTOcLXMxSsBSzcsK9GPTksd5nrOuMXHzDyg2PHPH1I13Rrs35ijilwZtzuF1P+OHtG7y9XfBeuxPNPuPNcsPPXr/FN9+8xen1hwYBkV6jXR8AuPssmGuVazTjbfe/i2xygM8SyKcqRSDG8CBinH1tmzSApqdhie6+CCxRKhRU8FD+TCPcUeGcA+fOsDHYxBFjDrJhm+BUez8H9lCtHtd1xnK9qFZQxFacJqmkAEZEoTq8cGzH1+etwQClUSKZCSUo7KKytYUdnnIApYBcA4YppPc4zCViVFcbG8qUMgGhlW7bBHr/0KocLSI0/DxMCRSKQiv6fELGrLUG5AvCJM5DWDbZ1MdI5Cg9UQlgh5odmvBadRL13afm+FjRmlNigXG9vefu/aphN42grM/lV2vEn6wet9Jzag9BmnCfPTVChNE8/bAmTJiNCC2fAmibxSwtFp/S1PIjVuR08aL9LvoyJkvstZ2iOApblet7uwV8vzp8SGJ/ogMuoeeDrFBMNmyphXmXuMnnEoS0IXj7HlpZfC+gfGkc9dc/NT7HoP8DAH92+Pef0c/G8YcA/s/MnAD8h0T0/4QY+D/41IFHEv2uutD3IqIRP29e2AEPGwXhVyY4FlnLMVw7pwmXdW5iR96p5OlpxennPyD+7B1oruDkUN8v2H54xPrugnr1z+OgYWzbhNv1hA/XM67bjDUHeCca5K8uH/DmzY84vfoAP6dGIQzLhgf/DhfzihWz5tono3HF2z2/EFrvWDWOcXl8wsObd/izhuO72kJtw03XD2f5vi+I84Z4voNCBWmpPPkikcW8YS4OPpQG89xXYUtYEw7zwkqVBXdR3v2xy8w47Ccj951cbRTV4mVzjk4KQi4qRGUVivYyquKf1uJsCRnBFTyebni4fECct179aIlrjWbEQBJOYcIlJKTqVA2QWyOGJeS22OVZo523vfsa8JQi3m1Ta2VnUeVDKDjpxjD5gsISEfRqUgmxiyOFgfa5BNj81chwjMJGJlLf5ItEc8pcGiuQLSnaEs4AiCThHR6ucMsmHn3y6s0vyNel5YlMw8jPCRQziESsrqaA9bbg3Y+v8P56UXkBSTJaZSrQiwXtz3GTDCQqiRfvUSfCUnqjkZPKEk+uy0qbUzDCMcxST5Cqx4dt2m2+pso5++uOy85aqLbVAFSNznLEuxTxdgu4lt6cxOimAHAKdly5xqLRaFLDzZD6mu9m6c1gcE+qXa8lV8ZaoWQPjUzburB51guVfp3xOQb9DwD8eSL6FyCG/F8FcGSw/O8A/BUA/wsi+hkEgvl7nzqoJ+BN5OHf3JoKHNuHjclQoGtlPGWPt5vDrUCNQNcPuXgpRpJkRsa3yw1vzlc8nK+Y5xX/X+r+HsaSbUsTw779GxHnZGZVvfve635qURBFEpIhyCAo0pQcCrREQ4YEgYYgCQMIICCAEF0apEPJEECDzoCgTUNWAxqBlmjIkDADyOJYBAHNdKu7592fqsw850TE/pOxfvaOk3nvq5Z6WOoA7q3KrMxzTkTsWHutb33r+6yrmJcb/LzDRi45h5ipmbYtaOZ9hgsAsp+bN3z67qcDY8B5yoLctMPK1GY1qHtAaxPybhQf1/fkzAMAwLzb2gjTr+svq4gY25Rr7HyGmxI/5A55jUj7jPU2KyvG+4x5WdGaoR76zPhp8j3TchUexNO3trLc8I7CwSEXh9fbCeXygOd9Yr2MEXvt5yXXb4THxsV6zwGuFZgl4x9gF9mMA2PoErSjJ/lYO8gS1NYrrsO1MuJyY7GxCcLKGfpqGi7Z9ywQMvVZMbsuzStSA+dA8gcfBxxcuNelGXzZO71V+j1PoWBxhK3PtuG7accpJB1EE5irZMrGxU5QhoAE7honX987nK1YphXn0xWnhyvCsnYKa0ywU4J4oKKwGN1EZi/h6aLXrmWrAbxeJ4KDikVi6q53BadpIwZMccrf3o1T7NqAgldrAAxUmsC7ilPNaJFUE2mDBtZq8Zoc/uLmD3MmswOeAqkrzq5r4IgkrsQK2TyIzlq5IuAGsmkAZEIavDah+PnkCjbuCxXuhVxyl6YQqHCrZJM4AiMSpYSwMT4LJJErchHyfen3HZ+TBKLFiuLi39hgUWstG2P+TQD/KWhz/49ba/+5MebfBfAPWmt/yv/2PzLG/EMQ7frfbq398EuvWxvwmvuFCJYfBCdTcjwurP8DDc20PqjShsxc/2wyYswBBIRRftlm5Grx+XrSbvjjfMOn62d82AOm7Rlu2tGyQ1knpOvEOuJOcW5rC9ycibliGkRWV1grNXvUZA9ZkzMNxlfGRZmW5ir8vB0okSXT68j4P8Dl9Tbheluw7lHNoh1zrWUwSjaB3UWELVI57QczDA4E1laEuOtnM7ahJI90nfWzyPnIZ5CNtYDZNUyBHLnRs8/4OK3aAJMNV8alx0aSYIx76eykhr5o5V4GC/x6oqblEwtOWb7mwjUvrg+KOOZ67xxA3Tbp+dN6G1gIlWz5xuEUOUS4aebGq2w0FQQPpD0eKG9E0XyrDS9QgDTuBCakxjmbHlSDwlkeZeh8zRluMaapS1UTyMFRsz3GHX7aufFNmfl6W7CtE/YUkLOnTXsiuWdpfgvGTvh70Z6RHASvDBuh4OXVKCQp5+ljwgxKagRbz9njdl3w/PoAtza4RHCGZOhdbwVoraEa0+EmvW7Aoy14Chm/Q6/mpFE6EiTkd95raI4Q2YiV03vUIbgPw0Ywyk6S84xM0vj1kFTKOpchsVEi+TUbXArRFKWPR5uRYWilW2zKQcmPrNUuJb2zzPLXHl+FobfW/h6Av3f3vX9n+HsD8G/xf191pAb81a1f+MmSn+TJO7Yy6xZ0s6uYGnFVrWmKuW6VdjBRMYPj6ULWUq4geEam/B7ijsf5ppoW87QicoBridttlZyAAGjj04p+ZRUMPb55eAnfLTpBKhilm5Li1K0atMSZevKKkbZqtAm7bxGl+EM2Ftgm716CYGRECE4MEDtiVH5RPY54HCM3pqFm/6aBOG4CAGeXrCq5p6iOTaVa3FLEhf+7lq4tIuAIMRPodXNrAAhTrtbAtO7cEswxYxdsUgWYhqotsM584IxWsH09p9oNsqtwqIeMtjZypjdoeAw7AtvSOTZkmBhy8YyhC7R0TRHX6g/uQfJgy3Wiz14RzVsDhWshxb9bowc/N2K5pNpNsOVwruhEstBiAfSsWSA63ohtKDjHFzx8+nKoZg+NUtCGjVZRakBN/vBv9EJ9HSlsw9l5WSdKXIZGsry35Wa12Rpu1wXXfcIPtxN+2ib2jG04+cxqmAnRElheGsEuQk8UOJWGgkSYy+imIBDsZDkbR5drmJHhXN88+oRuV4aUNeIs3e/SupCZ3LvzMJkK9EE5cR8CSE3AgN7LwMDz+3nbuMFJMsn3Mxhjw1OGibZCU6QyWOQ5+C8e+G5qiPdCjj/hZ49vNinqDfDrWQIUVJBJ9INbkzF54AaLXBuuxrIlGLFZMl8AZ6lydPw6kr2nSk2IPqzSPToBytSulzO2dYL9/pM+eIK3yjg/AM2WfCA3oHGcn+00DkE+bxHX54dDyS/mGX7aCeNljnorjpuw9PqFGS3iNiTsEglSzR6NL8bm2KjxokHOcpP2bkhFznXMumqxAEut5oFZs+8R2z7huk1Y94jSCA65pIjnfcLnREM6XyPGTxt4/0GtplrTSkv56rpxHbnFIiIlMAdlW6w2OHCRi9zHZoDWp/324vC8z/hpm9gpxzIPveIpJDzyFKEzVQP1ib+nn7uJsYdTl58xCJRGevHys3uVGQtgchURBLlINRB4bUl1ZX1WkxUz0mS5F/Me5k4Xo18rc1gH7zSDRu0Y29eLvJ8BUDZqhtZikdfY1+HAdlFBrJ2GiowhE/Jz6Dz0yHMa94M/IvUglbUcwTQ493ZBWb628myGZuh5YPkH0c8vnHSIyqbQZwlzd4pRK52xdW17UVvUzzhWeHefpYEKGQcxsje4Z6p4S/c6OjZNZ6hwLQa3QtRF0W7xBojsiezsEY75Q8c3C+gVwE1MokEfHpZ2OjKyGMss+rnWqPRPzWCtBq/Z4Dk13AoR9mdngUgSAjLGLRj6h2nFrx5f8OHDF8zL3ajVe93ld/jhhtkldsx4qmG+sNPmkzxEgR/IQyC1d1orMpHHo9yIlPEAFFxDyIjTTkFJYJCRSTP2FnjKtbWogZhG+ClIiGwvvUZVdoTxe4dnssN+k0ysNxJpcGmHcxnn+abNxXldWA2RFp5g6KOWCX3duenrMCkJvF3swoR58CS4JBrc+hk5w95yIPiHp4RFr8WloezmMj+GjNlu+hrnFHCeNnzHHpoqrsTMlLXcDS0J990llRHu7k8GBV3HRlycxMW+DNdEfiYwsyLYDkPQeuIAa4lKWuCpQc6wCEk2BK2YZL05nxFCPrBcAILWYBqsrwDrxNyve9ksW7NoySPdoq7L+83Dz7smANSEpcGkkpmHnr1qFJFkR2e5UCZOgUyWbR2y5zJcK6DPlRx46KZ7x4rX6OgzmitNc+p9ad3xSiiL5CDUlTPf2xhHyQfVlgeGdULQoThz3ePcNN8wXOoBYslMzWyc8YvBizNHEsFWoOblX0tg/GYBPdWGv7x1YGB2ls0pDIqjXRfo47U0/cld8tZ3PaECNdMzdOlM52rRTOMHqGCeVpyeXjE9XAHgzYJV7fDkkXPQiVDBn52nMtiF40CHlvwMDwBj8K+Hsva9B6wmj3ydkW4T1ttCOC2P8zseFIrTfhzqkEpiwDs1424C11iYSu5KJVc47zTQWVeJhxwTBXUO8M00RP53P+1Ke5PR8VG3RjI1MiL2POB01HMZs5nWqEdyjzd3+EK+Zmf0d9aNBHaZJ/CuGz94S+p/wefB3g9K+1P7Os7AEouKXVJAYjVFMiwml6Tg8mHAJHMmLuyNvThcUsQLVyfiWO8YGhAXHeea9hVumSYMV1YIPbmGaCNWroYcV1IA0Ssbf18OpchOO05c4fULOdx76YlUo9DhyE9XGGUPuu6FTWVEq9/0pIMqOX9gTRnTiP0ysHC2dcLlesLn9YR/9PqAH7aAXLtN3Ke44yluWHy3uLMcpMkm0uhcwZt7DxzuRxt+ZjdWpajHQ4kFw/c84/CnsB8s6ETHRXTZqVrsnHWRfaY1Og5GAaIUqt6iFaoAq59FblOTs7n/Xl/z0ldq4w99xfHtMvTW8FoZowaQmlN6kGgakO6H4bLUoHD2Ri4zwOIa/GS4uWYYeyIeure9XBda0+V6QvgpId0mhSaMq9rkJK444eSvLw+4MBWxVIvgyGdzmVdM00ZB0DSlFkoWPuKPMlTkpsTaEJ01gEYPVSvELElbxLYSE2XbJ9ZXL/DOIxSPwv6RQkWU4xAY76AieTCsrfCNOfX8GigWQIAMjQhGK6JdWQZE7gwx2oD1ihNOKhap2S6AZPrkn9wD+f1jWcuYJv9bNV/HuW1cboseuGRQ0SfME4mqeZ7SBU/KjmqYtZLueyoetxyoedYE9rHwzbKxR9fUkT+9a/AQTnTV7wfrDzowcoyBaS8WX5LDT7vB896QWsWjt/A24Nc7D5o1Q7RCxtBF296OQRk4VJWi0Gg8rWeMcGDyqLtH3SPKFrThWViQTrJ9oG/yYaaKTTYLw7IWsjHI+qsiYZE8V4aswT5t+O70CgD4GKN+P9qKiQezjpVld3jqAmcdciPDjt4LEgaSuwv69xLL1MiW5mWf5JXXG++PQGKrDn91/HuUwO33tXs1nFlMsDXSL7pliwQaLOqeDJSAeoYbx8GisXckG0Ji+KXUfwpN0X8ahzcGvw7ESDAcjKN2gftFmKQUZz10YwDPanxRMVGwXvHIW+9NNHIdCXi+ndCaUSu2KSYspytlo6cV1mfNSIUGeNr7glT53CFDF6qiBnTbS+e8B6QtHqiOI1xyEFBiKuE0bwghvWm61vLWbajdNdIAcFZfNUOV6VHr6mFoSR5YcU6S82nNwLgKk7s+yPg5xj8L29wt+4RTytidUzNgefBGLQ55aFI9NrsEjpGhpNYMkiVfx1SPHqjvfZ7xqEJzHBuMYhAywFzGNNzWGQZNH3jBZmlTCl13xBArQlgUCo/A6ATqU9wG/JzOS6V/OahQMgKdbt0rNb4m28WmhBJoAdRS4SozUVhvqNVKTfaYNXgfL4ClTL0KM6VPEut1s8Qrl1kEbRY6moC2oSjfXK5X2QJLTEtfhZlZ+7GSLdwYD67gKa4kEzEGUr4tgiGTxR5BKdkKcHccIBrNne/lAOj7HYbxLNCl0re1qydKQBcWy1oc9uwOFWMF3Y9peAP1aTB4s4mY4bNVAKZY7IYb75VEuQCGlCvgHLOyBhHBKOc6QsvNHLwdvvb4ZgE9WIPfLcPYrZFSqKuXydh1MGRl5lgDgcpXmjC8lW64S+auBskaKnXldYxThoPgY8KrVr44Z0E1Nbjke4O0HsWFLPO8BecGhkA3BHOFcO6FjhjHHtkHhu9kKxauUmYo+LflCdIRh38P/hgDtWDrY1O0B+MOCbnQHY4kG3PJI99mwm85OMg5GtPUb1LON+wZS9zxkL0KVUmjSfBkpSdy+WsMSYxabiKJOh2Z8VJW4hvd11y7vZmc5xR3nJZbZ/oM5+190coLALF4toh9m7DtQVUDU/a4bLO62kwMOUlgiDarP6U01rbs8VriIRN/b+aM1m09ZJgATaE+8bpZPGWMi6v49bzjIdJGbmzXQy/XGetl0TUFEPvFhwQXCYrTgM4B9mBjaKlH4tli0U2D8FtxMAy9SDAT1kxhfvn4/bFPJP+WWSKjFE+yF41mE27bjC+3E173iFsOupl5WwnKskcZBgs2IjGN3MiAQzD3w1qW63t/iCiaaRaoFQVCVgDz0KVKpPecqsPJFa0YdX0O6/QPHe+5hkl8KowaBLk9nHlfMxTKEfGtkyMEAkZUZQlMF8Tib0WGPjY7AQnG1MgMw2417ozucOFEIGkk3hs4psLJLijvBfSgpIGxWmzrhFos3OtCGVxl8astanPHcDaREjWA9nV6k60egyZl806oizEd/l2vgWwAxgK+ADKxV452c/dHa++zX0hpkQKDn3ZVeFQKYjU9WytAtYSjAoBlelFeJ+yXGdfXs4pzAaS3PTJrjK0EZRSHxMJdInYEjE3RQaIX7NxSrQ4Y0b3kpugwFBRMw1MkwwrvjuCoEyXLgW0kcJf0LvTa8mfIPKQjUqlZNK/BPGgViKLG5xwSDSpJQM+eAlGpqglSahf5EtkDYEhCuHHnjTAaiE2jcrp8TcYKRJui6ElNbUbPhxIN1uTfg35ftFYKG5bLpHHge2VdRSvUt2nSWN24gizSJyK9fEpYeh9CKkPtiVQDwCpH3pYKH4zCWfK8WL4GpdkDHCIwWedy2zda841/TjesNigvDtAL0CsocTm7f1aEtaI0xmp4zN/qHIIOkPHGI81W0eW5Kbbe3YtGrZlx3oCmR4n0sfLSFQhZ+oH0uSk25WZwLYAtgLnTlwi2q2R+zfHNAjrtUvR3awRHooUA01kTlX+YcE6+IYxVJS5bxWCVLpohTnNrKAAcOl6bK2G+op+iLA8e9pEA6JqFbx32EC77aOU1sgVI0dBhHM9uzXBTdNMyVjN2mbpTQaxj+SpZd84e2x5x22Zct4nhBxwWrmwSzlRMIeHMWHEcqopRe1qOWh3yNeD2cn7jtGQM4f+fWFRMzlG5uBwwtIlc39qISXYu2YjeN2YHjOwP+mwd25SNfnzwx0PlXus4iMabKDKa7Rip8wWnp1csD5cuQNYs1tuM19czXq5nvPLQmb5+s7jsEy77dHhfg4bZJVVVLs1iqVk3MtU0N33gBSBIQMypUzOYrEFDJcmEITD1ZjavO1fhLEEr9/dwpCDSOiZYKdr18JmFlro+n/X8a3XI2SFtUSdRgW4pJ5ow0iyX6kCel/G1AeJk1+IAC5g2au9IdVKVZSIZshwHY+Zh/ajRxXBfZOgrepKEmHTDZUh1j7ikoBK3sqnOrjAMy1WoA4CCBcfjngmjMxLcJ5vulCMBvKkeKqhXQnaMBCFLrBvx8YbO7hOoOBgcXpvWzl/v+KYZ+r3OLzUCDPbqBlVFHrs2tIjl+9FWVAdYWBWAMug66ABvEq1x4Ccz2rBPqM1SQWYa/K2oRdkYJAXqEM66YNBCCxMIRDLgOtK80LnfAHjy0imjpnJQH7PnsfSVQ0wlYkh4Or++wdXvq4Rxw6HSu7/H6OYOgHF2okRO8/oGQrrnLAtdDplGoscGbK6OnGG4KSqsgvtMvDQozWucFO0VGFG0LOThZQ4x48BjsJOgPl4DOSfnyxtuvjasbYOpPG3qyPhi8glOLN6Y/jhCVrJp7cUrBCTrqzXo4NO9lIHeFyMUR4/dNKyGHufDhCrDa+O8wCglMR76M4M2ud674b6V5JFvE14/P+HL8xNetxm1WnhXMIcdD6erGpnI+9lAEKRMkkoDdXvtJuOyAYyCciMzZ9sm/HB5wD+5nvGcAlI12ts6+YzJlYNBhTKPhoA+jvL3cX7+Pq/xUg2aoZjhbcVjIA37PPQMJCi3BqzFv6E5jk37g74QVxQNhMOLM5dszOOh+DtXn3slw5SXRBl6Q2M8veFWCvZWNGGdrcfZOZw8qc1SQsOzCpbmcv6m9dD/qRylAV+YiNAbJUYzNPnz7AEbDBZT1Yy4NANXHJdggCtvz1j0iunfLa7ZI/JY+JbJyPYUNyzzisfHV8znq45Rk8uLPQz4CJOlFYe8H3fRMSDreL9pqMZp5g9AS183JQS3auZVMzke5TUir1E1yws3Vkvpeh4AWdAF3mxGI4uRufNLOtnjZ7Eh0zSi50GcjUw89iuNkY92eOP0qny+Wmni7sQ0tG4P6Hlxk5QxAO2JPPoKH3tjS8pUaZQC41DR2+bnGMTHzUx7IkOAucd65Ry2fcLLbcHLOuM1TUiVaYuONNXnkHRKtTAPX7Mzniw0reO0FYzYCRTTnGbuUqXItRAWRW2UkKRKrBqh8PWNhJQl7899hJbkXuq/2eNGZF3F+fGCEBM+ckNTrptsHDn3AA3G740ZpmGrUYXG1gzsMFncWpdbAICUPfZCU7jRFSzNYOb3HKuR/vtHamBiOiQYbrGNrrNthoy/m4GXNcjBvEnPphlVtxSN/DAMM0kzuzQKzLlYbPzz/fr2wC7TpuorOjBl5HrLGh43AzU5aTQJSzouBk/BYHIW3gStQkmy4Mi3pyErcVoD0l9DoOv/LzxFSRiHsmspQSRrm13DzFk6DRxVoFqIJV1iCQCBXEZsS/RgyN8PuBaPunODhmGROdCu4ljqlji3nUK4J9JzEbYI2ZiRddf4YEgQ0YeMcV7Jdg4Z77gZCE3LZ8RzQVgGtkTySGvExKWxwDqlEIdaLMjEPFqahcvpRqYVoqtdrOq119J59SEmhHmDnzeAaY010cayXucDhj5K0I4Bw5iG6DNObSd9FX5YoiVTkpg9dkeCS5S1UxNqTT3TEYmHYBtmHgMPlgbC5kGrZuQnS0ZOf69q2BFPpFviQgZM6xxq5VLT9dhuM9yXJ84MydBaGnOlWawpIJXeHE+FaJKSpcnRLfz67U2tmyIItCTnLnrx+vt4n2YsxuM5edUXl/OmidLeQxB4Lu0RG/dWRgrhPK+YTyuWx0vPurnBWbLrEJahHhJBLL1icxM1VGV+Qz8jG6YIdi8sl2XecJo2fFhv2FLgoGh5GMff6ZJbnRKV8zu8Bwf6xtfKDdeus2ZY2uNufcoaFShFvra8GIsxh4tf0Svs97LwUfvl/jMeDEEgLmsCwVFci5Zo1cG+ZcuMhwP1COmeQP2Jv+b4ZgHdmi5FCXS7JcGSZPebWTXR3d2krZIa2+dkccnQcfHJCW8dmuGdDWX2H8KOx2lDdPQgzGHHzMJPeQ99Ao4Xp+UpPsECCYahsr6X8U1ZKOPIvXVVeeg2ZBhfiRO9BRL/2sizdMTd6cL0gCWNTGMbac5E6MObuZkpAVc+SymuB2HuCUhwGCc/ASDtAWA53fGQbNwz/1iCpw9JjTioeeuw3mZcLieY6xnAhFIbcm3IpkvFjgqL1BQVPjq/H4CULWc0UOXM7yYKNOI0RTodFNBCTAoV3ePKNXlt9tL3uc/gCwCSmU3rpCJdlxRxy17H02ef4X1VLZdcHXJ1eN4nvKSAldeJZIDxDhYAoFDB2D9Yi8NLdki1oRTa2II9BjPNwAfGwIF10QzA4lvO9XaZbPIp0fRsAykc3taZjB62iSUFOEuVinLYYET4a6qUVLihOS9UxRFWlCpWGFey1rY90H8p0MDUELyFBaUTm9XiNZFf5/NOGk3W0IzJh1DwMWZ1LBs11emaWpjGQdbInMCR4y766Eaz+oYCYr3RpuuJHWOoeR0tyzDYDqkmZjlJln5/jLRcA0pOIs/OaCbOSaqIefXp1+MUqbivCV1RRL++9vimAf0pCE4FfNkN/uJW8Rfpih/sj7iZC6a24I/rH+GfWxb8s48FfzzvmDkzL5yZr4W8SVsjHvvkSAfhIRTCoLiR8hBIFEjsyRyPibfGwlNbRM1VNclpZ2XKYDMHzNmHpHDJvZyuHPK67TbDrO1ALyP1xjtvRs5yxgGYn6MtWkfZ6HK6vcXVR7yYfpo2mzYyRerwML8VhZLA7XxROqNIHhxdjthdnRkkpYp8qlMBq8JQw+hORBhmg8h0lGZQGUrL3DCyTdgIlB0LvAMAgT+nixl+cINS5UvR/latEdunWzkwpT2gNYs57PjUDB4r9VWCI72fyOJc1jTsLAGwFg9XnDbqaLOivohQ88Z7QVe/f03TkPSZAuugL5701wNXG6VamNrpqNpkVWip6jyEzBXoe9rj+49w075HpOS1ynHcABWISn5fmC7S/9H1yT2gsbF8v/bkT+9ICTT4rBTWUg1QidElwUqy5mArFmeASAFPErmzJynbaI9WgKk6bO3tc3efyIp87r0Ko2wmzpKgl0hHOEssJ6JYdl0Sz9PD4yH8dmE5Se9IbCgDDx2JMN2orU7cdMHde9N0/PjChhnh5685vmFTtB0cOn49Ab+aDP75dkbDGUDXY3n0CQ8h4TSM1gcu0Q0L4ZBgV8MlN/zjSyM+cwOcsfjNFPHPPUb8t03Dd+dXPJ4u2vj0g77JaLc1BqnWDExuMCZg2yYNsgA0G5fyd8SuCwJGUSnhBbuQ4eOuTkE1OZKwffFI/PDJ0IX3BSHu8Bhw4dqzdJGxlcx14slUmfYDqHyPw0MJ9Kat8+UACbVqjxOAAv8Ui5qOsm/iaiTXadQ2UVqa6lwY1X6Oth4wQ5orIHW9aDsENw+VmYiFjcGtFIfAFFLBeMNpJReeQH6t+Trj9tMjrpdHPL88YMth+GwEo0hjUw75Nwmmcl7BFpx9huNgKUyexNm3bFpCWwyaWfbArgMzhnV2+DqUaojPvQflkqcUsO3kBpSlacsCZFPcaVOTDddWlU22HOhbsbBGZBosUg46gTz2BGQ9UDJDU8JyH3U6k2cvXOi6P6orw5utNN+FSZayx57dXYZ+9CsVJyJvKx6GfxPGU64WL40rTv75yRVMjhQRR5coaVLLZy/N4paD0kUTQ5My+j9m/gA0Ex8HjYxpWrXNLsGPzzf3OAT/z83gNTn8fvP4y1vDD3vGtSUYGDzYgA/e4dNkcPYU6L+GwfLLnbC3xzdsihp83o+DRWLuLCaqxnSxpt4w6Lv62VeUiaQCGjpsE/k/sXt69Bt+Pa/47ekVwfUy0tqOf+skoUwacjYjioNCW2ytwLWu9OY9BUURRpIJUGMoKx8zWoBpgbZhFEpyrsDGhPh4xWM1Ss8Uc4q8RuXGy9E/U1GNF2Mp+JhE5tZyPvsesXPglYfa+4wYEuZlxXS60ZAK+qSi6LKPDkpSUiuHutFrr9tE/O6B6+tsw+IzKoB5eEikuiqNRPzlCJwdPeh6IHGuM2ev8tD2JmhVXZ3D5uQKoMNTBf604mFKOH33Bb/RLNNhv8y4vDzgy/MTLtusWXgqFrcU7jj1x/FxzfTR7fOkNwAAvjbshpIJGYar6H63t0wsiNpILhoAnmLU5qQmCbWwVo2FGWQsqanb14L0N+ytKkSmtDpOWD4sX7jKq6jVYd8i0h7x/LzoGpdZg2nadHOQz0Pr0b6pSK0n0w/d+LMnX1tmBQnM4gzgWoUznFwMnHBfHVy1SFylyTmK/o2sG2E/XbNHsAH3OPQY/MbfX4vlgN6TitkSlDsNHHIJ9s50lhfh4aXz3AcjFp3orPawoc+u4VeTwWQDtupJwsQYVVAEuOEJHGQF5GwM3h+y/Jrjm/LQhXRPughUSpMNXVVzaHrQaZESk8C8bUKYzmkfX7/CkDExejnWcUqGOxhiQfY6oZf3gHWbcFtnbJn0Tjxrucjwjo9JmS9jmToG8/sxamItUAZcc0TL7pAJ4C7Dv394DuW1aYTLv3NQYOjZdC2OPCfNpvrv+rPJI395fPMe94NSRrKZmcXGOAPc9wh3KXAbBRMZwtkylaLPe8CLSpXyebW3phKBccdg+iCZcIeFoSCfJxeHdSXtE8k2hSMtA11dmqEpo0euMf3ZlRuluijVYCseL4n4zDd+SK2BZnTRVvW2vb8nwoqQYCI6InLOpD5IdMxzoWswu4aPPEAlkhLOFarQmkHEDms7RAZANzeBzOieM5Y9ZMHWNASXMU8bIsNossZlI77tExLj39FnzJyBG9sOEIWsIzmXd/tHd2s1uIJcCtw7Zqw9oBPVVcfzGWOXgR5Ui03uE5gBAqrORuML6VmMQ0FApyeOwXh8//EQxo1e5wGKcaZvlvKzid2uXhJZ1rVGLkaXTJ6ia2lIHHg2AKEC12xgjdGEVaCVUe4EoABfK7DrGX7d8U310H8zd5zRmM5O0Ukq+bfx73xByZnd4ferwWtqyI1ec/GGOJ123OGAs2efw9yDQK2cscjiqDwtNm9kK/fXOB/hopsmocohr9OhrB2P+068MDFKdvoAOUuaG/G84jR/0XH2VizyOiGtEYUHkQQbfs8p/r75NcI51nbetj7IwwZlXdVzk8w9b1HfLyePUvp7yXmlarFxE/DzbjuF1HSqFtDnEejPbiAtkrWBmUXizkTnY5GyPWSp4s5jbCWWgOD81ZIr061XHIKhX2+LZuYAdLLxyew4+0QMjDbqzRhtyMv6vB/xdyoPSwFZzBUqQwPCQpT+Qr0DfpU5BaAOsKAwUWTWQDV4TPdNFUZMLneJAohOmJhuOK6FySfFi2WwiOYfBsnnRgJnwog5bIQ89k9DZHIdKfjNcccyrQOe7weYa4TlLKJzajfYWPlwNR5gPflxPJ/6MseETiY+jaEGqeXrmkE9ovtgLtOiozmJQGe70Eph2Nu04NFnnEPC7DI3yq1WcME2zCApA29pcOzsqR8kkzPa6ARUNhqAqiruDBx0I48e6P9W8NBzA37YRizNMFTSFHqxpmFurPEApqoxNnn2GbkBzjjcgjk2NoxoQdASC7wwt+Jp4rI4lVst1WJuO4TKJ8f9YI0Er5RIwF9kAUIgBcZ5WRFP6532eTqMoYtsrnGMWwt2L7Z3rwu260ySps2o6QWJMXXFvVaY3x4yWu6+pIJryuYg35MqZBw0sq4SW2TZOrXSdE10+bnxtRsLhOm0KGdqgX9XRMW8nfC6T6iNxPtfM/FphZo6OXIr6mJErJVeLQrfb28oWCov3VW9P9b0rFyudQgJ82nF/HhBfLzCzRtxqpNH3QLx+/dw6AvYjTbcnaULSjO45oDXFHrWJesBvTE1lt3igTuOfwvnmhgw5EYv57Fyg1jYDsEYzajHjLeWzn2nDbODjpb/Kvi2dxUmZMzL7TBVStosQeEV0VtRo5Wh+T0eh74PANtozH+cFgZATfwUkJLnTaQztnRjGrJ87zMMJ0+a9Ter1orGCMRH/y6SusY0hml6z6IZM2jvNyIb8L+PXH6Bad4wX4b/tFcAYHYE8/m7atgCLCpHSYSwlkQOYJTXFQ76PYwyDgpZUAwkgwtCK/baIAYXxRksric6X3t8Ux76/cajWsLNAODGCy9Ob+ShocUWLfn8EbvAovGOLEuxArwzgydKQWPAccccdljbEHzCPK866jwOo4jAkU4o1qqlu89d/1mYL4fAaHnwYyKTaBOyZtewjM/ZhqExALet8A9XxC2ipS7ORRdKMnNuajYafGqZBZNqD7qAwDH58D03nIsc1ha1vzNM8xNddOsqTDgaeWCBMjwk4KeN8HmzTmh7JK6wrTiHHb+eV1QAH4PTxS34eW59/Fk/D46QiwyiUKnbS15hHCnzRjDnkKjyyhZYJ7puzaomuLFNZw1acXCXyqp4TjXOg634EHc8DIqXNO9gNSsrw3WWgCGG0vp9CWQM+9H5kZJfcb2Rvzianow89VkbDdGUatVP9rLOahPoXcES9jcyzqRvA87eszY5j/Ta4zRyLRZrnof1QI3VEAS26lWODNvV6mBkdoIlnUPcDxt/zp4F0AL2QTxOrw1DqrUa5AJYQ9z+TWAjztAvKeAle6yFrjk9wxWzrfgwbQqvFJZuvmWPS3bM9T+qr5I/bGf/aIKIHjC1KmRlTYHQpFkqjlMyCb0Xi7Va7KVDa7lRBj4aPBMkJFPs6KqxABu5GOTY2S6UOHQZjL/O+P+3oy2CXDrkIJYKGUeLOHywwFOwaBMF46kZZcbszEP/cXd4Sb0pGi1w8k0zfSH50w1wuO6TarkERzhivJEpguXXvp+IBPogi7WFxKkEm+WHpexdO9yYhjDvRK+bdgqM8U59X8BAAG/ckUyFsRY1s271OhHMcfdw3B8jLW/87Dr0xMYPxnCvYIvYr+Jj6hSKkawtcIAc5XbVscmQvABNzga2qIvaDNuZynW4hqBmqWm08KS8FKhFhNlkwKfLoVaFAEZK5hg4WyUd+5ICzMsZBxeeZt5UFts6KUcbgHKbHSiDGwWRSrMIpmI3TieVdU2YrgY4snZStbjWrlEC4KBjk3iNGaDLuHIVVU0jcS1DG9ccdoVFnK1k4hGOXqN6/5M79F5kEztNvXeSt4jX50f8+OUDfv/6iFc2vj75pM5ejw8vKgkgjCoRgyvFKfQTfNbGqwRLj5F4MDbR+7RxKSyaVh1uKeJ1n3BLHonvl4i4AWwW0npf5RwSTi4PWi7kMBVsRcikSy9N+FQNnpND3r3eBwny8ztNURIEA1KBZuUyP/CaHFaZ8DXSvDwKB+ZmGEcXk+g2NEVpvD9Yo9VpMAQNj6hE4TWyF8Lkv8bWUY5vl6GbQXUMNAgUzFH3XHbkkys4DXhh45tLOi9A8X1nC3ZgxDD2marBVshdpjajAvuLT3iYKfMjRyBqPqU9Yt1CZ29woIuOGACyiKURJ0YEfoBFtGn4+QHm+dzFldgwWiEX04BmULeAskXULehQDJXHDeG0IpxW3B+HpmmlZu5+nbHKZGm1B+pjBGBMUqzTxYSFNeG1XB6C9nsepIejACnRxOrz5QEv68y60xaXFHDJ/SGSxjXpXfQMHQCcMSisklmsNKEa5rupTGEajZmkNGitq/DzRuqW8wYb6T7ky4z1pye8/vQBr69nnaIU02hnKx5Yy7zCYMueTa+J7ibYLDXpLJuQ6xLufR9lZvWKcszcKwBXHCoskgSzBvQuQD9HTRa4f1B9h8OEiUIyB32KltZtwGg/COCQoY9HiDt+86sf8N3Hn3Cvlw4ApXjcrl5NtvfEU6jFK6yhRIG4IxRKiGo1OuCUWOdnpCLKOcpnvqWIL+uM79cFXxJl47JepEqzZvgdQKupjaWPZVz/5DIeB764NC0vPAwmEI030Kxdp88h8EzXvQeA2OhcZWM2pUsF6KbBcEsDWcbdCgXzl1yQWoWFQbQWD7AI9pgsbJWUFoU1JeSOPlgEPu+vi+rfLKA70/DxLmsdO7yEpB25smNjIzEd6SWTCE5vJohJhoGIxtPiyHiIO747vWLhTn6MNCk6LyvCvCl26VdhpRhg74GsNoNtm1S7QnDjZV5xMsTVlYPMli1GP0ZjGty8UfY4VZgpwbiGliywk+sL0RSnY5lqj4FVhJmcQjxUXrs9qF572ONBQtVPO+li80g8AIh7zRgARjs+GZWXzyDncFSapEbsnj3WEnSsm7B0wkC9lWYi2a9dC2UwO9/+yQGPgTR7Io5ZrzxYb2RRBcOv1ESr4FF0poVKDwEApqcL4vmGj8Vpf2C/Tbg+P+D1csZtI0hDKJp7cbhw+a7BWxKMAVqRQKOfV7/f5V0tf00N0caYvcW1GFbds7gWRyPxPFQmjdtx3VD12HqlwiwUtTY09TDwptBCckp5LUXkoKvKUoh+vEz+EmTSJSIAGhyTysjnrjjofSFdIZ8HnXy+vjmQoThPigI0eyLmE/L51kyuUSTsRpu9PkPvsDu6MmGvipwlZtzEyorBdu9SZxo1M7laOvLQy8GCTpgrpRrsLQxZPgV1mYuQns9WLW6VcPA0tB1mByzO4LccXrtsbid+AF1ml2JYw1oaa78wzdN057avPb5dU7Qa/H6VYQmBSxomS5m37Lxj88twJuQa4emTbVhc4yZHf+2GPo1FWZRhDK4ppGAM+VKGmLqCYsjqDCMNyRPrp0imokMUg9v8SPUDJHDz2P+UYKd0cG0HOCBlRxIqxSr3XDjVuomw1G5rRqEZGwp8Y4yzke6GsQ02JkyhID5ecboru++5760aVHagyfvRau69wzRzpGUCbCjB3OWQcK4OyY7emwGv2eFWOlskyUM2qMtNju7j2VWcPJkfSHlNAaAOQY3kDvY94no59QYn39cw7Yhxf9eAhK4FycFKwCdlxYroM1JxiJWMqRdvNTMHBM46ImVoVFXea2JLVr8xjCWBQXjo10IPcmkkLLcXEooqjFUb05kk8vnHRqVASMW6PrDliZkUJh5Yc0V1z81a+bodJ5src+2NHQxYOGP1w3qthZQ6Y95V4Ew/R7Wkd7QRFl8KySmsO/mk7tWjDZsi6jExGyctE2fBIk5FPRewVECvwHMlRyuBX21tSNYojq62dabp8NAp7LivUoDjENxWaSO/Zc9Qh4FwxO/lKropS7eLA7rNHDXKjw1N3SCH3gxA+PrZGwSO3JZfZ3IEIb9piv705jT0+KZqiz/tHAAZL5+dwcnRCL9m1wBOzkBspqQ8WnzGE2dUszsqpgG8eEAL9NFXPLF+cnRZ5VFl0k7NnG0DUOA8PTAxGmR7NOntr8+ltc8Iy4Z4vumE4sHjcWCyANBmZiuOhZBo0CVdFmwvJ6yXk46lj/ZxI2bvWP3PcrPVOGiztIoBMFcHIjmgpffdROg4KARAN47eDB7UI509fBbhJYeQcJo2xTN3hlpcDsiVyug+9kz69bnRwwoQ64WSRxpACtZq1psPmWrVjVRgkzGg+5Axn2+Yn17hZiq9yxbo2l5O2NYJOQXFcSv/fgyZhq1s0f6KDIx0JkUf3x6ZC7byANGAocsxZlaVNzNplu2Vzj9Y1q/BMYDrOQ1wi5x3rRbX24J2NbpOZCBoPq064EbSt14Hy1IKWvlJT0gYKfK+o9zE+H3PMCMxqqHsG6IydrmFZDy2HHDLAT+tC15S0OZ3dIW4/K50bXJT8RSJJvrbZg9Z8bU4HQqSaePMch9bsqorHixpvzyGijNrP1lOHLJpGuQ65MNfDxLHcl2kMS8iakJnlLUg90p+//6Q2QMSYoOunzE+jZWHJKIyizO+YqpCcTRfnaV/Qws64HeqMN8OTYZDRjVm5rzb0u87LZeCbdpN9rwRjJ6ii+/GtLk62BQI72v0QOTsdegC6BmJ+CSOD8F7zkOtEtWvZgsLT5kFU+XSjSAaCX6WdWTeTJDyay+PF5xM/xx5o9HvfZtQikynZoQtYto9XMws+9sf+JH1YkyjASeXFLuXz2xsBbaIWhqqUs4abCR5ArHIa9Wg7AH7dcH1eabzqd18QoJN9EJbLDrmDQBnDvBC6xIc+jgcRvdNym5vaQBHXqNXaWRb0prYdfXr51y3B7QT27nxOdiQEXmsvlWj1nRJPDGZU63qhZWkblMbHJdax02B3scJ5qjDDxyHqHTNGzIwj9YwRmowuYrvpqMz031DnrDpvt5kI+v69iQT8Ho9of7Y74uzpEsjjJjHp5djk1v04SWosdhWSoGcvKrQCbkpfYfHdwXICmMYzuBG9uSz6p/IlGd0RYO5TF6Oo/p6fsx4cSmitYDSugOSMcT1ruh9GYElUjVYzR0LKTuFxqTHMTHt+Rx2LC6rTkysBrO3ONekbBbJ3NfisJU7R65qelXBG32qhqmIXIXxfRO64uhYROcKzfKP3sh96v1vDW1x3HWEziMyqkIHO3nBxWgREF+0n6BoKIvYDiAPWlVccxrojlUzswbniOYlfGzJagR7rNV1V57ikPaA9TaRyD8v9jnueHp6hguZsOozNS/dySJki5mZIGOTSqEPgT+SR7pO2F5P2K6LZuilkC7GOLwBEHVtnjakPVBDlrMp58kjVLByxVOZ934P/YjhRhvkBggKskrtk58TeeHpfCWp1Eq89JwC1tuM2zqjlKDNxjHoiYkA6aMDt2zJEZ1/hoY3gODqMIfA7jSsjDkGEpE7uGchleKwXU4o2cN+fhjO80jr1N8RiiYnCZWjZgWxLBLj3fL6fa3yOuPPej9CPo6cjxrv9Bl47QOA6b0Cg2OiYEwDLGBRUSvYZYcOMV0ZoT5qDJeDJd/9cc8vvzfPMK7Cux1+Ok4TVx4qSikg7fFQ4eTidGCIsGWq0HKxMIaCeG1GJzbFT3Rs5vYhH9Yy5yAq8AvQg/dYBY0NxGs2WI3BM+tJGPTGqiR4Mp052Yqtilm5Q7BFg3duR2ZSYZYL9X5o4Ehoj6JXLpBQQw/OYmghnzHAwNijPDgAOP69++xcHI6uWc7m645vFtAr3wSAsTHTYJ1RB+yRDtQdwKlJVthgeLywBBUAwclwUZcMiLZg8QnnacXDckUMSQ0R4rQr59a6CvBkpBv0KcaA8FiP2ZM2l04rYfDciDS2wi6ZRs1tUzZLy06ZLHWjfneTQSDOmoltQ8YD2zbh+XbC6z5h5abW4hM+Lld8OL8e3HlqZe2XPRxG3y0H9DHI62HuvrYs38q/o/erONicUJOHlwGl7GBuxOWWSUZXHZCgo/qtjeJFvdw0BqMsvGbuqIAoF848Dq7ysjKT4LsI1cjEGZvHB+s8QHnUI42xsB+rQC8A0wJ5uKQ0g8QSxDIoIrgqjZ/TxvTD5jRLI94wUdEm2zToq9qkkfHy4ZIDLM/clGGkmjrVqtaK/rytB316VCBMCdP5hunpFeHhBuMZcrlFpMuC/bLoVLF1FX7aEaZd163o/kjy0YaGeE0eaYswN4beBiXI0Cxq3fXzpexxXResLJ174c9OG3TmDLsLYlUDnhouJNNxB/9VAMH04J6qwVxpw5TnfgyuMu8gl1dw92A7rxsAc9uJ5y6bhAwtSj+ENtWK0IhyWJrhjaXpayvLBlK9QRu8qY2SJG83WEElRsoiQJn/lbP8Wwa2e12TXzi+aUC/cLwQRw9nGxb0LrblTDvwVKfj0s/dWUzJwwQYxSIbuHyBvAdtBmJ0DNBDX4pDZZ73fcbzrgXYPWccoGA5b/CPN1L5A28CjJfX3DPgunvUPaJsQXFuCS5KHbT8kIaEqVo8NIPgs45uB59xikSfpGtJr29bHyZppTfSCjxwY3/M4XyE7ncfCI0h/J+EroZIhM4u0evP2jfifVoqTf6t2SOJw5JkoY3oid6KXZtUVG/hiqOuI7QhKtxzGX93oUvIiiWbwCz0eY2yeWi9UF8grxPMtREGXMlAIqWu4X3JHi/JIVcyKBBo72SqBoVUDVbOLtcCZTpQc6vBm25UfqxGmwY0qkgGV/r6do7gHkMX0xXBra2tWGaaUg6nldbAuFvWYRMTyeM96OuNVFtaA5wMiJIjAC/wXU4YzcbldQGewGRFzMSbscgmWIhpdq+C5PclQx9VE2UsX1yvlL4nVZ0V+WW+bgDrwAzOUqZn5XI1lcrM9MKRUhltw8kRM2t2RX+HmvgZJ1e6yBxv7qOBiW0GSdgyjSiMI0Yu63zUbPHD9z1DSglAaAZ7peaoVABfc3xTPXTxAlVxLt7xWiPtC7Rj8BwHSSpol74Wi0umCxctAJDDuDPk+dFMQ3U9+3LMXpEGowzc/CHLtjZk5gLFGNtgSuWMhTXMQUGvvC5Yv/+A648f8PrygJQ97k0yRl/S+/M0jrDOs7/itNwOuOrIw7Z+MNuwfG53wbgNGfV9o1OD4NgsLVadi+7hipEl0YrFeluwrjNu24zbHqHWXsVj54dR9NDlChO/uMMII6VLmot9nL7rWMvnkElEs/d75ixLGMcMHzu0ZB1Z8rl5P/QE7EtF2el19j1iy4F9QykIeUM0tWyMZmD3MIYzxM6JtuFjODbLZBOw6HCBmpsPMExFY/2QHqC1WvAFDgXRbvqeIu0s10Dcqoxp2LcI89MT0nVWRhJAgXl+usCY18N11HUt/ReYA832vi8jPZlmAcOORgaA9dBEp9iq0r+lkj1hGyADAxqcKbzZ59o1xdfSYcUGHMTNNIVjCKXiuCZKA4ylayyc7dF43Ju3EbHiqGE/+ovWZg6WcFJpjoYlYikoGTlAw5HiJ3rJFanJeiZV2NkRrXrUOSd6Yg/0Ow8mfd6Jy36t+XC+v3R8s4C+14Z/fO0ZwMlZfIgGJK1UEBtJj4ZKKnjjoqiNdBSek8c/WS1+3Bpya1icwa8moyyZmRuhky2qYyz0Q9MMe2lmhGmHm1K/mUPmLIv552hwNHJO2tt1IwVFGRQytsFPOx7wqmWzlNHSWLx/zfs/f+645ynL9/Ju0QaZXW2Khgx/Wnv2KiU7a8sYXwAu0bfPj1hfzri+npRzH0JGnDZMpxtR41zfyHIiv9auie6BTHzu10z44ziM8f/L0apBbfaQoSpDwXYrwJFaKaPsPibdQNNKNoNyD5ypgAUmT/MKJOjmlOVAY+UWl+yw1T73MDbz5XpbUFOXdoGm1bYxjc05ml6PaJmeOUBUbymkA63QVczLCheTJib9XGuH+Ib7D/SsWzZ5eZoOMwWFhMyaWMuxnoyxrIfO+kFyCBRT9qCicmLSAjB85YtmtFRNWx3vB6CCXHQPmrKyWjMqylfNffA2wJAgVICFtSx27s10BzPacJvtzUjCuY2yVgTiI+pgn0YVbD1VGvuXgaau7U/QGlARrGTRFht/MMLUG6lE1u7ZIOqTZARtsHjg5Ah6opy0AUxjfCweqR7D9P/thp89vllAn5zBv/DYF2ofx2UeurlvgFAZhsrd5UoaCnsB1lqRKhXql2TwbC2coQzRmYat9mGXXB3iLamWy8SDRX4/arloRi4ZqW1Hj9Ah2wPA1nLHuT/LmRFwoXMoFuk2Y70sKBfHDc8+Rj02/6x9G9DHkfdaSTEwZ3fIYGW8/f64rwIk0IWFDCEswzf5NuP25QEvz494uZyx5UCbok/MySec1fqMViy2dcK6TVhTxC1F5EKj/5cU8ZI9XpPDS+6ORdKoGsvOxk+unrJh2mA7buTjuYxzAQI7jNVO35wJ/qmvJ2VtyO+KyJpYtklTrzSqKrbajYFJ+4Mml80gB3t/XaW63Jmt845y7N1ROViQEui+x4NI3P17yAyF9n04wI5SzaIn1KqlRCN5orLmDulJk9xO+0HFs1xm5OsMOxiEH8zEh0E2MT25XahKkwbpdZ/w+XbCl23GlbNugU/VVGKAnbypCL5bx1HCRrDXZe9TnsKEkglS1QoXGg0qLCx8awedoNJIagD6tcgwQB2yDKQa8GTyzQJ+94fhQC6VkQXfY167tDFQ1v0YjpPOh9eR+ymNUiurob9OZImAvxWj/6kCf3HtpdHsDB6DAVDhuOkpD4Pi4sMDPruCj7FgrQaTc2hwBz7qPDQ2SrV43iP24vDD7aQd91NI+LBc8bS/YjndDpZeb63caGKuFqt4tBor8Oi/lPUAwRxlC8yY8bpBtGpUasCxecHIGkhDA0yCuxhTy/EmG3VFNx6Cg+ohCIjX4X2gL9mhXRek29zP01Kw+PSbH/CrP/q+v0ZhXZnscbssOly1bhHXdcFlm3FTQ2DKmM8+wwJ4CCMzgLOiAXagQAzsxWiwDLY3RWmYy/T70liV0DYWHesWgffTj3q9BtXLVslT9PLygJdX4k1nfq/XPeKHbcZPW8RLpkDiDVhmgnjOIfRJR2n2SrNU1qvQMrVhD+jAjIx1A0B2DouvBLkwFVSbdANP/L7ZS5aJXteoixnxfEMQJ6yQ0ZJHfl2w/viE2/MD1tuMUhy8zzg9XLF8esb08QXuvAKmEcvl6Qr/dDSDbtzAL9zMVwiPVT8nnrJujWwXw0o9KmcqlhxQmlXOt4hejRWo4OZyWDSGTQn2AkgLZQyvibN0oMsb03v2uNH03nQ2Sn8mWLaZLza5o9H7CbNOvUtrZ74ohZVf3xpeZgyvgWEfbxqZUMs5DRCLJjKgz3bJUjH0n43DYNHsvj6if0MLur4riY0c0EsqCypTkpEMybDE6LFZ5Aw3Dhq9Zmudjyr4VbZNs6ypGWRj4WrF5DKsqZimDcvDBWHe1eBiXyfs63RwLBq9HKWsr4bVDnn60LgCNIOyRWzXGdt1wb5NBzea8c/DNTHEtR8bNTL8JOJHcpTslN5Yq+muNVxyv5flHRptzQCuwFQD49qx+VvJigzoOGqpVstwhY8YOrpuE563CRfGoStw0Iv2gnk26vzvhcpSGbqw5og9A4w5WsqucxXhKtuDs2mqtKgfm6uTtFu0doSd7q9FTh63ddJgLs46AglIKU5YLnjDtKjopgUANJiMA0fCT061b2CC43oLnFyXdp5sw6PPWEJCcPmg+/7eWmnVIBeyKhzPz4esCUfYN5oUZdaKn3ec3TOWp46hS5a9fX6E+UIUT+OKyjsTDFc1wy8sSZHW+K6vqGrVV4NaHdYU8dO24HmPKK1L4Z49eftGTny0IcrTxdIcLQN9UKZzx6nNUVOn3//j113H/m2WKxr8QQM6M+NMJ2JIA9c51nLhtS1ryPPMguiwAMRwIbyehuTkF4hTPkzCc19lrwa3TM31ceJUPp9ILX/t8U0di3oZ3gskyWKqYeU7DJ1wfqjuec7eUItGgruUZdKUEk2RxWecfNKS7xR3THGnxd26dglRtzrFbcRohbMuEqUALeJyJ2UrmuTGkryoDx3GOQ6F8PUY/n4IUsMQiRx7Cni9nfCyLnjdI1J18IZEpn51fsWnD18wPxLOqu/J06PKZfeFqGsCuUyUVZVbxP56wvpyxu16Qkr+eO7TjtPDFWJlFl9PfN8M7EbVUG5Wx96lCVhBi/d5t/icRLtCpgXJFGB2ZNMlgxWLo9fZGRbxwppBhvX1wLlW843B5AHojXQz/NcaCUhtezwEc4B8Q5/iDmcrnlicSzxPb9niOVlsRTaiznMeB+IsiIYbvaz0fk/F4KNvXEyvPTT56B7lbA+wkpwPBX3SURk552UP2J7P2C80sTfy0+/x78aSEqNmi9AVi/DKZXCIeeNSDaoUdGtAccrZa02MzXm4yNIgkWDU3R+WrP5E48agIdqMxlldbQa3HHAtTvV/8l1Qk+v+h45Ru/7egWmkjhJKQFOmysri15+MwKHQIK90XF7vsnm8poBLdnjJBj9sDdfclMX34A0eA3ACVXxFYwtFP4NBsJALEMnivzakf0NxLtIvAIQrioMF2Xios0mzAMMJfeKrq9lZQDnoI8NAhpQmS+ayYWia3bYZOXu8vp57c1HgndZLfTfoq7RmOGPuk3aqUMgPjY8NwIZ5aFLJxGXeg06AvrGZGzJlyYZJstTrpKgxDef5hsfT5aglowHL4vJyPr7uXZZaS2eBdNkDAHxuIlYmlYUwgkY6p2mUGZ4W6tJMPCmaWdky7hEvOcCxNslkDaJteIqsXzLgi6IBLd/xtuExFDaI6A72dB5du92BWBTGktPUvNzeUk31pOX+WdwuC9EUc8CXdcbGLjoilyoelDrYAnngDUQuj2ABlncdRLt+7lDI6Q5ukk1HWFjqETtIAPf7yKYn9+P548SnrNNCPaA8KDDKs+M8zSS4kA9wXqsGvu389x7Q+/sfoRIcnGEBv5LM87pN2LIfILgeUPfiYI2sZSiTSYKlgSGKsaGMNtuqVdzI4b9HuH/u+it0e580DfCH5/c68QzCyacDhj7+rhxOYCqQW5GQNQBgY8XF15JhmZFHLl4GkzOKKogMRKqNpQyoaRps9yH9G3csMsb8awD+A1DS/B+11v79n/m5/wmA/xOA/35r7R/84mviSFscR2KFUw50B5BULTwvbDEbWIvDLRNtMTNuFZ0BXIVpY1ZEQw2P04bH+YbJszhXSJhiQojdz7E1kiGVkXCx8yK8mAwHRrut6BNOyw3nhwum8w3e8QK/01Ch79EfTabr8rEpSoJguWfoAQhtx1Te0g1Vg3qwiWvNdHNn/h2hJAquLINF8vPpNmN77SJXwmqgrLwgzj2ro5tj9M/KTdHPr4/4/vURn/dJYRY5hMMLQAPmXi2Sq4cx+vtDfk9L8+IwZtjSd8hmUBzkgS43bKLCSxfLNvHUrMVi2VacUkQqFo5lYUsLSM3gS3K4ZIJeiI3QEF3DbKuKQgnPWahxknEKPVHkdkcoYBxGOZyv7Xr72pzXDai/wIin6/dsg/XcP5hpUM6wcFtNDmWjYbOSeXjN1f6z83bI3O+P1vpw0vpyxrZOqtooPYuRPitSGqmQ+uYte1VPlcaoOIvJUZu/e08S7bpxhi5cb7lmAlfZIV7sPIyzD1i0XO/xZ+WgHkdHCYIFPoTG6y4huqzMuJGl039fKifo8Jv4oKrmS2v0HxrbETaKUw1otUM+k6MqlZ9AiDfErTS8bO1vdrDIGOMA/IcA/lUAfwbg7xtj/rS19g/vfu4RwP8WwP/ja964ArgNAoTOAMkaZEviXJJVk/a5o7I0U0c8MQMhVdNveBOeLxkGdJ1kYHGOOuXN4pYDZpdgDGWUp7jhvNwwTduBi+48BYHDyD6OD5RkLlKOlt0rM+Be4OpwrWz3LQWgvN1tnbC9PAwu7AVTJBbOcroSi+HuvQ9VhXTeraXPfsgCKasm+KUHZT23d/iErVkw3VirBRFjGpki3hZ8XK44xV3hsb14rGx0IYYDwt19ybQRJ16n8sBJVSUc7qjDKR3/F7hENiyBkDrHv9vTycZo7zJfAKrX7W3Bw7RhqVRdPBSHpxzw3RSwF3cQixKRpk3vp3l3Q+o4K/iz0Z/0oBp2PaLvzY6YWI8h4mkPmCd/MIh475D3LAzp2dZgjEMtlWYNtDFvsV8WbK8nXC9nrFtErQ7BJyynG06Pr5jl/ts2SD4QfVHcqUqmCeSuq9MDXeaKc9RBStmra5W4/sj1GCmLQM+4Szvi0wBJFX8IDY9etMKN0hPXQtmuVObUhG94UrllaYYeHbLornXtHakcnAFOjjRenCUXJFn7I64v97Rwxr3yvIU0TK+FOOnBAo/BIlrq5ZClYv9cHWQeNV4InSD9FwJhDIzK7X7N8TUZ+r8M4L9orf2XAGCM+U8A/OsA/uHdz/17AP73AP7tr3lj2oWHzAMGyQKrMXC5E+5PvnfAz5yFp2pxyV61FbYCdSwK3HSaXFX9hsAMhVItbomCjJR3D9OKGAkXVsx5wBEFYySrraPULsBqfrcJ621B2uMBc5YsemTPSEC9L+GM6UH+PSrc7XrSr/trH0f5O0Zv3w0043sZ2+CmDDcl5uMzT59dktJtProw2UYONvMwPVuNKvmtrBMvzvPXbQJ2HIK5YNE7CxdJYJwsP4yhHORzzz7j43TDFBKd71ACh5GRpIGbqgo5v/Ga1OpUbRCAUuwazKEZbaqDOMQrxmtoyjHiWFFI5SjwzNh4E0d3bzokOErxArRmLaCb2EhXlQEq0fIRuE3YPKPJikw8e6YRmtt0OH8XEx7cCx74dXUtFovblweAm6KtOOxbxL5N6hMKgFlWpHnkYzr0eIAjHGGMo2lslzH5dPh3gSXo3Iffuesz1GawCfV1MKdojZqH12xxK8fnPlqDyTXMluA6IUk4S8bd8e5zyFqUjULogcbQ8yv6M4A0vY8spr04vGSP5+TxnGjASBrjmTnn0RoErSpEVrdruYz6L9fcr4kwoaip/vXBHPi6gP4nAP7x8PWfAfhXxh8wxvyLAP6Z1tr/2RjzswHdGPN3APwdAFjMA74MtdFkDQCL4KUE6ROFALEmtuKwoz8IJ25gPDIWP5bwt2JxyZKh9wAxcxlv0BDZwbvwQvaDlKwwJsaMXNQPa3Ia1GomxgFlrn2q0rlCAfp8QzitahUm+uZllK6tfSBjVHik1+q0RoVQjND23goxvdkMhK9dnDbYAHpIp3nDcroinld4rRbckWYpWT6YRRPK4Vxcrjpted0n1Yu/5aAZemY6Xm7HjFZLZlBl9ZId9mr03pdmMLmMc914/bwPPUgg9yFjmrnJO/RJ3rsm+3XB5eWMl9cHpOK0OXrLAc97xPMecR28LMkir/AQEDda+X5Qhmd/EdMl7RfDHGeolVlxoKxO7QXrwSQagN5/+bu1dE9rdZQtV6DAHZgvclClmTvkZmSdvG1+yv22tiDGiiCa/yyz0K3mjti+HapQGXjLxWHLAZcUUaphTRvarEVwDRBKIDOLeICwVJo0vpZu/ZZap3MGZouYKOuo89NHGWNiW/VJzu7+0026BXoh60rg7DP++HTBp9MF0WVu4Hrs2WMvXm3x9uLgbOQN2WG1PXEhXPzIWvGW3iParlPVEQWDhK64CNAGMDMU81+pwYUxxgL4PwL4X/yhn22t/V0AfxcAPrnftmn4pMEQ39ccfr6zXnKjbM/whSDXF9qpkzwcvKvlRk0Jcv8APkYHbxo+TQ2nkPAwrRQUeWHuKShG29/7bRY9siXo3JsGE+cpQErGZG3R4NeqTMCCSuIBgjGGR6ltY1Evi1ZGU90Ccw+44oh1Hz5zsSw4ddTXJjGyhDj8fMkOL18+AF8+HJq/liUExqZbYypjTj1o0EZIfYXrTsNFQj1bsx+ai53KWO7QHcIRaZrv0b/N0BefEO60uaWHYF1VeqZg5wBo2rHeh9dxXRErad8jbvuEl3VWI4aNsXTHTTLRBskN+JI8ZYr8Os4AsyVvyrPPGujV0R4DPMLB8gaqTC4JSK0hVYOnSNdIMjRpsrtGsJnozAMiX1HU91WE2X5uTYxHl8NlzH2qKpN8/MG+FcmaKsmjpIDEzBgAB/Ewmbgt/HWDweQTnKmq5SIMnbGRDwA5O0oAWGZZIK5SLYIlyeH3aIupyH0wmGzjoURogzo1MabAgSZLsg7toOzpTMOHkPHdtOJxWjEH6qvVaoGdzgtiFcexIZiKhftDC1e4NLBEQ4/bsFaADvXI2lBGH/8n8c6i4+v3crt/6PiagP7nAP6Z4ev/On9PjkcA/10A/xkHnj8G8KfGmP/xLzVGK4DX3BeSN7SThkL4k4GBNzQWW4KF5fLVoT8k1jQEYwAusQKAyXKJ5AEwv/3RExsmmJ4VUFAoaiEX5+0g6anl2bC4x27/WHa+9yDlFJCuJ9LcHrjsU0yHEfqOPSaE2uUGACqBE49XpxQG/WneWHI7PND62WyBFY4/B4exiSqNxRHn/0OHmCwLBCWZ/7YTFp2y78Mx0jC2pJPTHdENluKwVIvddxzZWxJFWnymgQ7T4E3FEhJm5mePU7RyLqNcLG04FpnH+UcdkvEQCujlesL3r0/4q+sZP2wT1mIVi+34KrnCw1BTq6Jhsr3JOVqfHSSaZeqwWjJ81vOnYE5m6CRXAYCbp8d7S/eRsP8RJhmrDqG0SsUm8gYupoOOfV7ZY5Zlj62riJOBddsbG8OavHL+pS9Tdk+aPbcZ2951+UNICD4jxl2hGJGZvq0zzDrrWhvpo6PMARwFyaURs0VwagnuKycBqRnd/B98RWSu+H0VNiZhFT0gWtMrQtIm73ZwouQKUDP2y7pgL14/zy4bDlecwHtwmxkydDHFOA4zAUAekHzyO6YGqDBdRFIiNaounP16yiLwdQH97wP4F4wx/ywokP/PAPzP5R9ba18A/Fq+Nsb8ZwD+d3+I5eKMwafw/ttTU4AyV18NTXkd8MneUpBdDuCOtgEPBfSguzhaAGICPGqnT3FnjnXW8ff3jlEAa2yeluSxX2fcLgvppPMNjyFhmjd8+PSZ2ARDk3KEM1o5KgGORy0W223By+sZl23GlgNGw+roM7ztTUDnCjyP9N9jybWRwa3ABMYVErKa+0MNQG3LykbNLhkiMaZRaW37FKpUNc6SiuWZMzPqVURc94hrpupHzTAgjaquh44K7MbCCmZrGg1/2YrlELSOk5NuuBfWk14PmXkM5iG1N6b1oS8O88uqwSCYirVQAHnJHj9tHj/ulif4CAJ88MDHWPEhFJx9GYJTOwQWgB5qEZvaS5cO2CslGGcP1v4wOHngQyQXezs0G2ux2LYuq7AP+iiO1Uc9T8saQ2bSMe6I86bm2XrYhjhvELch/ZzZEYauC456DTk7Df7juuyuRehrgqukMetuMijUrLLE5H55TiiINkgpqZyTk/cDGblX7m/M1g1V0fGaa3+idmMLCerBNISQ8XiHRFGWT83tjWEeV+m1n1gV8hQ3eNbc371HzAVLdjTkBoHO/MH4ogKMFkiO3XHyrpFukCoZ8pACJYl1PQVaE6MLEl0n/LWOPxjQW2vZGPNvAvhPQbTF/7i19p8bY/5dAP+gtfanf723pMMZGgkfvxZdg1G7OJiqdDHJmhrfjJdk8ZJEBhNsYydWdlR+2SFDkrJPEIxcHG7bjNbo4ZHyXSYzhUKoVLl2lDaVB2/fI9ZtUpdzcr2xuvidL3BT5/Yq80WHkBryHpDWSc18ZfrT2oJPnz7jVyOWX60q7o0PTPCk7z7NG9xAw5RGr6hE1mZhOUMHKIibtfcJ0haVJ1+HgC4lfp/UpNe3mWAAcTGqpsMMoonSGlVgl+zwnBwumZpaAGGXHyNBLWfTMHFZHnmD8sy7HtkVJbuDuJkwXFqxDHUNG5GrsFNWBUrR/D7dZpy2GXtxCJk2/IeQ8MfzW9aFMCyIskrnZpoBbIWtlmiqpv/8e1VPMA1wJG0hks8nX/HoC5aQNNjJekvZ43Vd8GVdcMsUlWaX8RA3PMw3THFHYPu8UhyeXx6x/vgdtkTj9t4WLHHHebnifL7SiL5luQn1MHWHDU8D+t06v2cV6XW/qxxIPtnjliK23DNaZ6hnNYcEGxoEeCuNWDHXPWItnqCnZg4Z8Nh7kS1pvL66sXKgj+9osIzfcXwOuVk0QwFe5GuDrZh8whx2eJ/pmdkN1hSxFU+6QFyByaa9MrOFMu6jY9E4zg8Ijt5x8aDN0m5of7i+b87kl4+vwtBba38PwN+7+96/8zM/+z/8a34GAL0kojKoc3y9lEWMdQk97cFTJndytmd6GHbEBmzZwBoD4yt7D1IZv4T9YM81L+thsYs7y3ZbdLDm0Cwd3s9xdv/p9Fm57HJIANpeF+A6D9/nh+COieEZF+2ZJOGROfkDTtkz8npg0FjXDbDlM4/MF4FNAJoqbC2ivt6ZXg8NsHlZDw0wCQJ564MqKQXsifTQU/FE76rU5FMJAxADTKio0TYUB5V8EDxThJtEa8fxnyRpcBycGeUHxmsi/YJRyEyDOm8Ugv1frqeDdo4zdZjUG1QFG/UFZP4hD/df+NXyuShLoxmJkZ6pBhdNWBPyPuyZKZUI91KcLzgtNwSf8eH8qgqG3mfEkBTm8EytLdViyTedbbgPeDmFTqlF3+iEkSWRw9gdyxCkWzPYt4h1nfF6PeGW6D73Z4H/VCZIz1xv2WvmKrZvHyo5es3sSbBnCpLXHHDJd5BG60NYv9RwdobG80VeQGYXhBueqyg89iMw5Com3t40LAzhZZYucNmjNIstBSRNbADbGnIj9UUSn3PKcpH+3a007ePRfe7rXVhN7x3C7ouanJLZ9NcG9m9qEn3JvbxwxmByVLJ0DL1hdgbWVMyAllny4PhGymqoQwPKjP583BApFq/ZIe4TKgziPsEZ4skK31sCYc/E7xqiTBUctVzkGCGUlOiS0kN+tGmgLDfDzUkVDlWxUfDpQbO8Fge/e+wuwu71ELzeYx0AnXlQc7+1hvHVsKwKw2hGVrrNHqo5CFodnIxQYKs5LJjW2Gt0j2rAQdcg8GCJx0/bhJfslBMsGicjy4CMko0GTOEux1Z0UxD2h3y2OG/HKdm76dD3NjXgiLE6x1CF60bMayHX92v2WAe4J3DFIFngeMi9L/z61+Lww+bxV6vFT3vDtVSeNLU4OeoLRSv+t92i7+eamfdH74uwCJlncwRLY/7juUszc9+iMqik6e19ZtXGt2t5NLHwLWESGGPPh8povNYAZehbDqy+KVx+wsejo0pkpJ9aTtKCLZisPVI8i0MzRzqoJAXiEwp0HRbw7+0DwcGCKr7FZ11XpRrs1WGrXRJ3PISy6iwFlji4l4l+i8nAlYemrpn0y8e7F61hf4a3k60jfNzQlNABUOzyDClTtv5Ws+aXjm8qzjWWFw2NuMmDzKU3BucAAJabIKxi1ojlcslUuq/l2E2+f59sh0Yqmo4aj7QwwYM1yDWjzA8AmsmEQFx0Jzz05JG3iG0lHra4xk8xYVpumM83xPNNy33KwAjDHvncZSUXo8QqemMQAgi2sa0HtFFpcQxqxuSDRnZrNPyxX2eafGXX+xAS5tOK6XxFfLyS36ghqKglTxOGw3AJXbwjTbI1g7xGmGs74K3jtZQSdPTVDJa453JER2PeFWzf1QxCqwi2Gx7cB7zKQz/60RhD9vNO/rC/NP3IAzfhpw+MyZ9pWGmYRJZzIR9LCtaZoZWRESWeoKNRwhlAmwy8dfgYrVLdRqGovQIrX5O9dplVwZorU0Ffrme8rAtW3iwnn/EwrXjYyfhERNtEDhkRXWeoUtXWSp+FkA1K5hikuSzn2ww3REf1Q618qsIQgtuHkLmyzFr95OQV/5dsfjzGp9XZismTNR3pvvTpy730oCu/4W3VTVWMpuk+SFX3dmO8F3DL1aJmc7zP/FmiKzhNG87LFd4Vol9uE1IhieVdoKdG7KPZVXwINM7/SwOdI4f9/mq8d4waQX8rTKKdAT7GIWCiSwCMU56zbVg8UcNGjilhWAbPyeCaO0VRdBDG7ja9X9MMYfEJ1lbMgfTQ47QjRBqwQSXoYzSIBqA87tt1QX5+1CxlzNyfluc+Ns+j2G7qrIPRHNqMRtHVUBZ92jDXXh3U5JFvM/bLjG2dD/rUI2e5B/Th64H9oTS4vaDyNCcZc9Dnqqkvg1YsyhbVuGAcLKKgWlW/ZszQjzIJBtFlPMUNFcBj8BDj5Jfk8My9D8HQF+/wFBo+8LqdrFDA3KEqkcADHB9SuSbG1W6pN46WZwuxoZOMvaTOPApitcbsJ2crZpc1uwRwaFa9d3R+NwX5aCtOjrIsqUYu2eB5B37cC76UhIKKRxuRWsCHOOOPc3hTGREmbbFxxSWfp3Il5P1IWzxOD499lRh3TKdbT1qyx7bOyC9+oDPWA2VVJCVyJsGzbZ+w7hGpOB7jL2rp6O8qA2Ma5mlDZF0YoTfmQo1F2ai7qTj6dTYArAi3kYa60PmiI39gUmw8Duz1waGhx8VNehFfs4Y2hXNImF3WKVBvaGNZfIJBQykC6fXP6gwxa6RZS99jZ6tqVYLgJTs8p05NpZ8z2iccBbjGKdH36IlpJA98xfFNIZcXkT0BUYmiBYztI7KOWSt9mq5nQ0I7ml3XnpYLNruOxQPEEQ089r5mzz9LQTG4THouIcGwiJRooYyNt5w9buuM13XB6zZh48zjFBI+nV7xq4+fsTxcEJdNdVwAKHVM/l6TR96DOqmL+4kP6WCfZkyjYHmbsK0z1tusnqLWNGx26pOCd7DDmKFoNsaQi/MFsJXYLMljv8woXx66y4w0hGNCPN2OjjYCLUnDrHKWngLWbcaqk6I0WBJdxscInFm0ay0ekw0ggqmF591WNOw/xoyHkLgaKziHHXPcEYRzLtAEa5cILj6eKwDyC92OkMt982+7LrhcTpwBz9hLF5Lai8Ne3aEZN3LKD/LG8t8gziVGxcLAkmAVbMNDMDDG4VwoiJ2DwXcTCUIF16sKqTiezq8ILiOzBAKt1x3LvJGK5+Ar23ioa4QLR5vDwiP6EqQFgumVKOHvKb0dUAKA03JTITagDzyNvQz9t7vgWnjQ6LpPuO5Rm5/e0b2ePDVMHUMae/HYMk2LlsLB2TS9P8LN73pNtBmFu41l5I2/F+jH6VVrGt376xm4ypqCsorO04YnS+efi8N1j3jdJ7gU2WbRYG4Gs2t4Ct1qsDVgrcBL6tZ0lIAazNZg8TQRHy0UFpJe4CjL/DXHN5XP3YsEIEpUS6Xxf5mOcoYYEM7QhZLA7kzDiTOT2TXspXNAdx4pf0kU6Ikm5lAnIFrPgwClO9RUi8xN0NHtRxpU0kohJb8V37kf3oo/SdZqGxLbvwnF8T7jsiFjnnfl/sJUoFlyPNoZcnkV1TLKrB8+fcHTr39iJ3eSwk3rkYlibFM4KC4bVwV3WO/40LGcajjhTb9AjkMwzyST2srxZ60jeeATgOATNZmqxW2bUbcZe+laGABlRw+ejJbP/L40mMMqdyyK5EyFd/VAyzwc1aAZggjos9burDNcW6mCLENcxpLeyf7l4UDB29ic45YCUnXa2JQrSFrmVdkQeo1M02RD1o1cZWqY9vUduAFsDUkFE8uF5iTmUR6iGn1tofrJEQM1RUPcESfuw/AaE5s4SQgEEtuvC1aG3Gq11DOKO04PF8QzySzrfZb+0chyGTZEtcfj6WaC8bw2qeWZkootc/Yv9oStGTJ8l4EwxqtlU5DXyKUPp4nXqDUNxYpWS9NkyKChWoMA0n4cqzd5TzHJGe/be45Ecg10mTWDPTu81gl5UAgVKQvRKmr8s/KKcu+BhmZAmjmBGHgf6tHBi66DMGJ6JWFwRBm+5viGGToZoAKAYT1s56kxOluhHHZReMMYK5qI7Qi1aST195J0LGtEhvPEJdUSaIJtCgnztBHsEne4kYf+TrYL4IAh3x+yECSYO18OdnXja/Rf4nJuInhmNLEbDXwFMjCOroNg+KMNnjzQADFkzPBAjh6pAAViclnayE/VM6UveTU0KFs8+Fsa3mAwqAGKyXacdmXR5J24521dcEkBL2x80WmM7+ONVR+8rmsxZlUAizsl1nYfNE68z4jzpj0L2tCIX2NdoR5ByLQpZodQDRZW1ASgOi+PnEnuxR+YOmJNV+56G6LzofcMDQnUB1irxSWTzyXA/OfWtT4auh1aGbJd6ePk7LFuE277pDz0NVXEPeK2zjpDYUzTnkg83ZSHbhrpuEQQe6lkkR1ucCEhsDKjbIA1W7TstXqTe1+z1wG3kW31S8cYMBuMauXElg/Z8iiNXQa56tqIi+5sw/numss6GPsrVL2FgxuSYN0y7Kb8dFsw+4zJ54MMwXvrsVSLNQXkYnHJgTaXu88y3v9RO3+VGZqDGBnh4s3Q31MlVsxWG3Z+KAwMuxVR1h7+8OXW45sF9MUZ/HeeRhpV12QYMx433JxcLe1kzeDGfOaXTHxP6QQXHv0vjcj7jnF1EYIKtuhNnHwiCti0a6NTXO9LIgnd+xL2/hC2gPz+iGlDhnCKQ+OHmrLmeswggY6r+2FxVaO2X4Wtv8DXxrH86Qg53HPc65BNj1mffC3YeU2+DyAlj3SbkG6zMiPoc3c1w7GMF8qclPsAFH+kh5o17rmZuFWD50QYo2DoZ2/xMVqG3nhDMpX0Te4yK2saIMMproo0OUEU80aG3zIoNdBC6xYA6RVUg7JO1PTl96xOkos+oq7ZHWePqXZdmg7FDNS9RmlValUZLAIn1sZUuQZU20vqhW3PDgwu2XQtD4vZok8qGS8TRCgDZHJ/cnbAdUFe45uExLgK7xjPzh7r5YSffv8dtn3CxpCgDCfN00ZCdMzuyNkj7RHrFlWAzZiGGDKmuCmF0tmqzlaZZQKOfPYemSRwS0ZfW7+uSn2tDnt2KGYwtFE6a5fuKEwh3Isj16x2pCjex8O9Wjzvk2bZcp1OruAh7niIG5awI7iC4Aqiz3ic18N934vH6z6p7g/x5em1X5LFM8MruTYIaCJ9lvcybmcMFjeuc4Lqdo5nX3t8U5ZLHBhDQt2hgGwgtJ3iCI0M1sA1c+j4En5J2Lk0VWHlghn93qNv+BAKJsfThIPuhyxYl4vStaSUTHtUZ3lg4DTrw1KBDB2/H11hjGmamcvU3qEh6tnj0jTargE19VXGSHGoe0BeJ8LdEzdFQ+lOQ3GHjbQRifwp+T4eRZfo9YeVxA9aXoVTLkNOloaQODvTSUyZQPWZ9D8El00eJgXkzK9Zj+wgVS2EDHmRGNE1AzddqQRBrMViqaSjUk23g7svlYMvbzB0go82+Icr7GmDnboGO7IhOmix+hltoPMIIaFEB2ur8vFzcrjuZNAg2V0uFhsHdGFHiM7+zKJv3lZysYfB5z3iNTn8fqNzBcbBORzXLB+6GfN5+pBwMhUTN7IBployD10oh61Sk3PfIq6vE1KiisjZSgF6WRFPJMAmwl9+nRBiwr5F5OQhInC1dt3/MQDLEULCiLDvPIfAPuhv+jf6zBhiYFlbdU6gNYtSrM4xlOo1627tbVYO8MQzqH9TjWDpBqk6hmg8UutCdn0d9kNgsmDrQdsIAF73iEsKB6ejyWVMfJ8F/qKNiIaLSFOKq9NKmuwC9wq5Q7jlcv/lzMb7L+8pkgCiS1V+XpbozfFNm6LPaXhQ0RuhRAWTr4+YJQBVv4uu4czBXppPUtam2q2bgiFFu6043LjEptIs4rLNcM9VF13/PEdoxbuCeSIcfV5ucLHTAyWQ30MrckhmjdZg3Q637LDLBjMVwqWTRX2ZkV9P2L6ckW4TZJBn1CAvRTRoMuZlQrhtCMvamR1MY3TzdmDQ1OKUiii8c+MLKS4zPCRHLWRuja2pGQTAlci0k1RAINnexpnuDh6C0mlDy2p7Hld2bxeIzBjgKVRE2/m1EzOZnG1KC6Omd0Fi2QDgiFPX7NFa6Z+vMfwTPeyUCOuXYZlQ6VrL4snUHylbgLvM+pq5OHy+nPFnzx/xjy4Lfr957JUgwE9TxW/nDb+Zb3iIu4pOjSqB8tlrM5hdwYeY4YxTloL0f8SgAaD+wdPA2GjN6Nj9vUrmKA7nhEXFvSTjrzizPZysQ+m3VIbR6D++DK5gOl8xne8MoVufTRiv9Wj6IpusJENjhu0MyUAsMw3rvZ3v6JVcqxXWmjcVkQR6UTnMzDaSIJqqwzbYKY5wynsyx+8NJUmWP0I0kvXLvx2vC7QKAIBbDvi8T/hpi/hhd7hmo+KAZE5RsZaK1CjpnK3Fh2Bx8lSViUnK+5+zIdreDP1r+Ft8u4DubcNv5z7RJUpjMhLd0JsCxkCNW40hTZHSSPhdIN5+YdqbnXl2NF7tTCMNh0zCO3NIOE8rHh8umJabUsDu6XnjYYYb0Yojzq8vb7BoYxv9nZtUGlxMA3yD8VD6jokVdrnC/fqKWWTYQIG+vCzIrwvSZaHpzkqTrGUPuHx5RP7+Vxrop3nDfL5ierginG8dvpGsj5kich42ZNgpwU67+kS25FFuEeE6tBsqxgAAjDNJREFUI9/mQ6ZuQ1Y3HBVz4maYUNvE9GIXB6A7iIJUDMlSrGg2Kqwnh4uhByYYmjdYfMLToGM+uvO4mHovQTZVT03P8aiicCmVCG9E+UayDxIUQ0iYpx2//fgT/nvFqWqlVGSij6Ovy82/LQfszGOXjP6WfVfWEzgQBBfW0odNdm9wch6pdkqq87mzcnijqdXR72diFH25U8hc5g2PH77g9N0XhKcLbWrZoW4B+Tqrf2gtlimrdC/dlLSCpOBPDkdkOO2IyVTJxet6W3DbZuJkS4AWmIQDrjMVc9vZ3JyrO4Yex0pxDGSONWncOKBnWAueeejSWL/lwH0Zj7W8rd6+Fm6+hz1Ufz9u+DCvquVSmsW6R1xSZIGuPoUbbMXvTjf8jq0KWjO4ZI/fbwF/eXP4YTO4Dun1WhrqbnCxBuJQJBm7KD/KYJXF0ZTja49vx3JpNBw0Hp2SSF9LJj7Z7sYtBhcAVM/lxmO3Bz0Y0x1JKqicf0kBiUtRZxoeC0kAzGlDnCxalcEZ0v0em4yKF7PFG0ZYwVVtbmLAqNsmdMXOxDAcRDEV1XNGBVqmAC7wAP0iZdLh6Qr/cOvQDD9sknW3ajVDt1OCm3ZqALrKQUwgB3f43MaXg0EGQDz0fJ2xfnnE7eWMzIYdPiZMy4rp8Qq/FA4CDtYV+JgQM+OzxSov+RQ3fJeiBoBbCviyzfhhm/Hj7nvmyvfOa8bU1Q6taTr2rwbEkYy6/cNNq5GxeVyTB/jaj9dJBqVaM0hrRFon3K4nbHtAKR4i/KUiUgPz5L2jFHcYMuoPesFT3BBsxVPoRtMkB0AY61q7Gt9aKGiN/ZreI0hwripF8Hpb8P3rE/7y8oDfrxMuxSKYhj9aNvy3PvyE/+btz/GpWoTHC7GhLgu2lxNur2QfJywXUfycTmu/hjyDkPeAshMMIwHWh4wTboghHYLzPR1Qrp/oItUBvnsPS6dzJXkH59B7M80MCpsAGkF2iyf++Ke73sXGg0jStBTeOsCDUcP7yfMvfQ65f4Gh2DX7g+WdNQ1LSDiHXe+NqDDuVVRGScPI24rFVTwF2oBOdwNwAAt18ecTNCFxkBfji25w0emLX3N8s4CeG/BXKz/QHIhn5pCT8zbBItE2lcoMzPWtfAMyTyHSYJE4lxA1CI74nBQsuDnHAyNy46TLbe37INW4cI1pMKHAz7ti30pd5AZnTb433oA+CcqHMRVGgnu1MIkz90pBunEDtGV+AIrTrElkAQAKxNL4C/NGFQBAn4mDtFL0kke9Tdi/PGB7PTGcQ/BEmDfE8w3+tNImA6BlS9TKp1eEZVW4yDJ+PtICjaW/+/MNizyk3Mjdnh/w8uMHvLw+4rLOpG3t+n2cbBt0LhprRQ+buTbH+8AM3ROrjeuWPKoZPVK5BzFABsYVGL5OkasnNIN8mbF9fuRXPSHn2il3OaAMJb2uhWGoR74nVUgZgpRklWtxuGSvOtylUVZ2co1lnjtlc6QtVt4knM+I8Tid+/FXP+FP3J/hX3ynAW8HuKXuUa+Bjwnz+Yo47cpMcjFRA5krSwBozuga8tEdGE73Ry0O+3XG68sDLtcTLtusuP3EPrshJPhA0KRALTTbceStG3N8n9YMkjCNOFCXd/B8cZbSkXoIc8jo3w3f77H31hpN/op5CdA3Estqi8I/J+XQgGsmf1Thm+v0LHplUMGwTLXIjH9LmyjwnM3kwNpU/Jnb2ylSqR6s+1vEQweOHEyAsTA03u2YocJnowMcpt9AynqouZab0HuM0nzu+cEE6Vg4NBi+KcFnLKcbzh+fOx93oPhlZgBYV+A8KMMOFCQADtLS7JQmJ6QBKmyWu1siP1sNl7QWKJydjYGpGtRsDy5CAA50xFacsmWMK3TOtsFY5l2HDPdwwzwlxA+vak4gn1uofMpy2WlJtOJgxM6FD4KRhoYuwNVBg1GRcP5dydAqs0QY/9yqw7UQQ+nKlRWZe0MFugxnKTTMc7x0Y3Cn+2qBypIFgyemnKd1FraVbhzAtEVh89yuJ7y8ng8j6vf9k1wdVh4ieUlRvUb1dsqfkmi0TpcjZ3gOFo3WYzZGh49mSwHEmaoQhbkLPmO6aX3F9HhFeLzCn1YYDsayfrQS48PjxvfuLmkZ1+kfOAS2SZcFaY2q3Dlq9AvJQLJqmQyV8xF4ak8Re/LKTyctl6q/Z21DrQZ7Dlh30oO55XCA56xpQ9Oy8aQp/zt/z0Eq/Kpwrdwn0Q1K1WJvXZzLAjgHSvROcVM99JgCVR2mIZTuM6v3maUJaiNaLkAORWfW7AEENSA6tkArpZFT0VYN66F3qPnYQP/6kP5NR/8/xQHrNn38tbuov+WByxFsxeIrPgQLZ8zBUzTylKhALpMlJbWnkPAUV0RPD9ASd0yRyiihsAn1T2AVFbsyDjWwg0zJsJxF28C+nNNOWa4/0sV+9hh56ZWyQzclhUSU8ZI8qmTu/KDWbKlRdVkOutWqKOgqRt0V4wqsZ7hHGDeeNiUTCkwsiqGbkmBvhEXbLet7qhzBFogCiL6hlD0cLPVacaptAwDRZTRH1c5j2IfGku36JlxtXbJDa9A5hI8xHrxA6TypUhJGiwwLCf5f99g30cZMnut0yNwzSxYYU4mmVwVKsJqdH96TKzoASO4IIdS7zN1wM3OtZGZ8z1sGejIjpb9nE+vRhahKsGidtRGqIZXFme67EajJNBgLmJjRateCrxtr299mTQqsq3DzBn9a4c4rrQHOoqG9BqOMIJVfLlYnnOU+LKcb5mU9CNSV4pCyx7rNKNdO/cyVrP7ykHE71oTxsqkxX12uJY3bF3rG37mO99+z3GOTv4t2y3u/62xV+TwLaqh6ngqVzyLrZay65H7KPIwki3TPKHA/hoqTf9sfTBXI2QxrBsqI0UTUEFd95uZp+IpNV45vB7lU4K9ufdedHfAYiAFx9qRqZxk3nw67LE1ekVZGRYmAt3YwiyVe72Q7PnZyBU9xx6f5hvO0YvLUeZ/ijuV0Ix9KUZ2z6WBmMWYyxtGgkI1ZA6Bm4QxzyJ2te+gBJvkBY6cM17pyhEreyehbscpOKFvoPHQOaOHxyvAHPYz5dcH2fMbLjx+w3RaUYuFcxXK6Ynl6xfTxBWZogAJAS46yO/maG2nlbhPRc+fzF13xugV+f/MGKx0PMVtWAaRqhzLV4kUDOq2N6IDZEa94L74PWdnezzhUNrbBLhvseaVG84hrZ9oYxf6vFYf9p0ekleh5P748EUWRrfNe2Zz4qj6ftKZmVzC7gnAvCmWGZrMBpkZr+bvhOpRm8bxH/LgHfL95fNmp6bU4YsEsPuNxotF6xaJdgY2JuOYD7FKLw/r5AebLua9VpnK6aeDgc/M3XSc1/W6NPEMDTxfTNPSGBkcbAK83qXYAmk2QTXukA1oAcIWSoCHgir7RuBYkyKcUDkJo+u/VosCi5f51qo5NvJnSN1zj8RAsXYKmM03Xs2rYNxZaY4KF3Mtoq5pBS5WxslqoMY3VI0neN/FwnMjyknKoYXG1t+t9PER7SjTS5edlEvSoOwVl9ikkdP9A/czxzQJ6sMCfnHo2G2xjIS6x82IqkWJVvJjZukl0MroMq0EBKzFWp0MdxgA3N+yq1WDyQTUjpmmDcZWMnAN3+yV4cLYJoOPFjzfY0w4zS/MTwG5QN4+2BcW55feFTSIwighu5TXqAwJ0doNk13I0ZiYo3ZDH2P28wwNoptJdrEYD/VKvZHzAD61l1kR6OSPf5sNmZX09KD+25DUA5GFSVCQF4uNVoZpfOmoh6mLKXp2WElt4iYrexlmdGAAH23DydD+9IZEusQ0U6l5hK7W8Rtgvj3oujplG4eEG93ADYtb+BC0kbrABaIU3p2FjI35zUxPxD9N2gHu0QXbnKCPB3A0B9+c2tNllnH3Ar2LAlTO92VZ8N6/47fkFD6eryjMf2Dy+b/4yECbN+T5sJgnG3efgKvNgbVgN6sUh3aa3TfGBriiHdZVw+KfLwa4urxHpNiPdqBobN3ThtY+vZQ2rhNp2MFTPlXpEYjAO0HNu+foa21RvfRwiEnKEvLYwVeQelWa7c5TAIw2ozerYvhp+m24nePIJERlmYGIBrOmi19VC/E9l2lc/y7heDPXyRiC8DLh5bszyMsegbY3M0fxXbBL9/+1h0CVHDZ9MBVRqVG6VMw2hGRRj4Rqbv1arhH7K6mT3FSspCgjCZbemu4vEQnCBsw3RZ9Kg4IAp3Oqa/cGCrTVL4+3nG1qz8NXAlv3Q0ESxRwPo1od16Bwr4Fj8at4wfTw+RHWPqudS1ol+x1LzKs4bbExvpkgBcCO2f9svK/yy8gvbXhEIT35Q55OSevysZiIoYxmavgBVC4I9l3VCu84qPJbXCWmdkJl9U4vFus5qHi3Z714c0b8Kubys8lDzGjj7hhOvdG8bngKZFVjTkLOH3en8MwBjR5EE6BSrD4l6HKZr6dzPCbRqsH55xPX1hNs245aCiq2Ngftw/s2oU80o5tQMaZOYdhxe0UusDVRJXGo3YABUZ12hBsaeDQePDO5T5KO88z3TqjAfXAO9ZPms9e9kY+BqrjXDzJ+jK5U0VseGq3XsRasVZIWx9o0MRmeokD6S0C2F+qksIpcRGJos1cJXBy8uSYNRxthw7rBN02Co14NfZy3dCu5+U3Wmwbl2+N79pKhcM8+bpNyTETITUkYQTN1WLAPsIpr/ZDXXMXXJyANDidJyUuOTu3XTVRjfUix/6fhmAb2CArEcRuUlG5zpDRPH8Iq4c0sz4VosT2T1HUxKF8KdelO06yeLQS16ObVHXF4eULInLZeRIueqZqKWM5mWPMplRt3izzaa5O/EsLhjuih2XQlvH7PIisNDqg0xgRfGzYInH8fvKw2R+e/9Yh9x0fGz2JCJFy9ZXbKotwn5OqPc4qHBJtRI+vtRA/0g4DQ8iMRS8Dz95jQgHnjI3O9Q2WTIFCZpeUigK8XD2oJp3jCJZsu80UanWK97s0lpT4QrolYN+cCuM67bhGsi+7Mx4DqG+UYcVqRURaDJG6qmHOX9bzJ0wU/l76XR78rD35oBDJEAJDvVLDc7JHGGGhge1hb4kBFiggsJznVowwu/m7PyMWsHqGrSwQ0Qd9+5Chfk/h6D8+GozC7KDcaaXsWCNIQC39daHPLqsaeIdScv1NEHd4oJMew6MWqLRZZol6GN5AI7QDsNjLbB2woDgmLnAcveiodldlDmoC4qmPL835/T26BftdIqTaRzDWPnng2h+4ZBGDpUUZFimsXn3eCHreFzyri1DAuDk/X44B2eomFP2aYMFzF9GedvxiFL916m8DPHN8XQf9jGgC4nYbT08Ez1OTmLhZ2+vQHvfB2T2itYBIecs2lkllzVLUht8VeTx2/ngI9hxuIznGn4GDf8thnEkLCcb1p+SmNRMxWGJ6j5yYM4QRpP5m2A5e9X5n2PQfFeEx2AQhg25j6IBKDtHuU6Iz2fkC6LMm50KORuMASjxdhwrUfsWAKfsQ1IHmb3it/T5yYcFfIz6LIFozGHMRXGMbxU3Bv8XDZMeRjISoxEq8gvlLw1gbEh3m0GqXxOmF1SqVSTHbzHwWJNqaOuwIYG49dDf0Ovx5htZ4fww41YCTuxVlyuzH+ngB5sUfNjoi92GQAptWEpqHum6omuOoDDQJVm94nkWslbla4JYfOZse0KHzKcKyTMdZtxuZ64ymG9FVswhUTvN+iQiyb/NG+EpfNrlj0grSSsJc1lUVuclxvCMtAWBzin7J6NLOieO19oGjd0DN1Ymk9wISPUTauHEBKcIyctayJqs1SRW+LTp+w7T50rknQ3iLamgNd9wud9wrWQ5IJs8mefcQ47JpfhbNOqXtgrFBDp/SZbsTCMFiw5YO3F4ZYJ9rplr7j77AoeQsISu4BfrlRVpsaN7kIBfa8kvCaaLWvpmLezJOv9Jz7AmqDPoygoroXkL4COp9OfVH04pl6fvciK/y1oinoLfDf1DyoawO8R6anZ0XdCYUVQM+T4s8FSpi80IvKsJB2Fyk0MUxxPInKmxeJaYdqVqngvTuVjwvJwxfKrZwrokSVKi0G9RWokrtPBTEEwzsqBXiEA1hEZs11sgeiAd8YXrRptdE1qL0dsg3ybsT0/9IxwfD/OmJztnGM/7/TwsmRvzf3nNXO3fVMbufbysNcyHZgv9PCzEBMzbvY94npb8Lwu+P52whcOJFKSKk9YS2fJRoz2TLxpiI0aY0WyY9OADFjrsV9pitXfpn5NYyLmxkKDVQcm0VhNuYrwcMPDp2fU4jBNGzNemhpzj8MxuTjc1hnP6wl1N0qrBZ9Drha2dBVCUWWkiqRrbqdKm5vCiqavfVIatKyrQhWFcxXLvJLezLCJkWerUw9XMZuIcce2TjRub0m2YdsmXG4LXtloujZDWvPThqfTBecTmUdLVaoaRK7Chz45ra5GkirLdWUJCTm0h8SfNfhMgbp4XLcJL5cF1xSwiv6+JYkHIj0UhbxqI3em72zFpzawfDjTFwNxSRym6rD4RBPKmlRAR/ulaS0wCXxC5M3h+NrEcCGuOt3HXDrDZeUGqEBps20wweDse3Ji0KES0TffK3BJwGtuuJWC3Bq8MZidxdlbnD3YbpMfQ1BmbnDE5//Q8U156CO5PhXgNQFfUsW1FKRG4+En6/AYLB68w8kfL9BWaLfLHNFpNzN4DPRnsNQEk+GNR19wDgmBS6uHsCFINnRZkLcjLhtiQmBuOvl/VtI5+fwAe8lvOLxu3mAHbNvsQZUIpZHiTyvC+Qb/dIFdduUot50Hi7ZAmX1h7vug+yKTpW0H2uaYQhg73PDOYA2AAwRRmHJoLE3D2tN6YO3UxFXB64L9Nqk36c813Qrre6Q9IiWvWe/zesL3txP+8jbjc3IYKe3jIIUs3GC6FZ1OigI4uYw9e0waKAosi6k5X1DLoDnfcocn5N4I5S5Nh0olXRasL2fcrguutwV76eYhjst6CQRUaVCgDbYAQTBxXo+c9cmYSlY9da+m0o2zR+ofkHtTaeQzGl3Blo+a4u8dwnyJPMk6GoSPlMttm/R3rK34+PSCTx++QHRgmsAJ/LO3lfRsRG1xmjfK3AemSsmsBVSH5vzQtBW2VQVg9mPvQZg1D8sV5/mGe9VFgehG/DwVhzWRciJNGvf+hrMWpdHswwjLeFdhjHzmDnNR/6ZXSvrenJljeA3aoGkjlnsvEJt4l9LwW8PsgHYXQVMz3B+iGJWr0QTVWeAxGJw8rzXQoNHJ07BZdPTpcjV4yRafN+AlNaylvpk2/bnjmxpcjDvP7BoeA/DfMERDtIfpsePppEr4e64GpTVslWhKOoRUBLahgJFNHyUOpqrOMkCu45frCbk4HW6Q8jWwV6J+ikLMgLKHg4LiyBoZIQB32hAer6rvAoCCZ8iHAA1emDr8UogKpk1M2ztuTTB1ngDN15lUBIEDa8XGwclHgtv95KpkraYbVbfMmfsAFwBAa05vxTGIcGZiCwJjsdY0PBaPP6oG0RZ8l8MbuhepEvLPo88cSN/DK4ZOP1WqpUEnAK0Rn14qD8O+mG6myUe7bNSjANCKQbtFtOyUiinc9CQb7qC9vmWvfpaSSQPHbM+ZCpiOoyaGlOrwOvcc5daoQSZc+7XIAFIjI41mWEmRWC4iyiVWczqIUxzNTgwSwrQ2qRljBvqgrOUQSSLaxUEoi4OzNLLHY9+jzhDQva3qQeqnfYBcuuRv2ck+rxbWGipOA7dsjFQBVeXZA10+IeWAPXkYGDTDmLvPODOUQ48JXes1e3xZZ2zVHc61W1cKQ46oztFmhFBVUG3PjnVZSJtFZLmjKzixqmK09POwIrltWVyuN2LlXmuDE7RBEdnDqO69HELWAHp1Rr2+htSg/Q3B/8/ewBuD0o73B6/42eMbarkQ9g0ww8UITekIuRAN0TDG1IW7tgLcyvGCAeDmQsNeDQcHztidYR3nrqoWnBjdJgSfNeNxvmjTSTD0UZzKCtdXjqFB1BfxQD0cAkPdAVMNTHbArb0Z6KBf7pK7LfPI/23kjnuU24SyRuR1Inoiww4AYZr305zKK36Tudv+9+Hw8wY/bwcdGtkwYCRYOnLEuSxYLyeknYZKAMZ6fcZD3NWHMQ3DGSt6MzewvMPsmNWCpqX44hOvA4tWDEpl3Y/bQtz7LSpP28cE93Kie+SrnlcrVvXkZQAmr1Et2AxL/Haec1Na68G84m6tlcZQlS2w6JlcQ6Mmf+sMCTK+NnCm0/JknY8ZuWTReu+k2SibgzHATkYTbp+UOSLZ9XK+wvsCawtNc+4B623G588fsTOc4yxBOTIU5B+uMLbSsNU64XY5YWOfWIBEyyaW4Z2wdqpjtX02QRKcQhvEOBwlzV9rK/n38nMF0EaUUkBIGTG4X1RyJE44bbpfUjzg30HWjy0Ab75SbQVXEQYji9YMrsmwfrrHXqxy14OtWAwUAkoV6qhE67cHduDIbLKQLJ+qL9FpoTVOcW7iieh75kptwDZUsLTZd/bL16Lo33BStOFT7BcYGKapmP0iJzHqHQjlZ3KEw5OV1/HqjBfL8H+lGtyKg0sBG/NPc93hbcHMojtyCIZpDEm0kggV0cisKxSI7gMjejAHMAxlHD/bG5NogL52hbL3iQysYRtPPzrUjTD6++aqjQnTfPfZK2XZJQs+0wN2GzcNAL80/q0w0XA+1hXUbGGsOcizihnIuk2qKS66HrPPhEmyvCxADaWRf+744Ru1dgAZI6f3z6JvYg1c9sg5wwfWXwepAm48OHMIwgPTZfwzp4CN4aJRusmzoQEADrlAU5lce5BydSiotmp5P/4sce0dVQOCAYPgPwCYnGGDi4qPcccSEoJPaiACEORnbcU87Qdd/jFjBaDZL0BVpGodZacqia/rguseCUN3tUNMljZI2KbrS4wtRPtbGCk5edR60k1npIk65s6LGJcxDSHusLb0hm8kUw6iUGb9vH5PbIgRtP+TUkDmGYYtk2OQnPMSEib+jMBQAUAqAeh18ZanUDnjbjBoLeHEY5nRFSQecqPp84Roe/CXBn20BbPrGbSur2GtVVBAnRzV2sGIt0PH04X8IYc0S6XxKaS3xhvCfT/xDx3fEEM3h0C+F+DKk4JrIeEmw8YHJw9uGtDFFe/Qyhl7lkzfvBXAMeh62571HGRqcWQhHD4Zl7JioqCTmGza3LhBZQzhiGHe4RdqxknmbgNBK3ZJQKSx7FYBsKpi2z2ayg3wddjCgYYojUg1rZDF4TPckgleEMnenzlatbQp3KYD5KDKjHevIbTMfKX/Sj6We/c6IwJdjOU1deqrPkRw0CxTMGfKXuSBNMTRZzhMeNoSqISTLA+ucxXTtGF5uGB6uN5tLjyazj2FkkmGYNsmbHtEvoMX6H3qQVVzNNa4D5pT6KT/A1964ErT+VkV5xJWjOjTANBx7mDFq7SqnZwxFdYBrrHpCjJGAav36HcpBTy/PGJNERtDNEaapT5jDjse5tHg2eL1esKXS2+qixvSFHe2ZByGmbgxPeL8uThmCZFuuWT/Is61zKuKcznLg3OB1DLvKZ6Fm/Pj6xvTEJmWGd1xEzskG63L9x6ooujPd2sE5cizH31BQ1KuOTGvqKoktyIK6rk4ODPrwOJerSZFY2Af4cNgG3X6jAwyUYzaamflydSqCAoKmUMCv7MjnfftdOzPHd8soJMUZr8kiwMeWtc96Dfsrb75WIYU3ckaguZU0AtC3NWGk6t49BmPYWc1NZLEPImf6JAZAYJJBrQixggExcRlO5SYJXnkdcL2+itlxVhXsTxccP7tj5j+5EfYDx44LTC1AnuCWXdg29HWBOGaty0QjKJSARLUrbJQZJE303qFYJvqxwDDwy5Yfq1oscCEAsfDQfTvtXPW/XiNB7EuWzWQiPmwFRMFSx6URJ/MCGtClAy5WOx7hN8nOFuReAoQgA5kyP0bDxVeGjBQgT8kYwouI4Yd82nF/HRBfHplDXSrQ0910JXx1SKeVjzccbRT8thuC24rB3rGYxOzR2gSsTfjLHqzVD5fbUYz8jrIuRaeRPSGJhcloG/V4pb7QBxl6BYnR/IGtVqt4OpusbE0wW2dlbYYfcYcd8zTSnCJZNMhEx1xTzgNZiiBfXPjvJHXqKmo1WHfIm10A7Si4li8QQh8pswfn5T5Y/g+SuKjjen7dShfy2b1MxQ8gZhInAs6e7AXytJHgw+LppRSYxpS9rjuET9tM76kgFsmAa3J0vV/CAnnQJk3rTtW1axdJbPwfXalYjcUFmmIiaqLh0hx49Nwr6XXIpIAAFWf1+ywgxh1IpEsWfb9YJFk66nStChfDdgqbJmvZ7gA35jlMpYSuRpWHTO6gwHdE3Qk2JcKrNXgkoBrIXPV2tphxwSAxm4hH6PD7xZL+tSxYfK0+y5hxzKtOC03zKcVVhqgA4Qimac1bFl2JzcLoE9RCkbN4/7uxJzoSwXWDLSGtgL1JaJc5yOMIuqHvsKH9e48jpMFZEyxE0tmqh3Iq/RfKxZIR1iI3mMY2W/cWF1jFwIbx5cH4TL9DArbdN1uVMqC0x6wb5PqdguzYY47In/Pbg0/rQs+7wF/fvN45pd/ig77bOFtxcknYGiaiTa5BPcYMnlYTr0qMr7QZvgy4fLDR7x8ecS6zajVkLvTtCNOG+K0U/CBeJHe4FxWi7eRFjh+nbPHmiJet1kpd8LQGdX/3gzj4JhJAozPG6Ks6fDL0DSU/+R3Uwq4pYiVg66YaeyJxK9ksMgyRj3FBGO23rjkINoqbWJyhJCIruiOph3jpnf43K7oa91n11ph8ibiQn5jkP7eBHVrhoS0+PO1ZhRa0mqUm9D2brPQxiQnOeQDmvBH9S0EC1AQH+WPhabomNkim3aqDiWRZO7BwcwQPCNrM7HlXAVLC/DAUaoGt2JxzbRpZ87Q1wJccsNLLrjWjIKKAIuzC/gQLD5Gg8X1HmLiOHjJR6mAP3R8s4CeqsE/WXv5K+UHdXf7YIzwz1Mz2HLHlMRvDxB/RoNggGAM0+CM8psfA/AhFCyuaIZoweUoL+x4Iqd4DIvt3lmlFovt+Yz95dQ/NzdLxf1Hh3yqQV0ji1dRtqvqd+tETbktdOjGVbhpJz0Zyf5ZJW99OWO9zkgpwNqGOG04Pb1i+e4zwsdX2lwsybSaucFMDfCOLmouaC8V9XNA+vxALj1MO5MGLxliSKPTou4edY+qww5AIQ3h1GvwUw6602Auh3cFjUtauaa/Pl3QGvAYJrxytTDZhoeQ8Mh2ZWIu4AwHdFsQObv0QyUlUJThvoGxFS4kzMsK58qhQZ35M6pMcJEA0tUMWyMd7m1nFyKmKsqk6F4cNp56BaAaHfdqipZlLYLPOA8aRHtxOsyyuUpaLo4GXyaf4H3R3oqLCQvI+u+8XTVbLrW7JK3Xbtg8h4TTfEOMxCt3Pmsmfrv2LL81kipY5hUPDxcsDxeCQAS2aiIt0A4JQWMmzoEOy9dPjDlaIyXHGHfE00rerzKQNswzqDvWkESIvG7m+1FE26WZwwZSIffs2LcYjwNMphUVbb70e8RyuWWv6okGUF76Oew0+HY3CT7q+BQmcbx3GBCbxTPbBYYy85M3+FQ9GvwhYfWMJqRqNEsXwx5SbHz3bd49vrGnKP2dYBHCvmcrcAljjACAdggUufVJKwr4/XWr/M82mEYXiII/0+UaC+5YGktP2WPfI9yaD/StkWkghwSyMdB7Nppw86ZSpIdjzJIdi0idV4TxwRDp1y2g7gHlOkE02dNtInodl+S10mBN3iLSy5muH7Na3LLBxg0meuDxEfAe2HcYPMNuO+wlw+4FFY44xKcV7ukK9yEBEz8ct4b6JSL95FCuRNGU62FdJYMP09k5NXm4LaKEjJh3Hf+uhYZJcqbG4Hj/HMs4CI4sOKFi0s3CNGqA0s/Xw7BPqwZ5i9iezwe6XOUMNERqLsqDrXZu9agIOWahOtmaPJ7XE76sM15SPLAooiMjimB5wx2an3vpAnCa/fGfXViOVQUbUW5zI5eaUR9GpScEzmDKoGSoAcA87XjE8ZDNVDR05BwTKweujKu3ZnQCMqWAy+WEwK8v8r1x2onhFbshMlDeMKFaM28CiCYnXAnS9DQ3/JhSKwN0rVkdsovLhvMedDjudlnw5fkJL9uCL+usDfXSrEIc4/UW9cTZZXhb3mTX499bE21+0X8xrPlE2To16IvCS7LZCyYPjD2h3jg1AEgNtKFTSHuPMFWDrQBbbRqz7PC5uj+EGRJcwB0v+y8e31QP/Ul5y7KrdVhFy13u9JbaO8ZyqyZHzQQMU6Z7pabqNZFBqwVQmsPJy81ziK2gloZbisCVSurL5aQ89BCI3ywu98ayKS8PBbllIy65lJKSoSRPgz5gWGTZYM7MN3ecHmSgraTOqNKvd4fhpooFKIA6onuN/p7GFeQ9oH5+1JF8P+/wtxv85QKz/Nh57gDsIzB9+IwJn4c3MpRKWK/AnlkKLHZE/xn+4UYZsHDsR60Y20ia4HUBfnxC3iL1EJg9IQMjhwpHsmDGL9WC0FXs1SJwgPQycFSbBrpxcrMUj9tlwb7Fw+Yi6+Ye5nhPQVA+Y2tH9xzvCh7nG4LL+Ki4usU1RXxJEa/Xk45/k7DWIMVq2iGLA3gylhVCifMObo6aNz2Ew2duBinR+P+6zdh5sxKz8okhJOeL9i1SCsgpILPQlbMVfl5xMg0fGR8HiP1yvS34cj3j+fN3uCWqOk4+4cO04uP5FY8PrwgxaUPzPdEuCr7ULB3XJpDgZ3b34spK2V2u92cAbsIL5FINjCGWjoiKTT5h9h7OUuV2yzSc9ZI81kqBWXpkTzHh7C1ml1WDR6Z2S7PdGKda7NXycFffFBw3RhdPXsPe0bXN1alZdRlUH9fi8ZwCPu9e18QYoJ0hhlCpBsUSLk6wDI35i3BgdGTK403H1ws3Ulf+2a89vmFTFDj54wc1kC5vx8kagI2nrqRjXBoF7UuuuNaCrRU0NEQ4PDiPj9HhN7ND5Ex/ssBDIDefi/iKmoZTTURbbEYn7yDvzdiwSNCKuW6rhrFoVp8DelPx7nxIPtKQX6hgk1tAflmIQbJOb4LM4deZPy2mAqU4VbzzviCc1uNDw1m66LK3HYAFDBFkD0fbgXaxyM9n5NcFeaXo70KGO5H5gT2vcGGoUu5eAzbDbH1iloLj2+BJEqL0vVsK+Gmb8Be3iC9M6Tz5hk9s7eJthWv1IEZjDtlWU4EqLywkU1G40qIAOCHxhGtp9mCqoHAOQzmBKW33FEWhvAlEMfuMj6xVfo/RvsdPl6abNFZbG31FHW6FmmbJGzx6yqJrNb0yrNS0/fLyiJ+uZ7zuNP05+YzHacXTcsVpuamtXCmeGr3M5KnNwrJ5x2m5YTnf4KedqKfF4VNy+M3O/qFKYSwHdUaBtmT0X7V8ZH0mT/pCVeAYtmezhXXVM5EYRPETBWQlVLvfremB3jri/luQKql3dH+E9UQOPkQt/PVsVdpWsvbUDL7sET+0iSuRdnAskqrf8iYl4l4ikXFyNDcxsUaOZzVWsqGLeN1JxE3u5cbnS+w75tXzehDjFrp+fahodgAZQBumK9L3ZrYlFGGwUacqVfPVsMu3U1tsOKgtikOHGFRI7CgNyF6mQo/mr4AlyhufhjRKamuaCY0TiKK+Rk0pcqB5nG/49OkzTo+v8HFodGrDpw/W1GKRXs5Il0WbQ27eED6+wv96BT7NwHICagVeXlG/r0j/5An784OO3N+/7v0xwjxi72Y9NZe8NFy5pCW9lXtYiF1s+GexG5RbQHldkF4XglB4IMQ/3BA+vCL87hlm4p10a6g3i3qJSN8/KYzx7tFISiCvlCmHu4wxZ69YKG3O9OCQwBJBPwDUkEQWc6kW1TRVDzxsDraRCNXphunhyh6vDXmN8C8ntEriTyl7NNHUlgngu2oBALNauv7IzgwXmhi12vwUyCW6DD9QHFOxPCpulMmjQyeG9NUBaPBZi0OwpBRaTXfoGtddqxY2FDw8vSBOG36df9SgWwprvhTKsi/X04HN8vThmQN31aGqnAL2LWK9zopzk1/pjtPTq2bdrRl1nqosRTCuqxFu0CYu37cYd2VWWVdpqvo69yaqrSomN/aaWnE8ncx9GIXrnEoCjA5GhrPo1ogOq8+NNJa/YgRnlBgAoPc4uoGy+I4doNyfOqyf8f1prZL42l6JWi2ogmeIcXYi7d2G92dqYwNtcuiKse8G8p9+/ty+MeTSP23PyoG12DcNB2cbnGCRrYvK52L0ogEdtjGQsdruYnTyGWcW5XGGWC6ETx5F/mtxNEU4qAhKYHW+ED5YG5qtMAyz1MsO61dgZxbJLVPgjJnYGDH1xmLyKBl3Dcdeukq24z0NYcRlU1No+XkwVl0u5GQvD0x4YJVEn8jn0zbY8w67JITfPPcL6hvMYoCnBVie0GbCXc31AvvlGcYR08YIhDTt9DqPFVgCNV33jPYlI39/xv7TI/bXk5p2iMZLTl2jpBQaCDn7hIfgFNsMjIEGhiwqjN5rYJie5OxcJne12Waq6orMJ/rcwlwB3g4VASxwxWW0TCICOPDM5WjNYM8eW/bYi8clCdVNoCPHrjUMD5mj09ZIw3yKOxamMgJk4vGr+YYPywWB31vwfgDc3DUwltaPc/TvkXsAUhFZDpwykCOvk1PAukXli5NZd0FwmeiMcYcPvTIViGVks+jmnJ0OjtF7dvkB+V2hMrqQuIIdnuMm67/BsIWjGL7srKUkUgQ5eR0mC28w8bfQlkJa/G25nzofgM5P1+qJK6kKihuTKwTNtC4VItn7EnYEW/DUNtXuuaSIV4yuSKLlQtIkpDNF7+mt4SlRoHF/75cOMe4RWd2vPb6plsvhgxro7mTYeQiAZjDCgAG4EVoMY0zUaGgQLXQD3/rgBhMRVFsjVafGBJl1JBRaYR327ic60qcqOfY8vSI8XWEV2jDU1NwC8v97cJu3kXjbpw3Tw01/tt4m5NeF5HDX2IM6mwKUwbOxZIfYmHteDSzDPyNlUD6fqdRMKutEP7uFjnuHArskmBOAifFya4F5ApYT6ukB8AHNWpg4wcwLzIcb/LYCeev3yM/0e3asLjLqFrA9P+D185N+ds1SLbEe6BwNln3HEiacctHPLgFQ3V0qANs5wtSwqyzJynDYHpBMU159yQ7b6wnXyxnX24JtbOaa1hkkd5Q7aoQ17uNQNi+j85LZywCN2yeYROPtrRlEU7DwVRgDzojdSjCpQ1NNsvnG5z6uM4E3SvWHoajEPHRrCwJn144nSwHqA2XG3MVYQj9PJS71ZI6b1W2dcbkt+rXg8/OyqgwvfSaCG6u3KjYn11C0W8bP73xBGFgvHUayAHaSvhgzdIaarC3wHqimKcS4pYBLiqqrczAgGSohg4bgutk0QMF8y2Ju01UYJXmMtuo8iQy1TT4jCEXTNEqc+L/E+uiN76MMIz0CmNkGU3R5dh4ikvpZAnuuFNwB4Zkfk1BtjFpCGWb3TyGgG2P+NQD/AajI+Y9aa//+3b//WwD+1yCDld8D+F+21v5fv/SaqQJ/fpWHqk9Mza6T7znGEy3RtgOEK3Qf6TcqvMKvJ6OzxhCFEZAprqIZuuBkzheFMYjSV1Gtg6kVloPXqEaYrzOsNAsBHES5hky/ZQfknmu2arrhc+0PgHEN1hG3ejyUUVMs9uvSg5Cl5qTwfbXB1AxPgzrg2pkFbt6AssJig0HuT8W6A8+vsPb7vpKspezbe4KPrCUIKWcg7cC6URVSAaSGegmdXRL6BOB4DnIIdtlFk6oGtdFqUAd52KjXuaz3SYJDLaTjUnZpwtIUo7UFMeyqBy4UuJwdavMK/Yg2iBpL3FHfxkOCBw2j7TjFvinIlGjRIMdJCM9AjNnhmj22avHKfqWlkadosBUfc9DBIinHUwp4vTzg+XqiBj6v39O0YZlWTHHXwaKcPfZE8gtCt3S2Yg47YejsnWtcRc2eIJi7hqtunD7DhXSAIHV9DxtiWiO224yNN9BaLUsVbNrfCNMO9coFM8WqhbmjvzqeKAWocjWmaQVlTENSR6mxqu+fSSeTbYUVPLtZROcxeY9z6QmCGFn4YdPuui+01kT2gIy6vfZb0kBznWrGUoThRK+9F4eH4PAxe9wy4foAZdykwNgTUEMnAWsMPJpCMQDFQsvMmb9RxyJDaet/COBfBfBnAP6+MeZPW2v/cPix/yeAf6m1djXG/G8A/B8A/E9/8Y0t8Ju5fy30w1sBXtVSTgK94YYInSA1RQ1uBbhmGiwCaABpdgaLI86n/HwF/fxzCkjNwO2EiX6qDqdIGWiYSKlPoIyaPOGJoMXrQoafN4SnK/zTBWYpveHowTuM0zZ1e8ko35+wff8B6/NDp/9x80dMNA7OQuiMDP26GbQ7l3n5ubwHbkpJMD6WpQCYarghJg+fHey2afOWGrmVHzj+xfs/mbxLkr0W7RZQt7MOI0mFMT29Ynq86GQrWdNRqS866bVa7NnjdY/4cY94SfS7gVkKZ59hHdS0Vx4y7wrpufuuzy36I70SaJhsQZws5rIe8N4mQVuHVqgaWrdJR+XRjAbuUfsDIMw1FfZHraTOVxuJRP24T/hhi/hpd7jm3uR9ChUfQ8ZjSPCWOOef94jv14C/Wi2+7KQS+hgCbsXi7BO+e/rSZwR8wXK6wtqK8+misJWsg9qoV7CnoAFtmTc8Pr4eFA2J+kgJgDQ5JXlYHi9veOWNK9b1ckJ7ZfzXZ/iQSE+fm6WtDQ5JgG6gxjTEiXnoDDWO0gxCSzy6XXU6sH6vGThXdLgt16xVkLnTOJf7PP7u+D1JEkw7/jyJqx2fF2/NAeJDtTzxSdXCzhk4uXFZbCyRLNRTMr7weM1kgCGToqmKsQVNoOYmfUODxXentfc6a3/TGfq/DOC/aK39l3Ti5j8B8K8D0IDeWvu/Dj//fwfwb/yhF3WGFr0ciolXwqEkoBO+SoI3csIV4k7S+bxAp/lcEvDT3pAqldKfosWfnIA/Cht+d3rFw7TB2YqH5YoPH57x+N1nxA80oNOyJQbKFpSK15rBVDayPHu4wf6xAT79mrJYyVz3nTJXyV4BmCmRfK6rKnF7P8oPMEa5RmzrjH2bdKLP2qoTfSKC1K+5eTvRx/9sWIQIfJ3qZUG6zbA/9KlAP++IH14RfvMM850DHh8oG99W4PWG9tJQL1H1ZsQ6zz5ssB831aZpGzVQy2VG3SNaJb9JVIvCJXA1jTY/DjzRFcy2IjlpGI1wmqFrY9AzKscwyDBifj8nUItDKQHbOiHthMVKgKFrcseoMo3of3F/o9fifYFzWd9HOPUi5pVLb6L+1+50X6SxemWo4FocrtlrlfYxFpx91cxttgm/O93wx4/PmOeVMlXTNGMlymq3zsvZI+0Bt3XGzpIFBg1TTLB2JWiFM/HWDPZ1wnZ7UBiqNIvoMs6nKx4eX3H6+AK/rB3KGwLs/fUChqa9NNb5GofQA7rIGo99H+MAeGqDj5tITWT4nbZIFZeI2vHA1DJvb/ohI9NIvt72SJtzDkiFJJGpCidDjDnuXSSOpQK+rLNq1jvTsPiED/OKx2qxzKtquZC0wISXbcLK/riaSIDgQjKPBtYy4fPu8I8uFn+xJry0DRYWs/F4cB5nb/AQugnPXoAftoY/v1ZsVSpWg2AMorUIlr7+2iz9awL6nwD4x8PXfwbgX/mFn/9fAfi/vPcPxpi/A+DvAMCje8RzGvkqjelkQNSLJfhqw2RlRLrvWMXIyC5tAEoPssCMLnYzuTFgsGIeL4LbdYFzBSV5HYYAqMScHy+YHy8AADftiJ9eYM8JiHPHkRPBFu2nhvK8oG5Crucm0URNTcPqiTIpmq4T6h6UHrneFlwvJ7xyed2aIT3ombLNxbQ+YdfIyDpxmSsj1/K5xyDmXEE8rZifXhEer3DnVW3uzFJhHj1wmmlzAujPOcKUDRY7WukNs7e67HytWOhLvD0JUrIwvBnWgZmxpYDXFPGcPJ4TaW7MruLsiJIWTVNIxhkqn60tSqlrjc2Nsz+o8wlEsaeIPXnVDy/VEuuFM2uBRsQzdLSaIwZF970cISRrC5aZMGY5pCmpBg38OdZtgrMLyb1WiwKwSBwxfGSQCgBml/Gr5YbzvCKK1ritAMvQipKl9iZsI33zedPmZatGDaJFLnfMVEu1JMMwHZkb+xaBz49wrye+j1QJuSkdgrGe7131aIuDC/kgTSzURheyShh3hdFB1lkarltgCM2h5qoVFUAwWuLm6NiIlaqNcPeGWg2muOMJeBP4RWc9FY+t9L7K47ziabkdsv73pLRDdQTNuoIl7NiHhvO9IBi5QVXG1iO+mzzWGjRejZm2QC4PAfg0UfD2xnamFye4YlH3tcffaFPUGPNvAPiXAPwP3vv31trfBfB3AeB38Y/afOfC3TXQ+U9QuVKrXADaqUoD9mKxVYJSUu273WtueM4Zn9sNm9lhYfHH+xMMPH49UUN04gdVzROKIw9FbjpaR8FDxKgAwAYynWg3B/PTClw32i22ivpikT8/IgstEFALN8sDSNpALY4wbkAhF3m/+XzFpztmDWHsx4GOws47ZFkWVIgpTqxZ/XCFn7ee0QummDzq81k/nwkZ9mWHnS5AfOX3hEJI5pODkR10y2ivBuV5Qr2RANYR4hnplsxyuU24vJ6xbjNKsVhTJH9GZhYY00jLAzJwYeGrJVU8GEyVGCgCk2jWyOwWK03SJtoxlMlP8Sg1K0JPEoCJbkiWaJcUccsBpRo4S1na47TiYXKYeDhHXofW6BGiEOefkZq5poCN4ZnSOs+jNAPTmHvNhzW80TBVr3GFQpOvNLovblAAdHLUh/yzNL3j9aCfVR0b2QD+P+29a6xt25YW9LXe+3jNuV777HNO3cMtqLrE0oTERKCiVdGoUQQkBv5oKDShVAyJhsTHD0OFxCi/RI0RghEIYJQoII9opYKpKPDPpKSIWlQBJRcK69bl1r3nsfdaa845Hv3lj9ZaH33MtfY5+xaXu0/tWj1ZWWvNNdeY49FHG61/7WvfJw+KeeoKK6YRb93eJlC/KoemaEvtR4XPyvHrfJXVIhE7YSUfmKJoVytD03kYIwYvSgt0ym9PaJZJNJFY2dRPHWjsgaVFLK5kDCdZGws77Ryq4Z/5nBoTYcg+OD+qAxOxrsxs5hWlNhZqc5Ixrcxx6U1QcS8RctPGpZzrpjnWpprlFmRIhWUutAB6PnyGFGFRsPa6/+Z1xusE9K8C+OXV798pr20GEf06AL8HwD+Tc57P/34+DGXsK7VFNX5eIstM6v6XQinYxYbreatziE980Gqu+l5P+KJp0FmHRooMe5fwvDvh/WHEvp1L4cSK6zmZtAkStvUswrUXES7JTqmNrHmiuI8ILpjrhPb6Dm0SWmAC0tEgfHKJ6ZMrblEPbMZsTIRtxbpLOvEUhrFnWin1MvcxyIAM8481yDSNOMrITVQmemzKZ6gYWPHgjCMQZ5BnD07W1+AbtNZOz6EtOi8FA00EFeqqDbJTNPBTWwIF09sSXLTohba4JINGbjRrMnqzGlxon0BrYylwAVWGWGmA5LMA+3Be5tJ/UL+HllyYJ8WEmliBs29WA2Zr2dg4SnOSNprotmpOc/me2BR55zwMVu9aZVrVnPXavGJzf4jMwmATulC59kjgj8Fi8n3JXK3lonF3MRdoSgv9rp9h+4VXUWaVbMjebfR61nmV2Lf2IBICFfadqutMUkBtuoTakLseuu3iIwBsuo0RLEzycCbDzKGIv5kTNwc54cPrHA/RYl7a4uakxd+m8Wgbv5H9XeYO98cOHx0u8cm0w0n2paGMQTRbVK/fVDUTbq7i3g8k8eV1bCBSKLiJFUVPSwegBSKQRT9qhYRXE5/WsGyDmj5r4/icCKM2EVWsmBpLZ3mBR0/vg/E6Af2vAPgeIvoSOJD/AIB/tX4DEf1qAH8YwG/MOX/jdT7YELBT5khmJoohAjcL8bKDyf5sAqDNJ6qH3hs2WB2camFgIxFwClSyegC4dEw50g5AYxIvq9sF3TAxPl4tMZN3WF5elOq8NuLYmxPQGYYpACBE4LQgHzPyLO5CmTjwJYLrF2m0eFjuSNGw7ZRmZHKj1th4bfhc30iFWieGwIVDrNK3mRgW0SxKi7yyH072zQ4z7DszC86DmFf+9T1OX3uO+09uMI09483DtBEEo4Ynu0JI8dQVzXZdenc9y7sq1a0be2Rwt+gxNPBGWQdZHrLnDBnNugRWsrzC8d5hmTruCBUD6phMadH20cFrKzqx+xEJlKOJQBD9FRXc4oQgy3LZoW/NWnzFSi3Vzzkf+uDQ0etnU66s6eS6V8G7tRHXPWuHayFzwwyyCUweg2Td1YM+ZeTE91DbLWh6rvM0u3mlBUqAjHPDzW2ZxbHIRth+hrtc5ZJrSDB51pbnHd4WLcv1Eerl5oGg/RC7GbZfJTL4YCRcZVq9AIItmX+Olk3WwatT13r0aWSTbJlDwXNHsEJrej41eVDsHWCoydmI5/sDLvupPESNFL5rlosWYPcXDLM2IpOtGL8Rllm9yrOCz+dMsGSl+GmxsxGDS9hV50UD8iQdwjqUDEIA2oqi+M0wW+rxmQE95xyI6HcB+FHwzP7jOeefIqLfC+DHc84/DOA/B3AB4M9w5yZ+Nuf8mz9tu4QV1yZiZpwV1/dIhJwFUyftsErYyRI7ZgKJGJKntcXWEtBY1dFeaT8XjhkU2kKsF7Z09kn3pep8m5azGTMsoHZlVsBlkAMwJ8Cf+PwsQD5axMOAcOxF+0QKdw3rvxRP0Uyrt+XS8A1TZ3tS6Kw5xGREMY73GAAH+XnqHuh29MOE/fU9ht0dmqsjqAnI3iEcdpjnBvNxwDx1SMmiFSzY7iaYMDO0YgzoMqExd7i6OuJi/PrqVwqsGfjcAjMKXs7BQizdtEAY1wYUHUaoYq3lLB3ApvM3ZCoFx8YkuMjOOnqMJq58Z+sieswrvSwZ+NAUqqMzq5jTEiyW5BDiahPnSsGMtTt0vjQ2FMeiultxWbjgNi3tRiddJQTqB5JylpfoMMU1u1SVT3ViUmomydJehc2KlOzCHZ7anAVwo5F17BDEhdBtoXw5DliOmlkbzFOP8cQdpZvazHDC5eUB+5s7NMO86vXoSrWJMEpbPHe9quemNHbV7lumCax31PmNimceG8RjXx4YG7G7s+w/BlvqRMpz14DKMsEz2jOnsfPhLHP2a3hGjyOltRu1Xg1PI6uaFpZLOVahc8rcCEuDNBIyZF5I5j6JoQmTOfJmbmuj0JRXmjWwwi8qxGWIiQKddM6fdxN/6jG/zptyzn8BwF84e+0/qn7+da/1adWIGbj1a+DKQFEvaw03/nDlOWHvIi5dwOACHKWixaLbAdYOsdp+igDRS0i4ame8d3GP5zcvMOzG0vnZdAtnqtqObLIUbjSdAmDAZsOzQ/asjFg7CymWbIcFdpAmGvHwDHNf4IisAT1YKSSZgg0rg0GLnHxetxnBpnlD6Gj94MtrzoUi8RumdkMXi8ExU0ZawsvElGW1ebnIMTcFYjIDB/2cDNKJG6Lmuz2W08Aqilgn2jkkFJMp+OpatHTF11U9IDWg1xx0gANub31p8ijno+bvy3nQ341JaJzfQBh6DmO90skkgZ7NE0IyYu6bYInhEmsDi7OZyHK1mXCce9xOPY6hQcrETSXNgstuQuc8WlnhqRWeUtyC1AyyQC5Ke8wAOhsRkkVrA3bDiDxMIJuLMmQM3NSkDzonXG/nIly3lOsYgyusrODdxpzCuYCb61s8q7F7gRrnww7LSeaAdNsWeqJ2cpszga2q/wLQoudD+m0eW0BMktLMHgDzLc8fr/fQ2dC5sswtSxvMPU5LV6QT1GtWqaXl2ks9oaYz6vyw4j5UYNrgMM49TnOHMTTwycJSwr5ZcDWccLE/YtifNjBdLbMMQLRzRJu+4qIvadWVURE2xsSVtsg0a83KO8NU69Zu+2i0dLUkErXF10vZPxd66CvfnNksrWEMlQSW4SVMKHiXy6bc+ADQRhZ/YpMMIwR+IwavfGJuWi5WuiZwRkKZeeX7kTs/93wjAbLkDULfWraniJpYhLmANWutMeecCMHvsNxe4PTyEuNp2Dz1uU07ld/r7CPGbcZe47apmG0ktE1YPShle4qvhrktOKJuS/9WOL7JlGyKKnwzJ0Ja2mJXVy//lZK2Ewldplt28FOH6cQ0Ot0+N+zYTSD10WH0LZtERG6yAaRTDrLMLi5FWSAQuRGTQU5MxQtLg3nuighXjWOr9kf98FM+e61vzUWt7c2oDSeqOeMaL4023IC2G0a8H1Ypg/rBWncopkSw0tmpMq81zKJNVAA/2EpDjE2lR0HlDVy3YBePGwguBFabDLeX64pF98NFtP28FvqEQtjsJi6SUwIyF1zjzOJctVKi1YC+m7ZSE0A1v7eBOFeK0WQSwyxNXGtOAMy8MDYvDCV+OCeGsJYGy9LCB1dWpzGa0kzUOV/mlcIak1z38rnIBTLTc1tWawKv1CsoQkYvQlyqTdM5j66bWXq5yHIwzON9A19dezasboo3qzUZJrM/KV93plGrWY/qm/eW0JhVbCuD+xFOaqMgmTsnORrkXx9/eXPiXOAmImB9Ki2GMBE3BGmQzxloBWttTOQmjZQQyIjLdr1NLqweArsZTVG9+SzufIPTzBzluDSMISrGZRIH83bFJlHU8GXjRoo5LdZGogTkOSIfLcLdBfxhQJImGsUdu4sT2t20CYwPCnoCs2jrf90AY2XZGM9uotpvk2FUW36vG2/0vU2fNsVV0wQ0uxnu4gR7MYI6wVxni3jsARp4P5RdIUYeupIhG5GjZcElyWRbz5zuGBzmqcM4MX/Xi8NPiAZT5OWpr7jOAZB9x4NEpM6KAGEkRYdp7nCce0y+3dDG2P5tXbEBSmdNG1aIZno3w4lXAZLdWWGG5EyYp27F3OVzFf7hbawZoj5YAcDFsOmIZYy1ZgRVWLsNGBwXYTWQK8xWvGKrB1GYOuDUs0OU6LYYk9G1M4bdCbube7SXp8+UrS37Unc215ISFbxS7Aht2mbowMp0qqYnvw8MT66YA79OVdOOASwSsovozIwm+jLvvXfAzOJpIdmqEL2qWdbdvQYZIa0PcICveSddn3pddQ6lxE1FEetqX7tCpxlwwZ0lW1RWwZzwOVi5tmwebkRhkTbOaQwt88/n9EMO3ARnV7o17wcKPKNSu6873lhAdwS8250VwcAHk+WAYBh7ykC5YfWCzslijBZ33uIY7EY33RKwb9hZ3RCrOPpk8Mm4g/noXezuLqWzbsLl1T12d9ubAMAGE9RxvtQEmAEQxxZh5GakKBPB2AjbebSXLG9rbNyIcxX6l/DQl9MAL8vMae5K9V6bXBQLBFAYDIwLpkdhj5QsIBOcHJsIKLdYucJGlO/gcrnxqIuwboTdT2jDnRRXSQw4WvY8nVrWr5HMPyx83LHCuFVoqml8oQwungN8YzrQwv6LANBQQu8COhvQutVurhcFQYVVcmZZBtd4XOxP6Nplw1FWTnidoevNHyQw1Hh2Ai+/tb1cX6dlm+kpl30UrFTNFlSBcRDdbtXP9tEydVEobXqD+8TdhWrQkADsLGeOF8vI3H0SiVqZFyqfXGfRrltwc3HCc6HW8usRphEqqjQlacExTi3C1CEsDcNtskp1FyPcxQlmWDgAJwiDyHCoTCQQJGfa1AJoBOAFHufSxbWrON1XRujRIIrBejG/kGvh0lLqRwpBjseB/U6Dw2HpEKpaTn2teDcIS169PWv/2t4GuT5sfAGgrIiciejs2mugWPu5U5EGbYVe9DVDGX3DDUsq6tWK/+kSDbwjUNTTQkU98ZyGmOVrjpuPZUzdAq8LtwBvVG1xpS1mrFoHSuHRJUrKBp1x6GxEG+WGkQsXZAkGrAUGa4G+4uZayrhoIq4aj6HxaPWJrU0ECrtcH9hvU3Duorly1jBRHFc0O7NRboxxq9ioQXtm6VrFf6O066saYcF6BSfvuwVtpboXIjvLjFO/3gCVyUGjzSjYZlv1yEIjTNGARPe8mD67UJxk9HhMK3Z6DVMZCUBWFsRZRheFcbJMHZal3QRMAA+gjtPSsVJdcBil0KfFamcSgFgy5ZpjnUqGpvCRKeJqNV5ePlvPSc4ivBQ2npJ6k6oWi37UY3zzOTi8XDp8NPX4cHa497xnvcm4bhPe7Tyu22VjrKDbsiaDckLOTGubM2GKBlMypXnkomE9dH5g8cM6G4GhKCMJS6oYrfQLM676GaZlql889vD3e0wf3sBX3PK1QL3WY5zUjtrbuSh5kkkwrUdzdYK9OYGuDdCLQ4rrAOuAtkV2DRfPgweOB+DFAekFr+pytEUOQmtIWlRXppWf2iKVW486GVG4TjV2VF9cR13e0mNU1UsvNQqAhbJO0aLxLUNa8jmdidg1C3btwoqrMv/ULPucRlow+rNVVmMDGhuQpTibMqGZBozeAWixCCWR/7YyWmICto8MlPf8/Y43FtAJ3CEIrM0fMRNOAbj3GSGzka4hws4aXEVblBJ9YmPWY7B4uRgcRfuFffu4kLqzCa3oqg8u4KqdMTgvlMVq2YezbDwyp1r9PNX3s3DTrybYq4mVCwGGXQKAhZAjrRlONIinDqcXV5iOOwTv1szCrvh52QeXHkyaZWkRjjuMS4fbccAseN3gPK7CiBtibXBy8WyybadLShZhaRHv10zPuli8VJv9WLL/5MVP9KXckBokreiBiN0eXzeDOLZwrUfTBDRzhXXGLYbO0JAUR6VrUzP0KAXEO9+y6iL4QXDZLHg2MJtoL1KtiiH7IGYO4iq0BIeTb3G/dDj4pmzbSfF1ZwMr6al9XF49QrVoCaxCYYrl6/xsKOF5N+NCKHSAmi0E7JznuSXz+bB0+Hjq8dHU4ZPFYorrSvHScev/VTuL+UrC8/6EoZ3LKo6btthj1sk5h9RJill3MAhiQajDdh77916i1sg3TSgPaEghNAcOtGluOWkRqqDtPDNTenAw76qOaBWpC55/DkG+UCwUFXcmSjBdBMyy1poCK4GaJjANUCmu0ZZCbu0pOs0dDtOAw8LNX+fSxI2JjFuLMmujXZvJlMYtooyGcuk6tiYLA8ngo2kHf7ooBWprEi6dx7N+xLPdEbthLCuuupi+FkVtWfUtiqtng9PSYknKd+eHfvGgzUAds1ccfcXZAU5OG+Grc+v/mcbLC7xyvFH53OmMm93bjOcdcNWQaDEAO8sZtqEsT17O7F4uDb4xOXztlPHCe0Rk9MbindbieWdhOi22ZiH5WwkkFjYK/z3JxPb8ncQSjmyC20+wnd/giNrhlr0BDnKzR0KeG3adH9vSuJOFV97tJjTdUgXGyspL3V+yZjV2Q/1z3YJOBJe+w3PBiKQJwrXSRNGG0s3Kq4BVhAqQSd0G9GpkXXW+qg+q2S2gXo5nIsRDh3C3x3K/Kilma9euV2BTSC0WeIIvcoaqBVXz4IYoFD0pAPU24lk3473dAVfDqcjtqpN9wbErVcBWWrTLDZbYgu15aIpdGIBVsjatn6udfrVpgnashmQw5W3zD99wVBU4+bK1lkrGBz5Smcce73Tc6v+ssyVjBNbMf5L9izZiihLI9H2Ko4ukgu2W0lkZxxb+tMd4e7FJFPphwu6dW/Tv3sLdHECd4NgtWDK5b4FmQFHPzIm/pwlShOGeiikgz0D+ewsQ1PQ3c3HTyfbOBNsA8IouVSvcM145qnPtemaW6Yo1RQO/cNHRe4YstKv4bmlxEJcxQIIb1eqcKAwiZQ7Vq6vHiI1qNdfLKl3rK50N6KWLPAlTpS6qG5NKncR7hzT1RRdmSU4s8hocg8OUuJNd2S6anWsrv8791rA0yc5mOLMiDaWvRlZxrzveWEAHznZeDpI7RvlndcOeokFnLGdPktkZ8El43hMG1yBmPjk7x68bsKIZKu1iFoYKfBEN05m0UJOTKdraALh40wR+tmqgnBuEw1Amaz02hSKSVuZ+2UA0RaGwnuyy7Rws/NwWFgCwLv+11blsXjB0GHGZ0fer+l0t2kXMOrCtZ1s56RYs3Xoi4oSpui7DgrbzaJ7fFfZOWhziqX+g4568RfAMHzFfeKtvonzfnImFk9JKd1TN+tYwZeyyH3F9dYdOVgCFB15tVxk9TZM2cgjrqsBsYBg9X+cjSsfh5NuSYb1qhGRxWFq8nHvcB4cxsAaNEUx8ThYuNiWQRGFh9ZZZWfpQOUWLu6XBfbA4Bn44KOx4059YflZMkxUyyt4hBLN5kHbPb9F/4WNmkqgCZZN41Tg0QL9jiESDdgjA4lkumU8+K4O2koWrjk8IQHsCnSbJ5KuTYMCyEAlQi8Pylbedwvz+DMK20/exUTfzGZNWY/ETcJBaEjPXTIFMGqOrqBVD9/mhWJ8eKu9+DcMCNtiyHaU49zbi0i+48A26eaU6Kt7uKkaZYum9xBLF2A9LJ7RqtUBcM+8VE1/3TQkhCYQgb2RyB9Mc58hF0deti75RC7opVswPuQidzaXmQsRNmXzAXESzlMvTurUJ+2zgSLtCWYIyZOAUDUzkbcyJl1lTtLifu6KHfjWMeC8ZLhhKoNsMDQRSqDJNgLscC0/71QfHRcRw6hGOA+LcrjhmlUHXDwXF2NcMV15PK2RRdosyXA6SVYYNdGRoZbPUQYBM4ofIrNul0tzEWdLaoq3UNSNaHFmLVYmYuz/ksr+BusLM0ewqJYZF5tBgFrd5gG3YfLLobMCzLhd/z32z4GY4YeinYmNXTqUUVL13hV5Ww1WaMcVosPgGszQA6dLdmIRGaGt1K78PQqFcWileGgkWzIzo3MqB7+DRC/yz4rqq5hfROsZSV9qiYSu74ApDIyZCL5rmUZbnPqEEFQBFdrhcS7EeJLcaMZjOw+wnmH0CBgMIHReBe8fzrQc+DNsHlBQ7yzAJ1HhQP/EN5mwFrRB3QV8YVs3kA+Jgn7Jk9fydkICQH6rJmAxjwvmrBV/fSDck9s1dlhbT1MMH1iaaQ4MMgjNMW24ob3DnLEG8Ho3R2FHVXs7wdoBruq0UtJsKH2+E5si9A6vGea2HXw7xjIjQ2MjQjWxj07r/ihpy4NOHWSR1vURtkn10hqTB6BdBhq6YIrAWDJAIPq9kfAMg5IwlEiwZad3X9zM7szcZDa0dh7oM084/YLU42zvPJrDOF/F/YxKiBF8NsBq81eW+LIGbAOoiY4wVF6lohc/MItCiajgMmA87+LF70OlnXICtDC3qgF6G0BlVTY89RqVC3y1opAFEKWm636YNZb8LBU0xzrS25tt+ge3PTDWEiZOjgRePTkDMo/sF7c1hhZ6iQTz2aO4uYO/2cKehNEZ534AmvlkW0eJwhuBykgAXEDNbDQ7Oi4/jljuvmiWz8JRXSibTx5g2pvBZ1aSl0A74/M0ivqV89fPhDCsv6rk/+QYH+TwddXenLsNbC+zcjMvdEcNuLFDRPHU4nna4P+2Fq2zL/nCznOL4wOAY4zeUudYhqx/XerhhQvvOPczVDNrTGhkAwIjzlAbYMSAdDeLtjl2rJKs3TYAZZl6ZdXFlM+mdHzKQJPg6C7SOM/YaP18W/pxlAeZQMvMs2brZLaBOCvni3hXHFuHUFxpvbZBR1BZlVWVsYockk4rNXTsvFf0wr/UWgUJCMptMvKRG1YotZnUOMpgTFdcg1VNpjEND3IlJxJr8V3lhmqvxaGR1oWqLGqwBXrWNS4uTbzH6BrPUciYp+M/SENRWVMRMD3F0B4AsEA3bCgJ8iVqxq/s0Ma/HxhtludyomhsYVjkGi8kzy0U7qZKlooOu2Qw3EVU0JoFsNIjbcuPxyektF68u2gVX/am0TA/9hGE/ot2PDEdUjRTsLtRuukGNUsIk2ANYGTFzw8XESovC9jP2F2PhA5dM94wBsBH4r/jZOVHJfim4ottR/DQfgRL0hBS5AZPZNclkFhAqAkkRdi8F3ktaGQ2LR75PSPctosBLevxEUlCrmqm4jXsLQWnDjTrX9MkWGGYODTNdMmPLRIBLCSlzZr9pB88rjQ1A6SRWR5mm8cXFKGdCnyZc5PX/gNVUeWVOUIF/jnOPu9ItyA+X1kQMTcBFs6CVDsMoGfcYuCFKOfQsGZDRSkPKg1WUyBxoYHLilnUZrdAZCZ2JeL474r2bF7h65wWGZ3eF72+vTrDPPPBsx+5RAMs1n0bg3iONBlCjdWNATULzhSMad0SZ/K0DdgMw3CC3HWAsY+OnI3B/Dxw98hEC0yUgLUDyyKHG4CQIu4xauDB7TmJ47rtSsFX9kygrwPIgVjJCVRdR+WgAqylHTnCBXYKcSXCUik5c6xIuKZUHbNnF6kGr7KhzEwrF2nW1PkU2c54TZ8LRaOE1iRl4XBu0aJXsBThJpJY1YXbNUtQWj74FTT3HM2yzckYQtrcqS0Gs0IuOOvD/ovAUNcTsE2CFW5ZkQESCpaOIwNcFvpoWlrAWDnhD20Cv/wMAeycypQWLZczOdQva6yPa57egLjIWODnEw4B4YnqhZhg100MDnDZumM6z1jgq1sw5NKOZ8twgTgY5VsFcZEOLHKnuv9DYjF2qm4AQ5hbTcShuQKq82PRCRZNCWk23NE0A9XLTtAE0CKPBWf6SC0Nthn2WYMM9vxYy8j3gP77E/MkVprv9BhrQRqZux33eylwAOqEWroVRXRk1doUFusajddyVCaAK5iQB2ZZO2Zwz4JvCMqgbSc6veZlrUtBSeCpnwpAnXO6OeL/C3Gs+cr3EZvaELTrrW011Pp+nccA8dwBW2p01CYMYaORMOC0dxkA4ei6cxUwYHLNvFqHMadLAjCuDfCKQOQEnCbBlOcu4OQRxoYGAix64vEK6uAIc/4FphhbZNcjmrO6TYimIUoqgZQId7kD3t8DhxJpFgBpfMryj8yQl0P2EdOuRiz7LasuosF2tn5Kj2WinA0CYW5wOO9zdX+IwDcU+Tzt4p+CwJIsgD/PGpNKQpOqIhtSuMBflRL1uMRvskkfpY5GC+BJteTjHrCungMt2xmU3Mb9cVm1cTDdVE9taLG0sP7R1WEqYgsPBNugMU1Nr/Pvc6pBohYoVWciZ80gtwH9LLej+QY2UCQeZxAAXQy1lXDYJvSVRW5RsT3Dw6FfmguoNL2nVQ7fET05erqyGGK3ljj2n9CW9YQuhlQMqXC5gmwpradZumsCMkKsZdGlWtUVggyvq93xMiJ/08B9dYbnfsVUcsJUhrfDyraiVZAouou3YLqzpltIwAsrMHVfWSnUcn37SCTlJsSZa0Nwg3lbnQaAl2MRt29LxBzBF0e4m9CahvTqUbCzOjdjNdVy81OBoUjE0VlwyZ9arcSZumnycWYu+ir/ruQAgrfUGJvPDXNuu4zRs2DNqzKxNHoBkWy6y1opk3MC6ijCGM31NFLz4cioOn7Ba09W+lbrtos9ddeEWLF2ZF/K9sQHXfcZeMrqiB9OPaBuP6BvMLy9LV66bG7h0hMUM6uVaWOLC524ANS3gHLIR31rnOAPniQakBHM6gJaZnagWLYoaoGmZV952JfgjCeQ17Pjvyn4xRrZt+KEgTBkaRpj9Ee3lLZrpllecCRvWV/IOtQzzue2iwo9dP+N6bjc1k9oiUF2C6nb+2iJQ9clDsvChKUG07irVoM59EI0UuBnCYUZdwnVwGH2DXbMUKYEyX6oCry1NfakUSwFgiQ6NUmyBQreshzmrOmSsTBb1e4hJa39v0ODimxlcFF0z0ZhRKtX6ZNIlEr+fykVSjz7VG67VFnu7duY1xC3e/DexoupmDP1cbuRSLJxakHRuAuBJKJk3IIXFJjBtq214gpMBojIIAuAzMwMCIby4wPjzz/Hi59/F7d0VJt9yK7gLGHp2VW9b5hlrhqu+j3Vxtn7wqNyuFdjH7SYW0HIV/PJIKzaCYPOiT8Mbo9IFqjg7BAdk1o0pQbvebumkpYQMDuDLccB03GGZu6JFowVLbbcGxJle7LxOS1t0MDoXcBE5AzWGhbEg114DKZrVsFgpZNamDec+SqORMg4+bWhHoMoQK1XNyHcAJeiGOsOqttvYhM559GlBbkLZb9XJPlbiTwC2WWQVnDrHNoPd/oTu5p6LoE1gPZXLhROInrN/OLc2+bTdGlyXGbh9AZwm0CgMlST02Um1z8WowSRQM4Ga4+bBTS2AwQGXF8gXl7x9AJQSEDwoeP6cJOwZv3AxVuYbpcxgdhO4KUrkIXS+lfpMIqiOPic4ptQdfJXk8dzgLlydU7XYltIIFVbzodlIM5SHsPqqyuosBIfJt5iWtljK6fVw9nH1TJ2POupVIL+eC5wHiD4RoTC5gIewCs8xSMzbMmLq37+Jzv832FhUHaxKp5Dg5hqgc1ZTAFmGYNU9B1btA/2dAzoXW7nowfTFhlTsSdrlK2Es5YOTyOdq9kCdMkeq+rjJyCcCphkw0lzjVRb0qghaAQw7wCRcv/cJLt+5rbJucXJxofC660Jk0glfdUZmKejlULWoe4GHggWZtQsSCh0ovQwAXIbR9n7NjgxW+prDtgA2BeQxI4+rvns89lhuLzDd7TGP/briEMhCA6kOLVg6t81cnQvopbOujdIhKiwUAPJAUG1ryVrkupUALzeoyh/oaLAGfR2lSHpWcDZWzBlEnOzhDVptQzTYVURK5QXKtqr/rW9+XU3E8lA0MJmzuSLxDDbXWOYW83G37lu3AIlAfeBAyRghs02mGXhxC/LrilITiewNclqNw8lFmMED1wvfL4bfWySfP+yR1GWr9WhuDnDvvgQ9m0Ab3H4CxoA8YZVULlIBlVF5NbiLVVWn1AgllWSBEsEmgm25c7ULrlxPnVdsF2kKw8lHgNA8gNX0PfXDnAQSyTnCGDadzrmqqUDuKymgwoDrTJmQUsJjo3SmIyNQxkJuo6t0WtoC5dTjVemFJYYta7x8o7qIR2i3n7xiY3jDGfohbA86Y5sB6YhZtLIVmsbqkt00204qa/Kmcs2r1CxLLVda6IkyouB10Ts0x2Fjl6XYc52ZkrTIk6mEk/Rz91PB0AFsM98KVimZSjabrMUfmREzHofC6HAusgFHP7NUarNS4+LcIKc96LCD6lFr2z5ZhmMAvqGpi7xc2bWc6ZGcrRiEkhbXJhIAcATaAzQkILONirmYYS9GtNcHxktVHnhqsZwGTGOPRZbM5RwIhLE5TyZhP7ChQz1KNiS0Rz7nW7rY6h5kihRv3bmndMEQtzj3RrNc4RJlLkhDVN3FawRCMRU00LYL2nbBDkeonWBKXCuYp64IZWUJKq0LuKSMnRgca4fsLEt6ZUX0Qne82J1gG+5G1gd9ihbho0vQSylwI4OMAbkMWCeuP3LDtxl0DdCu2XLLnUNuO8bQK2iFlhnN6YhmnoBw4BsyRLYaPAH5Zz2yP5YCu7K76LqiM/IFW1kyAOCzsG0GtmScusJkKSwshQoleW/3I8gktO2yYXVFkQioVUK1gUy7NOuHb92yr4Ywk2/hI5uSnIt5lXklc0I7UJXVojCcagEpfAMAWYqvjtY4wHN1W6C1tOLgpnrf5p7QXCzTo5H/s1ab9XiD4lwZ7wqcwVZgws9VyAWclU+RC5ljhLBfMghUeJpWvmsFeWcz2iZicBGd4Jo7G7GrMNTSLNB4NlC+uUdzdYSRzlAVokpzUxombOfRDAfYd0+g5z2wv1wzpvHETRtztdQdWd8iHAaEsd+IWZ0LLhVt9mFCf3nctPKXB4jJqFu6V+PdVH5XmiVDQ3KTGfCDY8wgPwP3QkRPVVYnkgWAPHRgCyTDG+dtm/0C+3yR5gADhIh8nxE+vsDy4hLz/b4soZelLa5CMbrNcvSca6/L4lq3GlhhG10uA9yYwVZ+W2EypTiey7BqZ23ddQpgI2GsIyWDee6wyHJc6ZYAShZ2XoA9t58rx/eIgfAYGtwtHe58g2Ng2t3OJszJoms8ht3IVNS4PlxrQ2VlTjllv1z3K73QOQ7YbYd8jqXzwTFcAiAbw0G+3yEbU96rkApNJ9A08QOfTxbDPPoZkIJqCIzNnyb2T6vUSclx8M7RgiytSZLJkswI/i0aL2FqMY1DZWZhNgylOut2LhTxt3rU55+/b70E6tWTBmd9XV2MOucFCl1huHHqcTfuMPoGo2jhNyZi3zAFetcyfJozF/1PvoGZV32qMs+lpkeZqoanNfGEPBBiZh30U+AGI59eH3Z5o7TFZ+1qPeqzVp4tlmREPZHQ2oxdzmJ8QZXAEsp35ZcaYlptrC7gmrlLE0i7FNpi2y5wqkDYBC4GEgEIfPKbUHQuTMt8XpbPdStEoYXQOSGfeMkLoHSdmiagMSNDMJJJbzju1Ti3+OIX60DLg5tN4topqNzi86UZwPh6i9U2r2n1w0DSQUghricxRl5Wz2Lkod2z0vnHT1BhxRgCtR6mX2B3MzqgSBjQuGrT1DZxi2DnWugCuCg6NAv6LpXmHz6eh8teFahqmsBc/mrVUr9n8ztlqGysQlzJO/iRBcV8VYwrgUOdhaqAfn6ODWW4qmCqQ7XWZ98gZYta1ndtsGRync+Eu6XF1+6uEaLFxYubYod2eX2P/fOX6N59CXs5Foln2hvg4gIYdivOvcyg4z3w4TdASxAAVh/aDA0msTckF0FDZG77ruOOUTIlm0+7C+R33l8fCmGBmUYJ9OMa6EPgrD6lkjjAZZh9AnVMiUXpXkahOMa5RVra0vo/n3qMpx37BgSF8rbaKcAWzsqvBDHW8cDrtdpW3asArFn1hlFnMoBUFD8vw1ia5Ai5UGedqDTmTGgtq4ayfpD2H2i9b+sdqnzzwRJ6KxBxNXVr6OV1iehvEEPnrjxAsPHS9roGZR2tBNq2CuKAQjTrNhsD7F0SASS2nDNgMSuVz7QVhl6G2f5MmbNf5LQ5Q9k7pNsMmg4gd9gWHgFQn5kWKFi17TPQE++Y04OLgPdcSF3kxqibk6SAVZoxjOhYV0XPYmptZT83bWmyL6b6XYNwPZoW+fIaeXeBtLtEdi2ysaCwrBS24EVGNYKmEXQ6Ascjr0ZGzzfpxHS0UtSVw7RNQPQN48Epicofc/9r6V/evC3NSCmZjcM9Fz7jpjElRQsPwATLzvAVXKL7cd7IYixxkU6gMD+3OB32uD9c4n4cCgWyyEO4gN7O29XCGQQEPMwKU2a+vfHyfyEjGgZnG8N2dzetLQXX3gU83x3whecf4eb9jzE8vy0Jhr0cYa4TcL0Dhnf4A5WH/vEBeTqUBCIFC0TDgTIOKNK3kuEXTXPDqpkYwQ/xcQKZCWouQI0BtQ0X/o2pJlI90WWF1vWc6ddMmeBhDneg21vgdkYaJZgHi40BuU5PG9FdnOBaj93lYe3NEB9ZbajL58E4bX9XITjN6AFWTzwtHe6nHiffYlbRLLPK6jZaYCXuG9Cgzg/4XLYXBbZRDSBrEihl2GSRJZiXOpnJ8hkZCQkpA4NdIeWV3MH7HqQAGgIVVhUgjL0tYvmZ440F9JANPlm68nudUfeiL5KAAsFYYrhlfUrz/9VMGFfw84c3XZB2Yjv18MHxU9UmDBBK4sUM6pmlku65y2253cOPPVI0aLoF/bu36L7wAvS8A66vi5QoXFNwyjqrqdkBygwgZQhMAXnOWFM2KoVMcpEDKeXye8FK9XiTFGTrzL0K9HnDdsnA7EHGAziVIE8XHQfqZUbqB+Ert8htj7i7Rm72yK4DpQBaTjDjS9jDS9B0Kkt4evECFjPS2BWN9yxCU0S5WKSdrz7YYECCUVzlUh9jqKgbExG7CdXYtrKAuAnLImXuiNVtGKEVGimk6sM7BQfrIi72B/TdVHB7dYgK0WKRbPE8G9PsTbNIlRmotbJVSsDZCCvdsQEGc3C49+2Gh24p4WoccDG1pahuwBltPiwgGlcdljkgHwjxbsedmHMD9cF1lyPcO0fOvB1JIZVWVpZV02dp89SWfgCFdrsE4H5GXmZuWjKywts7XuENu3XeK3TjGqBKCHKS1R8A0y08r71HGrUZT0TwDD/+LYKsvFaROT+1iGGVZVaxtXNqqI4CiVV1Er1ml8OIEOyDDL/+XR/U1qYHQlxJ/y6B9jHYjSqMXzXvp8iQy8P5vN7HOpNV3sQio2bzvaI2+8rxRsW5HhuGMsMMxIsqY/gepCxPTjlgpi4SsMJ2G5xJi6H6tIuZCxuqOx2jZZ2P2wuQjUjeMedcluNxaoFk2DjARtGgPoGeW+QPvojw7i/bNmrUP6cIe7oDvfwI9OJj4G4slMZ438K/uMT88gtYjj1SssyzbgOa3cQaKtJC/iq3mZorbihsqYpnGXpOkvmPzA1Oc1Nl/xnGesB8tPLbhwXu2gPPeuD6Bml3wRtyDQf6q+fI73yAbCzMMsL1XwGFr8CeJtipA3mLrFRMk2Ac1wv4tHAW7pcW89JsCqjAw5tV9coX30A7P614bw4XRww393D7EaS6+sKeyMFAjY1R1RjqlRjT+OzG3Fr3MesDRrW8M2uQxGBX39f0sBZQAkCSQpusqzVg5EwYBPttbUTMxIJQ3YShn1f9HO2gXmSlNqa1ON8amCHBPV/gdoZZS8ZwQO2vkfsdUts/aCJ6MIx58BIFD5qOoNNxC63U+PkZPk+nI0yVsBQ2Ttsi7/blc2gaYW5vQS8mkE1skiLZeooWRrunpXZTakfEgV6vRRAjlVoLXx+wdb1E54/3rDW/iI2jzq9i+0drLcVK74QmAToPvcg1j551gnIGnFBWuTazJUhECehqh6nxqtAQha2njD3lmsei5cI1QnZbW4umrzPeHIYOLlYCPO+XyI4jx2AxJVqpXmBKY8EwIVTGRJgFl9IT1Rp+4nETwNbE2Il0rlcRKnmikskizrWsnPPFiSaKh9P3d54x7yWCXn4ClyK0jZqCZ7qfskZSAg4j8i3gX+wRDu8heQtQZspi53Hx3V9j/Qu9r0K1HC24OYl2tUWaTQlqRjjxJgYOnjUEo5g56x+ADDGP2QG2TYCpikk1Blo3RnkgfzgBX/s6EL8BGLAY1GUH++wZ0tUz5LYHwsLb6FvYdyZ0zceF5pgWt5VDyJUwmTZWKd87WHa4X9rCagAqymEmACx+1IiRcSO4vbscN40qD+hzFUT1gJsvXbsKB5T5Vh6QmipVolJRoYOVJrqRbgCgbkOl67dixIynAadxwOhbxGTQNx7XF/fc9v/8lo1W2gCyGbTLbL213zPOLY09qR8E6hiQXVM6PiksoGWCOdwC5TUPTBMXL0NceXF9x5CJbIuP1zAb5uY5suPP45XYyBj66QDc3/I1rx8ItWZ6RIFkNEsvc80QqCPm1veqCSPXYVmhxpwNTPDsQTBM6CtvW50zr8P80O5UfTg/xqD7rBGWBikNuJ8HfHTa4+AbxMyiYXsXsG8W7JsFjaxEJ9/CJ4PGJFw3EZ1UP7XHhhshV0ndgwde+oQXYcEBIxJlNNnhhgbcNA43LaGVhdbrjDfKQ1enGj3RXpapdQWA/SBzIeoDQEIWMRsUi6+cCc6wwE4vnaFFh8FktIb5z/tuKp2BTcOsluU4ICeWLi02cXHNGHLmZb6/38F+w4sK4cuSPZvOM9e3lWAKAI0BfbFH8w/v4PphpYw9di6WmdutX94j3wJpbDhLMRmkXN4OBaIoHZ310i8RYPOapddL7r4D9hdc7Op3JcParjDWIi8HCGVHRFBKyMvMN/TxAPPxx2tw8BX+L8Fcs6xy/ioIhINlFIXB1UAggrPjZWk/tTEoRpYZXo49Y+LeoRgfB8NiUGHNroszk/Ya1Fn6WWBeL8jD4nLNNDJNLv8vJ7JSD+RjN43ZzKEUbWl6OS0d7uceS7TolgBC5kazYS7qinaYYdoZ5CzyxSW38wMghbruXoKWr68JxDwB9xPSrRH3IAl82iQW9w+L7tkAmOULsLsJzfN70He0yO+9v36mMF8wigRBiDKvWoZgdntkgewYhvsI+PmXiB91CPcDcrQwTYbpDMyeQEOGuZRVlbJXzxzC1PFLJaU1malrJGVFV3X/rnMM5bxTTDBmTYgeG+rBa2WuGBG3y9Hi4tkd3ps/LEYcAIrJihb5mS+vRj2m9M9o7wyz8DiG9UZRA0JngNYa7EOHKbZgpzZCbwl7x301nX2dEjCPN9wpugaUKF16O5vQGIFX5L5SGqM2HGllmsAFCAlPLOYFYJH2edU69slwg5FN0mhAQpMTj0EnZg+d56X12CIvDZaxYy2XzGYTQ+vRPr+Fe39iypi2RDcDZzr9wG3YGhwFV9wEy7AUtsCm6y4lYNex/6dywmNGPgHpKBTKWihLmS6Z8WOYjDwbkE/Io2ajUvw1HnAHGDXtVeEmZ/mmVHxV6W/KdGiHLU3t6h1QWEqQR/AwLz8BffgCeSTEU88Y8COSAHz8qxl2rVmjN6B1EfvmWOZEYSJUcgjGJPS7Ce1+YqelYSkdiTTzvtb2ZmFpkMauZGq1YYli6jUOn4IrTURKt9TuVesiGmFI8f6tcg3nVnjntm85UwkA1iTsmgWddMadlg4/+7UP8HM//4VSfB3aGc/f/RjPvvPr6H/FV2Hf+SpfnyUiHzPSsUU89YWFRaYDNVbYT6E0xr1qlIa5DSFAfr+dQcevwNbzRPHzZ8/XObHM/HW4Z2ixbMcA79/AfsHAlrlvAR+ByQJLLiwcaqVXYn+Aw6HUlNLI1o32bocgdSwApRmwKKAKlKXmMPWqSM3Kp6nHOPWFoeKE4dY2as69QoRWtqvXuF5ZTXOHJay697r6N5TRurAWxLEG7NqcQo05NDpbZIRs0NuVBKIBfSfBfOe4SfJ1xxsL6DETXi5t+V27tbgrdOucfe4pyFRFFbVfT4YqLNa2TUTA3sXCEXUmIjUEG5n3XHA1J7BFqpQRoy2UOyOFWjKZm3TadlNkohNjj+sBhtVYQLNZPnDkBUCATLzqQMvFP8OW2wDb18vXSuRLVe5QQTFtKDAEq0Y2iKcecWoR51UWVul8NSPEdh7u8iPY66/AXiZ2u6mz/KtnSLtLxHbg4O4aGOdg+hdor+8KTS4dW4S7HewxIEzdmsFmQnMGUZyr8On72IeSDT+0ace5wAJklyc0NwfYC5Y+0NNGukLRUygBIq98QVnmG6SxK4XF0v0IrKyQeg5WK40aWlFBtbobNQvezqYfQCXuynoxUZ3sCZ0LeP/mBd794Bu4+q6vwX3HYV3lhXW/860eDB+buV5grrXWgrVxbK/ZsmDZmmC4piQar2zlL7zyGZgzclDcPgCYGEYBABf4Ab8s/P5pYS5elESiMVyIbZs1UVCJjNEzLVbYOWQTB/WhosOmBLsEmP0d7MXIxeFoynVRY5Zz/4KNrAWAjbRFBfHlYFbPXxG3U5hM/X7npYeaq5zGAS8Ol7ibBhyFbstSIsxaat3a6bzEtYGpoYxYsnHxA8i00WZhqJgbJIvQGq2yAZrVv+54YwH9EAz+j2+ItgQBnSFcNMBlkzGULH0tfnLRYG39V3ejWP1uidCZWmoX4tuozJc164swiJGXdPNhAMBslxQN/NhjOg6YxwGTBMDxNGA69ZheXqD7/9igd6NB3nkOpMpMUblRYly71qGmgYDWMT0M4IB/WpCPqWTjpTBc0c1qF5vk3QPxo9x6OJnspgsl0NlnC0uqWlrBOAHx8gL2QxXmTBkJSLdCQjQA2QNod4DpPoLp282qAwDfiJdyw4cIu1tgBg83ngo2qnjzplsW6w0WF1l2V/AH2ZUFAWDlntu4Dboa+Mrv1V3wWIZjMqjzcAByt2zYQmuT1afg6vK+WhKZ/8lsIIPaUjAuDWeMY1+anxrncX1zi/76HvZiBHXE5GRhp6gAl4pwFWZJ2/EKsIbyHqFEcAMQM64K2wrYbqf6/xLkF2Fp5bQmJi/vAX+39rd1ArtcXgA9F2JLw5FfOOAv8plTQHpJ8B/dYH55iTDx57p+Rnt9RPPsHvbaAx2TtPOYke46+JcX8EeWcV71ih5CLqqCuhII8uY61nDLKl+9ldkA1hWAzWwKY12AawL2u9NGfnnxDQ7jDp+c9vh7hx1GaUKLeeueVLYL0ZVK2yKnKZ9bvSaEEIWZH+0vecV4YwG9NcAXd+vv3LLP1ENDa1au8pN1MAf4NUN1MIc0FhGmmOGzKTn9IhZIrC8CdMHBGtbQMIYNJ8gmWFmy1Q4+qhRobeRi3DCjuRhhd1OBPcywMCbYmZW/66TotNuv1C4dxkrmJFSsZYR9+THo449hzAKVTk2LuB4dBvixK9rSivEHaY3WFcSwO2H37kv0neD5nSyZ+xYYhgrrPMMSzxg6FMKWbgmsN+k0M45aO9oaUxqNdOSFl83hTgwXJENKUbKjuMW5Fbe0nX+c5y03h20C3H5E8849zDsJuBCBqsUDR490yxolcW5l27Jq6Tzrooh1HwFgjn9ag3kUm8GRrfZYOtkIj50DhnFhg9Fqp2MduFM0CHOLZW4Lt16PpVaHVFZGweeB9Q53Fhh2SFc3SFfvIPUX1fVZYJaJG36WCaVP4HAP3B9Yc6XuWD4MCIcdwsTNPMYm7ji9GLlhqRMMuoVk+t1WPmDDRQ/bB78G7HEElfZHgSKH3TrX+gnGHNHaW9hh4aQFDLeYzoPaiJyw0aehzsNdHWGHecOI4U7uZpWZjgbwAKYWD8YjRdTiDFbVyPi99KCAmqJFEOVHpU9m8CprFlprb7kmlDPb5SE4LJlZLhrUS68EzgJ6NdfPE/FV7uT1U/Q3CLkAh7DuqPqHskIibZTJlIJYnwgP2vDSz0ctchMTu5bcLWsFuiEO7pfDiLZb0F8d2c1eimtDcIVXDbC+c3dzj+b9O5h3DXCxA2xXlrY1N5d3WguPsrSstVJERIoAbLjpbQO6ZAs4JMCkGW45oZnv0XuLVeDIbpaNms2bJsAOvAzPo0GeZSKNM+i0gI5H0MZuzKxLYh1lqa5SqRLsXQPs9sB1fZLTKs06zaw4qcWORSayY6NjZYiQHEeq6icAylJa9XT4IGiTReVMyGKcbJ5F4ItfQHz3A6SWpYxJCrmU0nZiG0Y11+w9wZzuYF5+AtzesslDUZad4XBAjy31s6wAamGqsr0tA0Mfxgzl2Ko4bBDnVpb1DJXZ1qO/OqB9dg9zGRna0mvkF5iXn8Ac7tZrNE3A7R3ybUI6dIiLK+eK92G3zUyjLfUM3Y+4MBEgfv35pmCt9FnXz7Cth3EJoAS3D7BXE8w7Gbjac7APAbi9Q/oI8B/tsdxdIHnHzJTLI5p372DeBXB1sc6xiwHUerhnE5BGlHZJTQpqad4liHbSjDSvhuRWYJSm1gw6W/2V1z5llIxd3x8tlvsdTi8vcXd7heM4IGXDBevWo2tn7K5vubNcup/nqcM49RiXjmmR2WD0DV7MPZbUI7MAK+83qIgENjK/E5inPgbClEj8Q1WnipNeRRo+9yyX3mR8z+Ua5AwpdpTlGucHGi/KZqmfdvp/gATuZLBEEuojb1sx9cEFXDcLWlH323czunaGbXxhFyCp0JeIW1kOJrYJvLSeHPL9AlruNzgtAZtupxw0qDbC/V4n0Ion10u9BDINyFXBtYZcRAmSAGSfEUNbYIpinbfEAsVslBwrIbAHjAyg3AzKRWdq5VQkCgDOlmhIDBftujV707EbAF1xpQS69LCLh/V3GyxYtWOydDYCEGomB7uaQ54TFRmA8rvQPtGYdYWTItP52oEbo2wHrgDr/gjmK67HlGIRqzIXV1ykril25f8kcwVKQH1wX+kKpoY7coINAViO2NBB54w0GoHV+FhVKpc6jzwRkGaQQdHZSbNDrh7mAJDjxQbuIeKHXOksHZrVa9Q5Zj+5tC1wF5/QtPLNU1rxcJWCBhhyc2BK4t0RwLEEYfPLLLrvnNBVMsl8vC0f82ncni9jVl+28t4sdSbo0pxXeMcGaWwRp25z3/BJ2K4yPyuAn7ObHvyPxJVuP+KZi7jw9+WesAK9mEbYUpTh5hZEmXtbgoMnC4uEofGwlHDZLOxVq4V9MIuPTXa0AEpITS1Vss0TfiHjjQX0jG17f8yMI8WzWybWQb1ckzWD50Lo+j8NZTQuY4810O9swmXjcd3NzBmVdt+hYRegMLdYDjvYqSvFLbV+K8Hy1MMd/UpdrFyLFD9XXnnB9poE6mYYIzepXi0Nbjp02ReoUP8ArJmpd0jjytBI3rHBs2R7+pBrRCPd3RxhLv2GQlncZlTHXbHREFcuOsC6C0pF9PogYEpivrfAreBh8IzVD547E68GXmI7twaKxXMwnzQQEfLiSg2glnilJqDZzazPLcvXx7jipl2Fx7QRBq6BKcJS48OeAD1G1WEGOA1q3UNt+8NpzX6PfWnCMt0Es5/4odYCUGsZvZ7V9/MCLCC1m6lBmlo2f5Ds3cg1s50H9ZmNn43hlZSzsF3PTToK2bkG2TnktucvXUEZi3AG5T02dBXz6BA4h4Iv3wEwZVEt606LFD+lpqPNRLUGh6aTzqIuCJdroQ85oNRyEFBgIt4GYPYeZu/hUJENdJVUnVs+55KdnxWt6xUtXweBYqsEBwD3ShjuSNbiaGkIc9LklBIyCYclURGDOy0dRt9s3JDmZKVAuhI+mPViCnxcHZLsG5XflSGjtcPXHW/WU7RdM3Q92DmtB8+vc/GTiwzr/xOtkEoNzTiT0ZkEZ9QBnIdPhivUiWBNi8Yw82U3jDA2cSdov5SCFi2ZKW8AkIhb/9+5Q/vBC5gvNMD19QpLSPecqtxlYFWx0xtMl5JapAp+zQrT+hijKtOjaeSb6P6EdG+KCbXTm0mLrwD/PCTQZWVQcMZ9z+fYuTIfXCt/1+Bgtr8DG30XBC8wEcuwlsaVFy+3DwYNeCLCQymDrEduA4w8KAAJ6Co8JdRKSpIhzkzjU+13IysF9NIU0+9XaCglEMaVUQGsQcSn7YM0ZNbUGf1akYoq/bqHv71gzDlakHYKewcbRi44855vi8k19PKq4JIM+7kOXLRj2Ylx9Q7dX5SOzNzvEC9ukPoL5Ga/XrdqxVFgprDALON6jerGohDK9xKAtfNTGDA6P7TYmvr9Crdd3PB8lbmr87SIdY0nhtw0UIeq4K6B97xGoCQBA45CHYH2VR2m3tbWww2E9CCVXXXXsXlvfV0ArLIZskJU8TmFbfp0u11FK2Q1dfBzW6Ay75l5VfdMJIlhx9Dg1jc4BgufqEB9RXmz2kWDdfrVhhobLwi8/nhjAb0xCe8Pp/J7TAYhr0sSrRhP0eI+WBy8xSJFUoJk4iT2cnIilOrTGqYUWe0SpQxnEjoT2VZKeaIijdmI7KutHjC14BMsVgeZm4z83vuI737wIOBtRtW5t/VuTOVm0/dtMO0q6OZ+YL2LYYF5tqzLU96SfNX4NxWKGN2+kGLhAtxOiB+z5IA/DsjBwvYz2psD3PMD6HkDXF6u2HnbcbAUjQ4AyK5B6vcMabiW1+A5wCwjzOlu5dYL/PGANvfI2DzEzjRvkBJonkB3IzJ5pLFZJYI7AvYXiDfvIVx/gQvQALKRB6rZTmtKgR8+qXqA+iPs6RbmdM8ZqDxcyRg4Y+A2++3AnV2X/Dnnx3HGLqE6mOrx6LWOAUhh1VAJkXV9jkD6ckCaj0AmXvVdzWiecfLAbfQW0FWIiqStzRkF6nts9ZfrjFa6ZkmSAp1CxsmhNga29pnt+tLclHZXfP3Lccbt/JY+C3M6sASvr+atrpTiWVQL4MA9h7KK3dBPawqIxTbA67HLdursPSdTgnZNWzxnWdUPHPUJLlRewytx47iQrCv2uDS8InYB/Tiz8UkmTL5FOw0iidsWV7Y1U0ehNaYsCoyBsXS10mwMQ9KDy7h0HM8ev4MejjdoEp0xuFUPPVs2AKg9IXPmCvLgDN5pWR+hXsKEzHz0UPOqwdl/Q6uao3JGm2J0kEszQNsuaFpfDCQArNXvyAVS3Xa438N+NMGYr8Ld3/Ks8wu3+R8z0rFhvBPCr3WRqYuVN+eGKy2/KyyRRlmSz0rnTAwxNIGlcs+bQSp453xsOi2TAUxmN5orXr6SySwZnAj51oOOn5S3kwQHXprK57RgTrpK8KrHpGs4+O8uka7e2eDa+lCTk1ptP21ey8awjnd9AMrc6I6g3RFWg5cl4ErYOgCMP3F2lgM/4qoO13psHrgShCh4fmj1u/X9Jav120D9SMH7lUML4q5Zgznqwnjg85cS48ltA7pIsABsqvDsYHgFcf8RkD5aIZ2iYW/W2ofLDPEpU6UUGkngGylMKtymGHrdI1GkHxIwJmjxHm4C3d7B9h/CqtSu6rX0O5YikJUSn8894s17m3NOwYNO9zB3LzmjXzx/lj44ZE6R1GZKUqCryFckBev2mZllamaWHqP2hNT1jFDVCT4NuNaHg9Z9JJOPYwfXzyBanbS018WahN4F3LQNvGTwLNblijx4lP9ZCR9brX02tMY3LVfwhjH07UViA2dAW8IBhkpstFjIsvPQ+XaqA9YTo8F7NffNaEwsxVAV4lEsnamABhGO8VIbYbtYeK0AZ+huP3EwnRNwd0Ch610MoOc9bGUAoEvm3PaIbV+xXioYhpShECTALKCwwAk0Q8sEc/cCuH0J3HNDhqosprFFEiw2eQfjEtzlEe79I+g7LoBnz8Vz0iK3PVK/R+oukduKK5riunwvJzSU/SjLbAl+ZYmtDSVkQIbfl5e5ZON88x6B4wE4zKwqCayQR9pCFdRJsfXijC6njS5aMNMxzSBzCxf8yqMOYZX3VdbK+c0YTcHEqQmgfWRtce2WDQGYuB8gHy13mFbNT49KLjw2t6MpJimFgw95kJMBOVe03k0bYK9OzAh5712kq5tyHrVTN7dDeVBm2wGuQ3Ydsu3X4q9xPJ/cAKLqtq5+Jnlv1odGDsil8gleOcQRCJPMSVnZLEfY8SXM4QXMNLK7lUoQfPwN2GlmlcYElpzoW4aOdvtiokHLzGydszlBbQD2HdCZja5M/dDfzE9dOVXGHACQ1UbRNY88nANgtTKPzcOMajxfoaKzIK+dqNnb1R8gEcgmNMOMfTboh4lFw7xDMw7lf61IAlgVf5PXWUGWk87WRvQ2irm0svI4+M+icfW6xdI3FtDnaPEzd8yB05ZYhkVSMdPVYU3GYAKG6v+NGhBIYfR81JmrpSRt/wGNyp9KUM+ZELzDTH2paLtuYU2HJpQGIrufYD9YkL/rV2D6rn8U6eZXAqYBjOMbiFy5Yfjz+XXSv5d9cfL3bZaXc0SqbrCUAlKakJaXwPQhzOkTUJw3GSiFBU3Jcln6dh6eIfVXQHPBN7NpQLaHcRew7gLWdDDkkKobOedtNqv7kVL9noAU+UbPYuiM5IHlJcx0BzO+gJmOoBSRjAVu3ltrDGc8d+A8Y65qCxUkRSkxxKA3JQA4h3R1g3jzPsKzX4Hcvws4mRkp8D7VQeqxkRj2IH8EhRkU5vV/yFW1hEeYMsBnbl8fgraGJF4xsrEIrkVudsjtjgO1BufmAtRcwrgLGNtvP4Me3rp1cM5n167+2zpP3SMM55sH2845IKRQjjvnsAZ/f4BZjqDltLKIHkkIctshXVyB2g50Oa9U3SrzLnIY6gJ2OyG+YAmAHAwnW8PMUtcXmdk8UpsCsM3AAY6eZwV+PuxcOlThUIHYAJwBDWbF8kMEzYkDqso5pNX7tzQXVuy7PvGqtHO+yA2w6Yktmuo5ax1Q/WW3jlhF/ZFW2d4yvvzgopXxBjH0jPeHldKksAh/bV1ozk0EgIpJUr0vJIvRNzgsLU7RYRG8q7WxKKMNjYczsZwo1a2uNbNVfdH2M/O6TQINnlkR2g0nwQ1xxVS3kEFYv39WgCkH5R4GkThxwJG/Z5UbMIGzlbOAYcYXMPPdZrPZWGTbIbgO3rzGJX8Ehy6Z3iOvp/4Kqb9aMWqgCtyvOPYHNYeH1MIN60IDg+q1799d97H+DNMAZsCnDnl/dj0HvvoaPRIoP3M8cn1rvP7TXqv3h5YTB0bZZr1q20A3Kp52th1zBmXxz6nUKPT3Tx11gR+Qnxuk3RVSd8nXuZ4f5JC6a2B4vm4jTqyhH+YqWw6gOK+F2yoRYeiufcDO0eM0Z8f5ysfjqzply9/Xn3P1O6W0rfPUtTFZmZrDPTCeYKcTkDLymBE/6YEPb+CntjQjkUlouxlNu5QeFjXgCIEpjipjUfTXbdg4da2H8zDufdZ4rdlLRL8RwO8HlyT+aM75Pz37ewfgvwfwawF8DOC35pz/7mvvBdYK7/l3/oBcROb1b85GWNpalsVocBktrkODJa76x/UI0SBEAyIUo1nvG7hTYB1kk9APE4alQR8tnJ84Mxg72GmEuf076L7ys7ysPG9/rydUnS08dhOlR5btr909YLkBpWm3lLbzQpy2bk8zcJyRD8xrLm42bVxxVz1VdbZyvk8FQz7bz/NjVEKt2qDV7KS62KVDHZVat2K9cjzr1/Z8NQUjlvcqHrxIN4d87sa5qf5MlvtcO1w109NtzNXSW/5XGThF3EyvhaGHjlCKTYc6oMj3uqhHxDDFY9onp5G1T05nFFJvkeZ2Q/3kY9x6zD7od6iLogAes/jTUasbsproAnfpV82Vuiu0vhbGsBuWitWJ6csqKTA/1FoXLD633UO9mXMSwevWMerxCGGh/r6uetP2C1gZU6cFeczlOqSxw/LxNcYXVzi8vMI8dw/8Z/VLHZDUPKVmxvjokHK3mk+fxblvdnxmQCfGBv5rAP8CgJ8D8FeI6Idzzn+9etvvAPAi5/wPEdEPAPh9AH7rp23XUsJ197jzexSD2PJ65dBNEtxzJmQSQn7VXts4ftrtsR2rR+A6VJchJoMwr5oW49Tj/v4CzYehOJdYGzDsR/SXRzSXR8bTwRS15Fl/pdYhKWJTKtmqN0+tKfFZzRA2wg4L7DBzY0+TJLAkDjq9B/wC0qxdueX1g6Ru2nAZBrpsJuTFIh3brXdoOek1VUyEsqTjsO6gXY+lOq5MG1GtlCrsmrhGoZMeAK+MRKPFuohaq6O2UCvMA8sqiUa+k0lIS88yCWOP5dgjShakqy5tClG3I7K8EjNNLNtTlcgw9hulzXoOlZ8rQTMSGK9uXjGPvBZFIiAFhyBSAdZGtLsJ3cUJzeWR1SOlmYsfHM1GhCp7MR8/DvAnOU5lsoicQqyKpY81xShsUAuKAWClyaWBV216Wb02LmDYn9DvR7T7EaZuIjrPHkvH8i1M92Ep5uew0v+4g7Zb3+8SqHlZrqfOucIhP+OL83EmaPcwkXQZu8Dbsp/Ctf+UkSNfGz2PG0ctldTOhFrJ8TQObCwe16xbV/+1z2ztiORcWK/DmZH15lQ+gkJ81nidDP0fB/DlnPPf4Y3TnwLwWwDUAf23APiP5ec/C+APEhHl/Gqv6pgJ91UQ1SrwGC3mZBATO2P3NmKwEfvGCxXxdcsD62hMROcCusajtaFYVKmD0ewb+GSRs2jK2IChYRyscQE5Z4TAMpzxw+dFpAfA+kAQbExlgPtmwX4Ysduf0A8jbFtjmOnBRVLFPpX+BADTRDQDy8S6i/Gh+Jfx2BhWJHDjTjRAsshJ8HppjFAFRjIJaezgby9w+vgap7sLTGPPAdDwhFNd6FJnkAm8+KZQtHTUwZlInNJ9i8m3GH1ToK9am96a7YRXHLHGEIdmwdBPGHYjun4ucrdFs7oJLH0qeiyrTAMHsJwIfupw++IaL+6v8HLcYZJAf846SGAq7FW74N3+hOf7Ay73xzJXNMvaarFUAf4MIjy/Wc/fU47bRrT3Ht3tjK6XruWzxGOrQPm4ToxeN2M5gFvLhiAxGfhxYAPzas6qB6fa55XAeLYKzpkwLy3uDheYQwMvLe5E7MHZuoDWBjgXYaS3Qy389J4AUJROu9YXiIGPPxU9JX3Ql+lcGuzW7LXWVeHslrAEh7tpwIfTgBdzi/vA9/LgEp53C97rR9z0IwZxAqtHPY91Dtrq/jTVaub8GnvfFC10zboTVhu6mAzUI9SahE7OVetCwcXVCk91fl41Xhd2eZ2A/kUAX6l+/zkA/8Sr3pNzDkR0C+A5gI/qNxHR7wTwO+XXw7/yE3/ip19rL3/xj3dxdi7e/BgBvARfzm/5+Bwe7z/w8UvtmJ+O982N73rVH76tRdGc8x8B8Ee+nZ/5eRhE9OM55+990/vx7Rq/1I4X+KV3zE/H+/kcr9OA9FUAv7z6/TvltUffQ8x5ugYXR5/G03gaT+NpfJvG6wT0vwLge4joS0TUAvgBAD989p4fBvCD8vO/DOAvfRp+/jSextN4Gk/jWz8+E3IRTPx3AfhRMG3xj+ecf4qIfi+AH885/zCAPwbgTxDRlwF8Ag76T2Mdv9Rgpl9qxwv80jvmp+P9HA56SqSfxtN4Gk/j7RivK+L1NJ7G03gaT+NzPp4C+tN4Gk/jabwl4ymg/wIGEf1yIvrLRPTXieiniOjfldffIaL/jYj+lnx/Jq8TEf0BIvoyEf0EEf2aals/KO//W0T0g6/6zM/DICJLRP8XEf2I/P4lIvoxOa4/LUVzEFEnv39Z/v7d1TZ+SF7/aSL6DW/oUF5rENENEf1ZIvqbRPQ3iOj73+ZrTET/vsznnySiP0lE/dt0jYnojxPRN4joJ6vXvmXXk4h+LRH9NfmfP0BEr9cN9K0cOeenr2/yC8AHAH6N/HwJ4P8F8KsA/GcAfre8/rsB/D75+TcB+F8BEIDvA/Bj8vo7AP6OfH8mPz9708f3Kcf9HwD4HwH8iPz+PwH4Afn5DwH4t+XnfwfAH5KffwDAn5affxWA/wfsFvElAH8bgH3Tx/Upx/vfAfi35OcWwM3beo3BzYE/A2Coru2//jZdYwD/NIBfA+Anq9e+ZdcTwP8p7yX533/x236Mb/okvw1fAP4XsNbNTwP4QF77AMBPy89/GMBvq97/0/L33wbgD1evb973efoC9x/8RQD/HIAfkUn7EQAnf/9+AD8qP/8ogO+Xn528jwD8EIAfqrZZ3vd5+wL3UvwMhDhwfu3etmuMtdv7HblmPwLgN7xt1xjAd58F9G/J9ZS//c3q9c37vl1fT5DL3+eQpeavBvBjAL4j5/w1+dPPA/gO+fkx+YQvfsrrn8fxXwH4D7FqBj4H8DKvQtv1vm+kIACoFMQvpuP9EoAPAfy3AjP9USLa4y29xjnnrwL4LwD8LICvga/ZX8XbfY2Bb931/CK2Ohpv5LifAvrfxyCiCwB/DsC/l3PeiJBnfky/FZxQIvqXAHwj5/xX3/S+fBuHAy/P/5uc868GcAQvyct4y67xM7DI3pcA/DIAewC/8Y3u1Ld5vA3X8ymg/wIHETXgYP4/5Jz/vLz8dSL6QP7+AYBvyOuvkk94HVmFz8P4JwH8ZiL6uwD+FBh2+f0Abmi1t6n3/VVSEL9YjhfgDOvncs4/Jr//WXCAf1uv8a8D8DM55w9zzh7Anwdf97f5GgPfuuv5Vfn5/PVv63gK6L+AIdXrPwbgb+Sc/8vqT7UEwg+CsXV9/bdL5fz7ANzKMu9HAfx6InomGdKvl9c+VyPn/EM55+/MOX83uAD2l3LO/xqAvwyWegAeHu9jUhA/DOAHhCHxJQDfAy4kfe5GzvnnAXyFiP4ReemfB0tGv5XXGAy1fB8R7WR+6/G+tddYxrfkesrf7ojo++T8/fZqW9++8aaLFL8YvwD8U+Cl2U8A+L/l6zeBMcS/COBvAfjfAbwj7yewScjfBvDXAHxvta1/E+wS+GUA/8abPrbXOPZ/FivL5VeCb9YvA/gzADp5vZffvyx//5XV//8eOQ8/jTfAAvgmj/UfA/Djcp3/ZzCr4a29xgD+EwB/E8BPAvgTYKbKW3ONAfxJcH3Ag1dgv+NbeT0BfK+cu78N4A/irKD+7fh6av1/Gk/jaTyNt2Q8QS5P42k8jafxloyngP40nsbTeBpvyXgK6E/jaTyNp/GWjKeA/jSextN4Gm/JeAroT+NpPI2n8ZaMp4D+NJ7G03gab8l4CuhP42k8jafxloz/H9PJWRW489XjAAAAAElFTkSuQmCC",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "spec_x, freqs, t, im = plt.specgram(x, NFFT=512, cmap='inferno')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "spec_stp_error, freqs, t, im = plt.specgram(error, NFFT=512, cmap='inferno')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "spec_ltp_error, freqs, t, im = plt.specgram(ltp_error, NFFT=512, cmap='inferno')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x_in = (x * (2 ** 15)).astype(np.int16)\n",
+ "x_stp = (error * (2 ** 15)).astype(np.int16)\n",
+ "x_ltp = (ltp_error * (2 ** 15)).astype(np.int16)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "wavfile.write('lp/x_in.wav', 16000, x_in)\n",
+ "wavfile.write('lp/x_stp.wav', 16000, x_stp)\n",
+ "wavfile.write('lp/x_ltp.wav', 16000, x_ltp)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 511.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 511.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 511.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "for y, name in [(x_in, 'x_in'), (x_stp, 'x_stp'), (x_ltp, 'x_ltp')]:\n",
+ " spec, freqs, t, im = plt.specgram(y, NFFT=512, cmap='inferno', noverlap=256 + 128, pad_to=4096)\n",
+ " spec = 10*np.log10(spec)\n",
+ " \n",
+ " make_playback_animation(f'lp/{name}_spec.mp4', spec, len(y)/16)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "torch",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/dnn/torch/osce/stndrd/presentation/playback.py b/dnn/torch/osce/stndrd/presentation/playback.py
new file mode 100644
index 00000000..4d7e6c78
--- /dev/null
+++ b/dnn/torch/osce/stndrd/presentation/playback.py
@@ -0,0 +1,25 @@
+import matplotlib
+import matplotlib.pyplot as plt
+import matplotlib.animation
+
+def make_playback_animation(savepath, spec, duration_ms, vmin=20, vmax=90):
+ fig, axs = plt.subplots()
+ axs.set_axis_off()
+ fig.set_size_inches((duration_ms / 1000 * 5, 5))
+ frames = []
+ frame_duration=20
+ num_frames = int(duration_ms / frame_duration + .99)
+
+ spec_height, spec_width = spec.shape
+ for i in range(num_frames):
+ xpos = (i - 1) / (num_frames - 3) * (spec_width - 1)
+ new_frame = axs.imshow(spec, cmap='inferno', origin='lower', aspect='auto', vmin=vmin, vmax=vmax)
+ if i in {0, num_frames - 1}:
+ frames.append([new_frame])
+ else:
+ line = axs.plot([xpos, xpos], [0, spec_height-1], color='white', alpha=0.8)[0]
+ frames.append([new_frame, line])
+
+
+ ani = matplotlib.animation.ArtistAnimation(fig, frames, blit=True, interval=frame_duration)
+ ani.save(savepath, dpi=720) \ No newline at end of file
diff --git a/dnn/torch/osce/stndrd/presentation/postfilter.ipynb b/dnn/torch/osce/stndrd/presentation/postfilter.ipynb
new file mode 100644
index 00000000..3a225aee
--- /dev/null
+++ b/dnn/torch/osce/stndrd/presentation/postfilter.ipynb
@@ -0,0 +1,275 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "import matplotlib.animation\n",
+ "from scipy.io import wavfile\n",
+ "import scipy.signal\n",
+ "import torch\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plt.rcParams.update({\n",
+ " \"text.usetex\": True,\n",
+ " \"font.family\": \"Helvetica\",\n",
+ " \"font.size\": 20\n",
+ "})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def load_lpcnet_features(feature_file, version=2):\n",
+ " if version == 2 or version == 3:\n",
+ " layout = {\n",
+ " 'cepstrum': [0,18],\n",
+ " 'periods': [18, 19],\n",
+ " 'pitch_corr': [19, 20],\n",
+ " 'lpc': [20, 36]\n",
+ " }\n",
+ " frame_length = 36\n",
+ "\n",
+ " elif version == 1:\n",
+ " layout = {\n",
+ " 'cepstrum': [0,18],\n",
+ " 'periods': [36, 37],\n",
+ " 'pitch_corr': [37, 38],\n",
+ " 'lpc': [39, 55],\n",
+ " }\n",
+ " frame_length = 55\n",
+ " else:\n",
+ " raise ValueError(f'unknown feature version: {version}')\n",
+ "\n",
+ "\n",
+ " raw_features = torch.from_numpy(np.fromfile(feature_file, dtype='float32'))\n",
+ " raw_features = raw_features.reshape((-1, frame_length))\n",
+ "\n",
+ " features = torch.cat(\n",
+ " [\n",
+ " raw_features[:, layout['cepstrum'][0] : layout['cepstrum'][1]],\n",
+ " raw_features[:, layout['pitch_corr'][0] : layout['pitch_corr'][1]]\n",
+ " ],\n",
+ " dim=1\n",
+ " )\n",
+ "\n",
+ " lpcs = raw_features[:, layout['lpc'][0] : layout['lpc'][1]]\n",
+ " if version < 3:\n",
+ " periods = (0.1 + 50 * raw_features[:, layout['periods'][0] : layout['periods'][1]] + 100).long()\n",
+ " else:\n",
+ " periods = torch.round(torch.clip(256./2**(raw_features[:, layout['periods'][0] : layout['periods'][1]] + 1.5), 32, 256)).long()\n",
+ "\n",
+ " return {'features' : features, 'periods' : periods, 'lpcs' : lpcs}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "lpcnet_features = load_lpcnet_features('lp/features.f32')\n",
+ "\n",
+ "features = lpcnet_features['features'].numpy()\n",
+ "periods = lpcnet_features['periods'].squeeze(-1).numpy()\n",
+ "lpcs = lpcnet_features['lpcs'].numpy()\n",
+ "\n",
+ "x = np.fromfile('data/critical_3.pcm', dtype=np.int16).astype(np.float32) / 2**15\n",
+ "x = np.concatenate((np.zeros(80), x, np.zeros(320)))\n",
+ "x_preemph = x.copy()\n",
+ "x_preemph[1:] -= 0.85 * x_preemph[:-1]\n",
+ "\n",
+ "num_frames = features.shape[0]\n",
+ "x = x[:160 * num_frames]\n",
+ "x_preemph = x_preemph[:160 * num_frames]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def stp(A, alpha=0.8, beta=0.5):\n",
+ " A_num = A * (beta ** np.arange(len(A)))\n",
+ " A_den = A * (alpha ** np.arange(len(A)))\n",
+ " \n",
+ " return A_num, A_den"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "frame_idx = 31"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[<matplotlib.lines.Line2D at 0x7ff3342887f0>]"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXAAAAD4CAYAAAD1jb0+AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAAsTAAALEwEAmpwYAAA1Q0lEQVR4nO3dd3yV5f3/8deVnJM9TiaQEAhJ2EtCUAS3Ea2rDtx+62hFq9bR1lprtfZXO7S21iqK1Lo3VKrVagUHslQSQKYQEghk771zrt8f9wnLBJKck3Of8Xk+HufByck55/6QnLzPda51K601QgghvE+A2QUIIYQYHAlwIYTwUhLgQgjhpSTAhRDCS0mACyGEl5IAF0IIL2Vx58Hi4+N1amqqOw8phBBeLzc3t0prnXDk7W4N8NTUVHJyctx5SCGE8HpKqcLebh9QF4pSar5Sankvt2UrpRY4U6AQQoiBGVCAa62XHvq1Umq+4/YVjq+zXVeaEEKIo3F2EHMWUOC4XgBkOvl8Qggh+snZALcd8XXckXdQSi1QSuUopXIqKyudPJwQQogezgZ4HRB7tDtorRdrrbO01lkJCd8ZRBVCCDFIzgb4eg62wtOA5X3fVQghhCsNdBZKNpB1yODlUiDNcbutZzBTCCHE0BvQPHBHQMcccdujjqumhPfOskb+tmIX3z8umXOmDDejBCGEMIVbF/IMhZfW7eXDrWVUNLZLgAsh/IrX74WyOq8KgC1F9bR1dptcjRBCuI9XB3hlYzv7alo4fkwsHd12tpc2mF2SEEK4jVcHeH5lEwDzJg0DoKi21cxyhBDCrbw6wPdUNQNw0th4AErqJMCFEP7DqwO8oLKJYEsA4xIjiQqxUCoBLoTwI14e4M2MiQ8nIECRZAuluK7N7JKEEMJtvDvAq5pJSwgHIMkWKl0oQgi/4rUB3tFlZ19NC2PijQCPjwiiprnD5KqEEMJ9vDbA99e20G3XpMVHABATHkRNSwdaa5MrE0II9/DaAC+oNGag9HShxIYF0dFlp7lDFvMIIfyDFwe4MQe8pwUeGx4EQK10owgh/IQXB3gzceFBRIdZgYMBLv3gQgh/4bUBvueQGShg9IED1LRIgAsh/IPXBnhBVdOB7hMw+sBBulCEEP7DKwO8vrWTqqaO3lvgEuBCCD/hlQHeM4DZMwccICrEQmCAola6UIQQfsJLA7xnCuHBLhSlFDFhQdQ0d5pVlhBCuJVXBviuikaCAgMYHRd22O2x4VbpAxdC+A2vDPDtJQ2MHRaBNfDw8mPCgmQWihDCb3hlgO8obWTiiKjv3B4bHiQtcCGE3/C6AK9obKOqqb3XAI8Jlw2thBD+w+sCfEdpIwCTemuBhwVR29KB3S4bWgkhfJ/TAa6Umq+UylZKLXBFQceyw3Hi4t4C3BZmxa6hsa3LHaUIIYSpnApwpVQ2UKC1XgEUKKUyXVNW37YU15NsCz2wB8qhYhyrMetapRtFCOH7nG2B5wBLHMGdprXe4IKa+qS15us9NWSlxvT6fZsj1OtaZC64EML3ORXgWus64FlgCTCzt/sopRYopXKUUjmVlZXOHI49Vc1UNrZzwpi4Xr9/IMBbJcCFEL7PFV0oK7TW6UCdUmr+kffRWi/WWmdprbMSEhKcORyf7KgA4OSx8b1+PzrU0YUic8GFEH7A2S6UzEO6Tf4IxDr5fEf1wZZSJo2IIiU2rNfvx0gXihDCj1icfPxix+yTAow+8MUuqKlXuYW1bNpfx4PnT+rzPtGhEuBCCP/hVIA7+sCHLLR7fLS1jN+9v52EyGAun5XS5/0sgQFEBltkFooQwi94xUKejftrCbEGsOjamUQEH/09JzrMSr20wIUQfsDZLhS3uDt7HPeePYGAAHXM+9rCrDILRQjhF7wiwEOsgf2+b4xjOb0QQvg6r+hCGYjoUOlCEUL4B58LcOlCEUL4C98L8NAg6mRHQiGEH/C9AHfsSNjUITsSCiF8mw8GuLGcXvrBhRC+zvcC3LEaU2aiCCF8ne8FuOyHIoTwE74b4DITRQjh43wwwHv6wKULRQjh23wuwGVHQiGEv/C5ALcGBhARbKFWAlwI4eN8LsDBaIXLlrJCCF/nkwFuky1lhRB+wCcDPCYsSGahCCF8nk8GeHSYVU5sLITweT4Z4LZQq8xCEUL4PN8McMeWslrLjoRCCN/lmwEeGkS3XdPULjsSCiF8l28GuOyHIoTwAz4a4I7l9DITRQjhw5w+qbFSKhNIA9BaL3W6IheQFrgQwh+4ogV+nyO4Y5VSaS54PqfJnuBCCH/gVAtcKbUAWK+UStNaL3ZRTU7r6UKRueBCCF/mbAs8HYgDapRSzyqlbEfeQSm1QCmVo5TKqaysdPJw/RPj6EKpbpYAF0L4Lld0oeRrreuAXGDBkd/UWi/WWmdprbMSEhJccLhjswQGYAuzUiMBLoTwYc4G+PpDrtuAOiefz2XiwoOobpIAF0L4LqcC3DF4aVNKZTu+9ph+8LjwYKqb280uQwghhozT0wi11o86rq5w9rlcKTY8iPzKJrPLEEKIIeOTC3kA4iKCpA9cCOHTfDfAw4Ooaemg2y4bWgkhfJPvBnhEMFrLXHAhhO/y2QCPDTcW88hccCGEr/LZAI/rCXCZSiiE8FG+G+ARwQAylVAI4bOcnkboqXq6UGQmiuut31vDc6sKiAkL4qfzxpEYGWJ2SUL4JZ8N8JgwK0pBlXShuNRn31bwo5dziAmz0tDWxYZ9tbx3+0mEWAPNLk0Iv+OzXSiWwABsoVZqpAsFrTXvby7hTx9+y/aShkE/T21zBz99exMThkfy2c9P4x8/yGJXeRP/XL3HhdUKIfrLZwMcjG4U6UKBRSsLuP31jSxamc/3F65mxfbyQT3PIx99S0NbF3+5fDqRIVZOHZfAqeMSeH71Hjq67C6uWghxLD4d4HERwX7fhVJS18rjK3ZxzuTh5P46mwnDo7jrrU3sq24Z0PPk7K3hzfX7+dFJY5gwPOrA7dfPSaW6uYNPvx3cm4IQYvB8O8ClBc4rXxbSbdf8+vyJxEUE8/Q1mQQouOPNjXR196/V3Nlt5/5lW0m2hXJn9tjDvnfy2HjiI4L475ayoShfCHEUvh3gEUFUN/lvH7jdrlm2oZjTxiUwMiYMgJTYMB6+eCqb9tf1u+/6+dV72FneyEMXTiYs6PBxb0tgAKeMS+CLvErZtkAIN/PpAI8ND6autbPfLU1fs6OsgbKGNs6dOuKw2y+YNoJ5k4bxl+W7jrljY1FtC39bkcdZk4Zx1qRhvd7ntPGJ1LV0sml/natKF0L0g08HeFx4EFpDrZ+enX51XhUAJ42NP+x2pRQPXzyFUGsgv1i6uc+Ws92uuWfJZgIUPHTh5D6Pc8rYeAIUrNxZ4brihRDH5NsBHtGzH4p/dqOs3l3F2MQIhkV9d6FNYmQIv7lgErmFtfx1+c5eH7/oi3zWFVTz4AWTSLaF9nkcW1gQx6XYWL27ymW1CyGOzacDPMGxnL6y0f8CvK2zm6/31Hyn9X2oi2ckc9XxKSz8LJ8X1hzeH74kZz+PfrST86eN4PKslGMeb1ZqLFuLG2jr7Ha6diFE//jsSkzgQMuzvMH/AnxLcT3tXXZOTIvr8z5KKX574RQqGzv47X+2s35vDaeOS2BtfjXvbiphTnocj102HaXUMY83c3QMz35RwJbiemalxrryvyKE6INPB3hilNECr2hsM7kS99tcVA/AcSm2o94vyBLAs/83k6c+3c0/VhXw3y1lhFoDuf30DO7MHos1sH8f0maOjgEgt7BWAlwIN/HpAA8LshAZbKHCD1vgW4vrGRYVTGIv/d9HCgxQ3Jk9lltOS6OioZ2EyOAB720SFxFMWnw4OXtr4dTBVi2EGAif7gMHoxXuny3wOqYm2wb0mGBLICmxYYPemGrm6Bg27KtFa5kPLoQ7+H6AR4b4XR94Y1snBVXNTBsZ7dbjThsZTU1zByX1/veGKYQZfD7Ah0UFU97gX4GyraQBrWGqmwN8SrJxvK3F9W49rhD+ymUBrpR6xFXP5UqJUSFUNLb71cf6HaXGlrGTR0Qd456uNXFEFIEBSgJcCDdxSYArpbKBNFc8l6slRgbT0WWnvtV/VmPmVTQRHWolITLYrccNsQYyNjGCLRLgQriF0wGulEoDClxQy5DomQte4UeLeXaXNzE2MaJf87ddbUpyNFuL6/3qE48QZnFFCzxNa91ngCulFiilcpRSOZWVlS443MAkOlqh/tQPvruyibHDIkw59pSkKKqaOvxu4FgIMzgV4EqpbK31iqPdR2u9WGudpbXOSkhIcOZwg+JvqzGrm9qpae4gIzHSlOP3DJxKN4oQQ8/ZFniNUipbKTUfSFNKZbqiKFfyt9WYeRXG9rBjE81pgU8cEUWAkpkoQriDUwGutd7gaIHHAjaXVORi/rYa80CAm9SFEhZkIT0hQgJcCDdwySwURzdJutZ6gyuez9X8aTXm7vJGIoItDO/HEvqhMjU5WrpQhHADn1/IA/61GjOvool0k2ag9JicHE1FYzsVfjRwLIQZ/CLA/Wk15u6KJtP6v3tMTZaBTCHcwU8C3D9WY9a3dFLR2G56gE9KikIpY0m/EGLo+EWAj4gOoaPLTnVzh9mlDKndlY2AeQOYPSKCLYyJD5eBTCGGmF8EeJLjfI7Fta0mVzK08sp7phCaMwf8UFOSoqUFLsQQ84sAT44xArykzscDvKKJEGvAUU9A7C5TkqMormulxsc/9QhhJv8I8J4WuI8H+O6KJjISIwgIMG8GSo8pScZA5rYS6UYRYqj4RYBHh1oJDwr0jwBPMLf/u8fkpJ69waUbRYih4hcBrpQiyRbq010oTe1dFNe1MnaY+f3fANFhVlJiQ9kqLXAhhoxfBDgYA5m+3ALPdyyhzzB5CuGhpiRFy0wUIYaQ3wR4ckwoJXW+u5jH7E2sejMlOZrC6ha/OpmGEO7kPwFuC6WmuYPWjm6zSxkSeRWNBAUGMCo2zOxSDpicZJzSbbtMJxRiSPhVgIPvzkTJr2giLSEcS6Dn/Ep7TnIsM1GEGBqe89c+xHoW8/jqQGaeYwqhJ4mPCGZEdIj0gwsxRPwowI3tVX2xBd7W2c2+mhaPWIF5pMlJ0WyVLhQhhoTfBPjwqBAClG+2wPMrm9Das2ag9JiSHEV+ZRMtHV1mlyKEz/GbALcEBjA8KsQnW+C7TT4Lz9FMSYpGaxnIFGIo+E2AgzGVsMgHN7TKK28iMECRGhdudinfMc1xkuNN++vMLUQIH+RXAZ4SG8b+mhazy3C5XeWNpMaFEWTxvF9nYlQIybZQNkqAC+FynvcXP4RGx4ZTWt9GW6dvzQXPq2hinIcsoe/NcaNsbNpXZ3YZQvgcvwrw1HhjkYsvtcLbOrsprG72mD1QejMjxUZxXaucI1MIF/OrAB/t6CPeW+07AZ5f2YRdwzgPHMDsMWNUDIB0owjhYv4V4I5l5oXVzSZX4jo9Z+Hx5C6UyUlRWAMVG6UbRQiXsjjzYKWUDUhzXGZpre91RVFDxRZmJSrEQqEPtcDzKhqxeOgMlB4h1kAmjYhi0/5as0sRwqc42wK/HMjSWi8FUEotcL6koaOUIjU+nL0+1ALfVd7EmPhwj5yBcqgZo2LYXFRPV7fd7FKE8BlO/dVrrRdrrRc7vkwDCpwvqReVO6FwHVTng925GSSjYsN8qwVe3ujR3Sc9Zoyy0dLRzS5Hl48QwnkuabYppdKAGq31il6+t0AplaOUyqmsrBzcAdY9BS+cA09mwp9Gwb9+BPmfgtYDfqrUuHCK61rp9IGWYFtnN4U1LR65hP5IM1J6BjKlG0UIV3HV5+75Wuube/uGo5WepbXOSkhIGNyzz70L/m8ZfP9pmHIJ5C2HVy6GF74HRbkDeqrRcWF02zXFPrAic3eFsQeKN7TAU2JDiQsPYkNhndmlCOEznA5wpdR8rfWjjuuZzpfUi7h0SD8DZlwDFz4JP98F5/3V6FL5ZzZ8/ifo7t9mSQenEnp/P3heRSPg2VMIeyilmDk6hvV7a8wuRQif4VSAK6WygUeUUrlKqVwg1jVlHYMlGGb9EH6SC1Mvg8//CK/Nh7Zjb5iUGmdMJdznA4t5dpU3YQ00Bma9wQlpceyraaG03vs//QjhCZwdxFyhtU7XWs90XL7TBz6kQqLgksVwwd9hzxfw4rnQWH7UhyREBhMWFEhBpQ+0wMsbGRMfjtWDzsJzNCeMMd7fvyqQVrgQruAdf/nHMvM6uPptqC6Aly+E5qo+76qUIi0hnPxK758Nsau8yaOX0B9p4ogoIkMsfLWn2uxShPAJvhHgAGOz4eq3oHYvvHwRtNb1edeMhAjyK7w7wJvau9hX08IELwrwwADF8amx0gIXwkV8J8ABxpwMV74Old/Ckuugu7PXu40dFklJfRtN7d57lpidZUZ//8QRUSZXMjAnpMVSUNUsG1sJ4QK+FeAAGWfCBX+Dgs/hg5/1Olc8PcGYteHNrfDtpcYMlIlJXhbgY+IA+HKPtMKFcJZTe6F4rBnXGlMMV/8VEibAibce9u2eU4/trmhieorNhAKdt6O0gagQC0nRIa55wopvoXANFG+Ayh3QXAktNaACwRoK4fEQPw4SJ0HaqZA8EwICB3yYyUlGP/iavCounJ7kmtqF8FO+GeAAZzwAVbvg419D0gwYfeKBb42ODcMaqMjz5hZ4SQOTkqJQSg3+SRpKYcNLsPUdqNpp3BYWB8OmQFyGcV3bobMFGsugOBe2LYPPHoYQG0w8HzKvh5FZ0M86LIEBnJQRzxd5lWitnatfCD/nuwEeEAAXPQ2LT4Ml18MtqyAiETBCZEx8+IGTAXubbrtmZ1kjVx6fMrgn6Pl08s1bYO+C1JPg+JsgIxtiUo8exi01RvdU3sewdRlsfBWGTYVTfgYTv2/83I/hlHEJfLi1jN0V3jWLRghP43t94IcKiYbLX4a2OvjXDw/bCCsjMYLdjpWM3qawupnWzu6BD2C2NcDHD8DCE2DLv2Dm9XDHRrj+fSPAY8ccuyUdFmtsZ3DxIvj5TrjgCehqM94kn5kDecdeCnDKOGNLhZW7Brk3jhAC8PUABxg+Fc77i7HQ5/M/Hrg5IzGSfTUtXnl+zB2OAcxJAwnwvBXw1CxY+3eYdgXc+Q2c95gR2oMVHGm8Cdz2FVz6T+jugNcuhTeugpo9fT4s2RZKekK4BLgQTvL9AAdjUPO4a+GLxyD/M8Bogdu1d+6Jsr20nsAA1b9dCDtbjdk4r10KoTHwo0/hooUQOcx1BQUEwtT5cOs6yH4IClYarfw1T/S5/e+p4xL5ek+NV76BCuEp/CPAAc59FBLGwzsLoLGcsY7w88b9qbeXNJCeEE6I9RizQOr2w/Nnw/rn4MTbYcHnMHLm0BVmCYaT7oaf5MDYs2D5g8aOkdX537nrqeMTaO+ysza/71WzQoij858ADwqHy16E9kZ45ybS40KxBip2lB57AyxPorVmS3EDU5NtR79j4Vr4x+nG9gJXvQVn/x6sLppyeCxRSXDFq3DJP4xFVc/Mha8WHzYnf3ZaLBHBFv639eh71wgh+uY/AQ6QONFoie9ZSdCXfyMjMZLtJd4V4GUNbVQ1tTNtZHTfd9qyFF66AIKj4KZPYPw57iuwh1Iw7XK49UtjlsuH9xg7RjaWARBsCeSMCYks31FOt33gJ+YQQvhbgAPM+D9jC9rP/sB5UXvY7mUt8M1F9QBM7SvAN75mnLEoZTbc9KnRbWSmqCS4ZokxkLx3DTx9Iuz4DwDnTBlOTXOH7BEuxCD5X4ArBec/DjGp3FD2O7oaK6lo9J59ObYU1WMJUL3PQMl5Ad69FdJOM0Iz1Obu8nqnFMz6Edz8BdhGwVvXwru3ceroEIIsAXy0tczsCoXwSv4X4GBMf7vsRUI76/iLdRE7SurNrqjfvimqY9ywyO8OYH65CN6/C8aeDVe9CUFhptR3VAnj4IfL4eSfw6bXCX/hNK5PqeDjbWXoQZzfVAh/558BDjBiOu1n/I4zAjdh/epps6vpF2MAs/67/d9rnoCP7oUJ5xuDh+4arBwMSxCc+QBc/1/Qdu4rvYsrml8lp6DC7MqE8Dr+G+BA6Nyb+TxgNicUPAn715tdzjEV1bZS19J5eP/3yj8b0/UmX2LMsrEEmVbfgIw+EW5ZQ/fUy7nT8g7Dln6/1+mGQoi++XWAoxT/GnkfFSoOlt4IrbVmV3RUPQOY05JtxpS8Tx82NpaafhVc+hwEWs0tcKBCorBc+iwvj/wt0S370ItOMvrxpTtFiH7x7wAHxqQk8eP229GNJfDu7R4dHt8U1RFkCWDcsHCj1f3FnyHzB/D9pwe1taunyDj9Wua1P0KlbbrRj//GVVBfbHZZQng8vw/wKUlRbLJnUDTzXvj2ffj6H2aX1KecvTVMS4oieMX9xp4ms26C85/o1w6Anmz2mDgstmR+HvwbOPuPUPAZPJUFq/4CXe1mlyeEx/Luv3wXOG6UDYD/RV4K486Bj++Hkk2m1tSbts5uthXX8Sv9D/hqkbE0/tw/e314AwQEKC7PSuGL3TXkZ/wAbvsa0s+AT/4fPD3bWJhkt5tdphAex/v/+p2UGBlCsi2UjUX1cNEzEJ5gzFNu9Ky5yduKanhYLSKzchmc/DOY93C/T6LgDa4+YRRBgQG8tHYvxIyGK1+Da98BS4ixFfAzc2D7u31ujiWEP3I6wJVS85VS2UqpBa4oyAzHjbKxaV+dsdf1la8bJy14/XJo95CNrro6sP33x1xm+YLmOb8wzjbkQ+ENkBAZzAXTk1iaW0R9q+Nk1Blnwi1rjK1q7V3w9g/gyUxYtxBa60ytVwhP4FSAK6XmA2itVzi+znZFUe42I8VGcV2rsSIz6ThjOl7ZFmNmSrfJZ67vaIE3rya9cjkLg24gfN79PhfePW6Ym0pLRzdvr99/8MaAAMdWtV/C/BcgcgT871fw14nG7+fb/0JXh3lFC2EiZ1vgs4ACx/UCINPJ5zPFcY4TG2/aV2fcMG4enPsY5P0PPrjbvP7XtgZ49VL07hU8rG6mIOMGc+pwkynJ0cxJj+PZL/Jp6TjijTPQYpwJ6MaPYMFKmH6lsbf7m1fBnzOMbq/1/zTmknvwTCIhXMnZc2Lajvg67sg7OLpWFgCMGjXKycMNjSnJ0VgCFJv21zFv8nDjxlk/hMZSY6qeCoTz/ureAcO6/cZ0usodVM1byHPv2fjD6Bj3Hd8kPz1rHPMXreOltYX8+LT03u+UdJxx+d6jRojveM/xr7FJFiE2x31mQMJE46xDMWMgPN5nP70I/+RsgNcBsUe7g9Z6MbAYICsryyObRiHWQCaOiGLT/rrDv3H6/Ubf6+rHja/P+4t75lsX5Rjh3dUGV7/FZ7Xjgc1kpfp+gGelxnLa+AQWrcznmtmjiAo5yuKkQKvxaWncPKPVXb0b9q4yZhGVbIS1Txq/vx5BERCdAhEJEJ4IEcMOXg9PgPB4dFgc9QE22gjGFmY99kkzhDCRswG+noOt8DRguZPPZ5oZo2z8K7eIrm47lkBHS1spOPM3xvXVj0NzJVyy2Dg5xFD55i34zx1GuFz3H0icwNo3NxIfEXTgLEK+7ufzxnP+k6v5+4o8fn3+pP49SCmIH2tcenS1Q+1e4/yctXuMfxuKoakCinOgqRI6Dz+lnsJ4QTfpEMp1FI2B0djDEoiMG0Fy8kiCooZBVLIxU8Y22jhNnbTqhUmcCnCt9VKl1C8cg5e2nsFMb3T8mFheXlfI1pKGA33igPHHmf0QRAyH/91nnCLsyjcgOtm1BbQ3wn/vgW/egFFz4IpXjBah1qwrqObE9HiUnwTFlORorjp+FM+v2cNFM5KZknyUk1ccjSXY2A+9jz3R8yubeOp/37Bx+y5iaGBGXCfHxXYxwtpMRFctAS1VBDZVYW0pJbRpBxQ2gDpiGmNwlBHkMaMhfhwkToLECcZ1S/Dg6hain5xtgaO1ftRx1WvDG+CEMUb3/ZcF1YcHeI/Zt0BMqjHz4Zk5cOGTMOlC1xw8/1N4/6dQVwin3gun/MIYtAMKqpopb2jnxLTvDC/4tF9+bwIrdpTzy3c28+9b5x78VOQCTe1dPPa/nbzyZSEhlgCuPWk2184eTUps71vwaq3ZsK+Wx7/ezyebdpGoK7k4tYuLUzuJ7yqD2kKo2gW7PjrYZaMCITYNhk+B5JnGZcT0of305itaa6FyJ9QXGZ+YGkqNf9vqoKP54KW7EwIsxthUgAUCg41PRD2XsFhj//meS3QKBPvWp1jlzn2Ys7KydE5OjtuON1Bn/uVzRsWG8cINx/d9p6rdxsKS0k0w8UI4+w9gSxncAWv3woqHYNsy44/9wieN048d4pUvC3ng31v5/OenkRrvX3/8H24p5cevbeCWU9P55fcmuOQ5P9tZwa+XbaWkvpVrThjFXdnjiI/of0u5oqGN59fs5eV1e2nr7Oai45K548yxxu+mq8Poh6/YbpwLtHy7MR21fp/xYBVgtNCTZ8LouZA6F6JHuuT/5bUaSmDfl1CcC+XbjJ9bY+nh9wmKMM7sFBprBHBQuHFbQKAxQ0x3G2+cna3QVm+s42ithZYq6D5iiml0inFqxcSJkDgZhk0yBroDnW7LDimlVK7WOus7t0uAH3T/si28u6mETQ+edfQWX1eHsQf3qr+AtsOMa2D2bRCfceyDaA3FG4wzxW9+yxiIO/lnMOeOXvfxvu21DWzYV8vaX57hN10oh7p/2RZe+2ofT109g/OnJQ36eWqaO/jd+9tZtrGYjMQIHrl0GjOdmNVT3dTO4i8KeGndXjq7NZdmJvOTM8b23opvqjB+58W5jkuOETRgdL+knmyEeepJRkvRl9XsgfxPoHAd7P8K6h1z/i0hRldX4iRImGAErG20EdwhvZx9qj/sdmiugLp9xqV2j9Gyr9hh/Gt3LBizhBqzlno+KSXPNH4PHvT3JgHeD+99U8Idb2zkvdvnMm2k7dgPqNsHXzxm9Ft3dxjT1sbOg+Qso080NNZ4kbTUQHUe7PsKdq8wrltCYeb1MPcO40XaC7tdk/X7FZw2PoG/Xn6cK/+rXqOts5trn/uKzUX1/OO6LE4dlzCgx2utWbaxmN+9v53Gti5uPT2D205PJ9jimtklFY1tLPq8gFe/KsRu11w+K4XbT88gyRba94PsdqjYBntXG5fCNQe3Mo5JhTGnwJhTjX8jEl1Sp2k6Woz/4+4VxqXGsed75AhIOQFGzYaU42H4NPduh9zdCTUFULr54Btr6TfQ7dg8LTzBCPKkTEeoZxpdMiaRAO+HioY2jv/DJ/zq3AksOKWPOci9aSiFLUtg2zvGi0D3sfDHEgKjTjT6zqdcCiFHH5zbWlzP+U+u5i+XTefSmf77Ubu+pZMrFq8jv7KJP10yrd8/i90Vjfz2P9tZlVfFjFE2/nTJNMYPjxySGsvq21j42W7eXL8PheKq41O47fQMEqP6cXYkux0qd8CeVbDnCyPw2h0t9ISJRpCnnWp0u3jKeU77orXRuu0J7MK1RihaQmHMyZCRDelnQly6R7VwAeOTdcU2I8yLcqFkg/F/wZGRMWOMIO9ppQ+f6rYxDQnwfjrD0Q/+4tH6wY+mvQnKNht9e621RqsiOAriMoyZCQM43dnfP8nj8RW7WH9/9oD6aX1RfWsnN7+Sw5cFNVwwPYl7zxnPyJjeBx23Ftfzwpq9LNtYRFiQhXvOHs+1s0cTGDD0gVFc18pTn+axJKeIwADF/80ezS2npQ/s92fvNhoCe76APSuN7oauVqMPfcR0Rwv9FKMx4AmDoq11Rp27V8DuT4wBRzC6QjKyjT1tRs3x7FP99aWtwRjvOtD9tREaihzfVMb4V/w44xKXYUwxjRxufMIIi3XZpwoJ8H566L1tvLl+H5senGf6Io6Ln16DXcO7t801tQ5P0dVtZ+Fn+Sz8fDfdds3JY+M5YUwcw6KCae+ys7OskS8Lqvm2rJEQawDXnDCa207PIDbc/aeZ21fdwt8/zeOdDUUEWwK5bk4qN5+SRsxgaulqN8KjYKUR6kXrja65ACuMnHWwhZ6c5Z5T6nW2Gn36hWuMwC5abwwkBkcZdaSfaQT3YAf3PV1jmfH/L9tizD6q2gVVecab7JECLGANN04yPv95GD1nUIeUAO+nz3dWcP0L63nxhlmcNt68/sea5g5mPrycO88cy13Z40yrwxOV1LXy0rq9fLS1jMLqlgO3h1gDyBody5kTE7lkxkiiw8w/xVxBZRNPfJLHe9+UEB5k4ca5qfzw5DSiQ52oraMZ9q1ztNC/cOxfr8EaZvQpj55jzLBInAC2VOe2gOjuNMKpYrvREt33pXE8eyegjMG/nm6RkVned1o/V7HbjdkzjWWOf0uNTyadLcalo9mYqJAwuL9lCfB+auvsZvpvP+aq40fx0IWTTavj3xuLueutTf0fUPVT9a2d1DZ3EGQJICEyGKsL54u70q7yRp5YkccHW0qJDLFw08lp3DA3lcijbRXQX621sHfNwUCv3HHwe9Yw46N99MiDH+1DY4zxGGsoBAYZA/CdLcaAY1udY+51iTEPuzr/4GyNwCBjUG/UbKP7JuV4Uwf2/ElfAe7Zkx9NEGINZE56HCt3VZpax6ffVhAfEcyUpEGuQvQT0aFW51qzbjJuWCQLr8nktpIGHl+xi78u38VLa/fy+4uncs6U4c49eWgMTDzfuIDRb1u50wjyih1GC7punzFtr6X62M8XnmisNI7LMM5SNWyyMb0vfpx7umgctNZ+OXV2ICTAe3Ha+ER+89429lY1m7J4pqPLzuc7K5g3eTgBbhh4E+4zKSmKf/wgi81Fddz3zhZueTWXSzKT+e2Fk13TGgdj3nTKLONypM42Y9uGrlbjenf7wda4JRSCI90a0j22Ftfzn80lbCysY1dFI83tXdg1xIRZiQsPJskWwsiYMFJiQ41/Y8IYGROKLczq1yEvAd6L08cn8hu28dnOCm6IH+P246/ZXUVDWxfnTnWyZSY81rSRNpbdOpenPs1j4ef5bCmq57nrshgdN8QNBmuIR80G2VHawMMfbGfN7mqsgYqpydGcO3UEtlArSkFNcydVTe2U1LWSW1hLQ9vh+8RHBFsYGRPquISRZAthWFQIiZEhDIsKJjkm1GVz/j2RBHgvRsWFMX5YJB9uLeOGue4P8J5+0pMyBrZoRXiXIEsAP503ntlpcdz6+ga+v3ANz18/i8xRvr9tsNaaf6wq4NGPdhIVauX+cydyWdZIbGFHb/3Xt3ZSVNtCUW0r+2uMf3u+XpdfTXPH4ZuNWQMVE4ZHMTcjnnOmDGf6yGifarFLgPfh3Kkj+Nsnu6hoaOvfYgwX6eiy8/G2Ms6aNIwgi2cOyAnXmpMRz7u3zeUHz3/Ntc99xXPXZTEnPd7ssoZMZ7edB/69lTfX7+ecycP54yVT+z290hjziGZyL2NDWmua2rsob2inoqGNsoY28iqa2LivludWFbBoZT7TR0Zz6+kZzJs0zCeCXBKiD+dNG47W8OFW956dfk2+0X1y3tQRbj2uMNfouHCW3HwiI2NCufHF9eQW1phd0pDotmvufmsTb67fz+2nZ/D0NZmDmxvfC6UUkSFWMhIjmJMRzyWZI7n3nAm8ueBEcn99Fg9fNIW61k5ufiWXG15cT1Fty7Gf1MNJgPchIzGSccMi+GBL6bHv7ELvf1NKZLCFk8b6bgtM9C4xKoTXb5rN8KgQbnwxh13ljWaX5FJaax54dyvvby7lvu9N4Odnj3fbIH10mJVrZ4/m05+dxoPnT+LrPTWc+8QqVuWZO9vMWRLgR3He1CTW762hvKHNLcdrau/iw62lnD99hE8PvIi+xUcE88oPTyDIEsAP/vk1pfW9rO7zUs+t2sPrX+3jllPTufnUAew15EKBAYobTxrDR3eeQpItlOtfWM9rXxWaUosrSIAfxfnTR6A1vLup2C3H+2BzCS0d3VyW5aNLkEW/pMSG8dINx9PU3sUtr+TS1tl97Ad5uLW7q/jjhzs4Z/Jw7j2n9zMkudOouDCW/ngOp45L4P5lW3l53V6zSxoUCfCjSE+IIGt0DG+u3487Vqy+nVNERmIEM3o7I5DwK5OSovjr5dP5pqieX/97q1tef0Olqqmdn7yxkbSECB67fLrHDB5GBFtYdO1MsicO48F3t/HW+n1mlzRgEuDHcMWsFAoqm8kprB3S4+yuaCK3sJYrslI85gUuzDVv8nDuOHMsS3OLeOVL7/yYr7XmgX9vpbGti4VXZxIR7FkT34IsATx9TSanjEvgV8u2sjqvyuySBkQC/BjOmzaCiGALb369f0iP8+qXhVgDFRfNcPHJkoVXu+vMsZwxIZGH39/B1uJ6s8sZsPe+KeHDrWXcfda4IduL3VlBlgAWXj2DjIQIfvxaLnleNHgsAX4MYUEWLpiexAdbSqhr6Tj2AwahvqWTt3P2c8H0JBIi/Xvfb3G4gADFY5dNJybcyh1vbKS5vevYD/IQFQ1tPPjuNmaMsrHglDSzyzmqyBArz98wixBrID98KYf61k6zS+oXCfB+uG7OaNo67byybmg+xr7+9T5aOrr50Ume/SIX5ogND+LxK45jT3UzD723zexy+kVrza+WbaGts5vHLpvulpNpOCvZFsqia2dSUtfKPUu+8YpxBwnwfpgwPIrTxifw4tq9Lp8R0NFl58W1ezgpI55JSYM8eavweXPS47n99AyW5Ba5bVaUM97ZUMyKHRXcc/Z40hMizC6n32aOjuG+cyfy8fZynlu1x+xyjsmpAFdK2ZRSmUqp+UqpR1xVlCe65dR0qps7WJLj2r7wt3L2U97Qzo9Odv+eK8K73HnmWDJH2bh/2Vb2VXvuKsLS+lYe+s82jk+N5UYT9hJy1o1zU/nelOH86aNvWb/Xs1fEOtsCvxzI0lovBVBKLXC+JM90wphYjkuxsWhlgcta4S0dXfz9kzxmpcYM+Gzrwv9YAgP4+1UzUArueHMjnd19nDzbRFprfvmvLXR1a/582TSv3A5ZKcUj86eREhPK7a9voKqp3eyS+uRUgGutF2utFzu+TAMKnC/JMymluOfs8RTXtfLCmr0uec7nV++hsrGdX35vgkwdFP0yMiaMP10yjU3763h8+S6zy/mOt9bvZ+WuSu47d8LQb407hKJCrCy8JpPalk7ufmsTdrtn9oe7pA9cKZUG1GitV/TyvQVKqRylVE5lpXfvOzA3I57siYks/Gw3lY3OvStXNLTx7MoCsicOY+ZoOS2V6L/zpo3giqwUnlmZz9rdnjNvuai2hYc/2MGJaXFce8Jos8tx2uSkaH574WRW5VWx8LPdZpfTq2MGuKN/e8ERl+wj7jZfa31zb493tNKztNZZCQne301w37kTaevs5pGPvnXqeR76zzbau+3cf95EF1Um/MlvLpzEmPhw7n57EzXNQzO9dSDsds09SzajtebR+d7ZddKbK2elcNFxSTy+Yhdr8z3nzbLHMQNca720p6vkkMuBlrZSar7W+lHH9cyhLNYTpCdEcNMpaSzNLeLTb8sH9RzvfVPCf7eUcccZGYwx4ZRtwvuFBVn4+5UzqG3u5BdLN5s+5e2FtXtZV1DNgxdMIiU2zNRaXEkpxe8vnsqY+HDueGMTFY3u2diuv5ydhZINPKKUylVK5QJ+0RdwV/ZYJgyP5J4lmymuG9hucQWVTfzqnS3MHB1j2o5swjdMSY7m3u9NYMWOcl41cal9Xnkjj3z0LdkTE7ncBzdiCw+28PQ1M2lq7+SONzbS7UH94c4OYq7QWqdrrWc6Lt/pA/dFwZZAnro6k/YuOwtezqGhrX+rtqqa2rnhxfVYAxV/v2oG1kCZhi+cc+PcVE4bn8DvPtjBt2UNbj9+Z7edn779DRHBFv54yTSfHYwfPzyShy+aypcFNfxthecMHkuCDFJGYgRPXjWDnWWNXP/818dcZl9a38pVi7+krL6Nf14/i2RbqJsqFb5MKWOpfVSIlVtf3dDvxoSrPL58F1uK6/nDxVN9fhuI+TNHctnMkTz12W7+t829Z+rqiwS4E06fkMhTV2eytbiB859czZcF1d+5j9aa5dvLueDJNZTWt/HCDf5x0lrhPvERwTx9TSb7alq4+033TXlbsb2cpz/P56rjUzhnynC3HNNsv7toCtNH2rjzzY1sLqozuxyUOwc/srKydE5OjtuO5y4b99Vy++sbKa5r5YQxsZwxIZH4iGBK61v5eHs5m4vqmTA8kieunOGxO7IJ7/fKur088O42fnJGBj+bN7QnTdhX3cL5T64iJTaMf/14DiFW/zmDVGVjOxc/vYa2Tjv/vm0OI2OGftBWKZWrtc76zu0S4K7R2tHNi2v3siRnPwVVzQdunzgiimtnj2L+zJFymjQxpHpWQb6Vs59HLp3KFbNGDclxmtu7uGzROopqW3j/JyczKs53Zp30V155I5c8s5a48CDeXHAiw6NDhvR4EuBuVN/SSW1LBzHhQUSHWs0uR/iRji47P3o5h9V5lSy6dibzJru2a6Or23j+VXlVPHddFqePT3Tp83uT3MJafvDPrxjWczLqIQzxvgJc+sCHQHSYldT4cAlv4XZBlgCeuSaTqSNt3P7GRta4cKVmt13zy3e28PnOSn73/Sl+Hd5g7Fz44o3HU97QxsVPr2FHqftnAUmAC+FjwoMtvHD9LMbEhXPDi+tZvn1wC84O1W3X3LP0G5bmFnF39jiuPmFoume8zazUWJbcMgetYf4za3lnQ5FbF1VJgAvhg2LDg3jr5tlMHBHFLa/m8vzqPYMOlsa2Tm5+JZd3NhTz07PGcWf2WBdX690mJUWx7LY5TE6K5qdvf8OPX93A3kPGwYaS9IEL4cOa2ru4+61NxlTW6Un8vwsnExMe1O/Hby9p4M43N1JQ1cwD503kei/c39tduu2aRSvzeerT3XR22zlv2gi+f1wSWamxRIU4150qg5hC+Cm7XfPMynweX76LqFArP5s3jkszRx516l9NcwfPfpHPc6v2EBNm5YkrZzA3I96NVXuvisY2nv4sn2Ubiw+cWzMxMpgnrpzBielxg3pOCXAh/Ny3ZQ3cv2wruYW1xEcEc+H0JE4eG09qfDiRIRbqWjr4tqyRz3dW8v7mEto67VyaOZJfnzdxQK12YejosrOuoJodpQ3srmji9tMzSB3k5nUS4EIItNasza/m+dV7WLW7io6u757VJyrEwjlThnPTyWmMHSYLzzxBXwFuMaMYIYQ5lFLMzYhnbkY8rR3dbNxfS1l9Gw2tncSEBzE6LpwpSVFYZKM1ryABLoSfCg0KZE669Gt7M3mbFUIILyUBLoQQXkoCXAghvJQEuBBCeCkJcCGE8FIS4EII4aUkwIUQwktJgAshhJdy61J6pVQlUDjIh8cDrtud3nWkroHz1NqkroGRugbGmbpGa60TjrzRrQHuDKVUTm97AZhN6ho4T61N6hoYqWtghqIu6UIRQggvJQEuhBBeypsCfLHZBfRB6ho4T61N6hoYqWtgXF6X1/SBCyGEOJw3tcCFEEIcwisCXCk1XymVrZRaYHYth3LUtdzsOg6llLIppTIdtT1idj2HcvwOsz2trh6eVpdSqlYptVwp9QuzaznUIa+v+WbX0sNRU75SKtdx8bTf5ZBkmMcHeM+LRGu9wvF1trkVHaS1Xmp2Db24HMjqqc1T3vSUUplApuP3mKmUSjO7pkM5XlceVRNwmdb6LK31o2YXcoT7HK+vWA/6PcZqrdO11jOBm4BnzS6oh+O1VeB47Rc4/hZcwuMDHJgFFDiuFwAu+8/7Iq31Yq11z2BJGgd/dqbSWm/QWj+qlLJhvJg9oi4ARwh5TD2HsHlQQAIHGgTrlVJpjteaR/zcehp4DmmeUpdDDrDEEdxpWusNrnpibwhw2xFfx5lRhLdx/OHXHPHC9gRZQL7ZRRzB0/7ge8QCNUopj2lNAukYf4M1SqlnHW/IHkMptcDTPhlrreswPhEsAWa68rm9IcDrMF7IYmDma61vNruIIzneUNI9pf9UKZXtgW9ywIFPU3VAnaf8vBzyHXXlAh7RRXeIs8wu4EiOLpQVWut0XPy79IYAX8/BVnga4FGDhp5IKTW/p9/Ulf1tzlBKPXJIf3wdnvOmXOMYXJoPpHnQz2uBp9RyhPWHXLdh/C49gqd9GjhE5iHdJn/Eha99jw9wx8ehNMe7mM2TWkuOmrI8qXXkqOmRntF4PCcon8UYwOn5PXrEYgtH3/wKjJ+TzeRyDvU2HDaI7xHdAo46bD2TCTzl9+gQC9SYXUQvFjvekLOBy135M5OFPEII4aU8vgUuhBCidxLgQgjhpSTAhRDCS0mACyGEl5IAF0IILyUBLoQQXkoCXAghvJQEuBBCeKn/Dwpa8gPdkyTNAAAAAElFTkSuQmCC",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "\n",
+ "A = np.concatenate((np.ones(1), lpcs[frame_idx]))\n",
+ "A_num, A_den = stp(A)\n",
+ "w, h = scipy.signal.freqz([1], A, fs=16000)\n",
+ "w, h_stp = scipy.signal.freqz(A_num, A_den, fs=16000)\n",
+ "plt.plot(w/1000, 10*np.log10(np.abs(h)))\n",
+ "plt.plot(w/1000, 10*np.log10(np.abs(h_stp)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "x_frame = x_preemph[frame_idx * 160 - 80: (frame_idx + 1) * 160 + 80]\n",
+ "\n",
+ "window = scipy.signal.get_window('hamming', 320)\n",
+ "spec = np.fft.fft(x_frame, n=1024)\n",
+ "\n",
+ "log_mag = 10*np.log10(np.abs(spec[:512]) + 1e-6)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 648x432 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig = plt.figure(figsize=(9, 6))\n",
+ "\n",
+ "plt.plot(w/1000, log_mag - log_mag.mean(), alpha=0.7, label='spectrum')\n",
+ "plt.plot(w/1000, 10*np.log10(np.abs(h)), \"r--\", label='LPC spectrum', linewidth=3, alpha=0.9)\n",
+ "plt.plot(w/1000, 10*np.log10(np.abs(h_stp)), \"k--\", label='short-term postfilter', linewidth=3, alpha=0.9)\n",
+ "plt.legend()\n",
+ "plt.xlabel('kHz')\n",
+ "plt.ylabel('Amplitude (dB)')\n",
+ "plt.show()\n",
+ "fig.savefig('plots/short_term_postfilter.png')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "periods[frame_idx]\n",
+ "\n",
+ "p = int(periods[frame_idx])\n",
+ "ltp_num = np.zeros(p+1)\n",
+ "ltp_den = np.zeros(p+1)\n",
+ "\n",
+ "ltp_num[0] = ltp_den[0] = 1\n",
+ "ltp_num[p] = 0.25\n",
+ "ltp_den[p] = -0.25\n",
+ "\n",
+ "w, h_ltp = scipy.signal.freqz(ltp_num, ltp_den, fs=16000)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 648x432 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig = plt.figure(figsize=(9, 6))\n",
+ "plt.plot(w/1000, log_mag - log_mag.mean(), alpha=0.7, label='spectrum')\n",
+ "plt.plot(w/1000, 10*np.log10(np.abs(h_ltp)), \"k\", alpha=1, label='long-term postfilter', linewidth=1.5)\n",
+ "plt.legend()\n",
+ "plt.xlabel('kHz')\n",
+ "plt.ylabel('Amplitude (dB)')\n",
+ "plt.show()\n",
+ "fig.savefig('plots/long_term_postfilter.png')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "torch",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/dnn/torch/osce/stndrd/presentation/spectrogram.ipynb b/dnn/torch/osce/stndrd/presentation/spectrogram.ipynb
new file mode 100644
index 00000000..9d3d96f1
--- /dev/null
+++ b/dnn/torch/osce/stndrd/presentation/spectrogram.ipynb
@@ -0,0 +1,173 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "04cba77c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/tmp/ipykernel_252797/4199785080.py:9: WavFileWarning: Chunk (non-data) not understood, skipping it.\n",
+ " fs, x = wavfile.read(f'data/a3_short_opus_{br}bps.wav')\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 513.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 513.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 513.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 513.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 513.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 513.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 513.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "<Figure size 513.2x360 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "from scipy.io import wavfile\n",
+ "import scipy.signal\n",
+ "from playback import make_playback_animation\n",
+ "\n",
+ "# coding noise demonstration\n",
+ "for br in [\"inf\", 24000, 15000, 12000, 9000, 8000, 7000, 6000]:\n",
+ " fs, x = wavfile.read(f'data/a3_short_opus_{br}bps.wav')\n",
+ " spec, freqs, t, im = plt.specgram(x, NFFT=512, cmap='inferno', noverlap=256 + 128, pad_to=4096)\n",
+ " spec = 10*np.log10(spec)\n",
+ " \n",
+ " make_playback_animation(f'animations/opus_{br}bps.mp4', spec, len(x)/16)\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b8408ca1",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/dnn/torch/osce/test_model.py b/dnn/torch/osce/test_model.py
new file mode 100644
index 00000000..616a0ec5
--- /dev/null
+++ b/dnn/torch/osce/test_model.py
@@ -0,0 +1,96 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import argparse
+
+import torch
+
+from scipy.io import wavfile
+
+
+from models import model_dict
+from utils.silk_features import load_inference_data
+from utils import endoscopy
+
+debug = False
+if debug:
+ args = type('dummy', (object,),
+ {
+ 'input' : 'testitems/all_0_orig.se',
+ 'checkpoint' : 'testout/checkpoints/checkpoint_epoch_5.pth',
+ 'output' : 'out.wav',
+ })()
+else:
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument('input', type=str, help='path to folder with features and signals')
+ parser.add_argument('checkpoint', type=str, help='checkpoint file')
+ parser.add_argument('output', type=str, help='output file')
+ parser.add_argument('--debug', action='store_true', help='enables debug output')
+
+
+ args = parser.parse_args()
+
+
+torch.set_num_threads(2)
+
+input_folder = args.input
+checkpoint_file = args.checkpoint
+
+
+output_file = args.output
+if not output_file.endswith('.wav'):
+ output_file += '.wav'
+
+checkpoint = torch.load(checkpoint_file, map_location="cpu")
+
+# check model
+if not 'name' in checkpoint['setup']['model']:
+ print(f'warning: did not find model name entry in setup, using pitchpostfilter per default')
+ model_name = 'pitchpostfilter'
+else:
+ model_name = checkpoint['setup']['model']['name']
+
+model = model_dict[model_name](*checkpoint['setup']['model']['args'], **checkpoint['setup']['model']['kwargs'])
+
+model.load_state_dict(checkpoint['state_dict'])
+
+# generate model input
+setup = checkpoint['setup']
+signal, features, periods, numbits = load_inference_data(input_folder, **setup['data'])
+
+if args.debug:
+ endoscopy.init()
+
+output = model.process(signal, features, periods, numbits, debug=args.debug)
+
+wavfile.write(output_file, 16000, output.cpu().numpy())
+
+if args.debug:
+ endoscopy.close()
diff --git a/dnn/torch/osce/test_vocoder.py b/dnn/torch/osce/test_vocoder.py
new file mode 100644
index 00000000..e71a5c37
--- /dev/null
+++ b/dnn/torch/osce/test_vocoder.py
@@ -0,0 +1,103 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import argparse
+
+import torch
+
+from scipy.io import wavfile
+
+from time import time
+
+
+from models import model_dict
+from utils.lpcnet_features import load_lpcnet_features
+from utils import endoscopy
+
+debug = False
+if debug:
+ args = type('dummy', (object,),
+ {
+ 'input' : 'testitems/all_0_orig.se',
+ 'checkpoint' : 'testout/checkpoints/checkpoint_epoch_5.pth',
+ 'output' : 'out.wav',
+ })()
+else:
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument('input', type=str, help='path to input features')
+ parser.add_argument('checkpoint', type=str, help='checkpoint file')
+ parser.add_argument('output', type=str, help='output file')
+ parser.add_argument('--debug', action='store_true', help='enables debug output')
+
+
+ args = parser.parse_args()
+
+
+torch.set_num_threads(2)
+
+input_folder = args.input
+checkpoint_file = args.checkpoint
+
+
+output_file = args.output
+if not output_file.endswith('.wav'):
+ output_file += '.wav'
+
+checkpoint = torch.load(checkpoint_file, map_location="cpu")
+
+# check model
+if not 'name' in checkpoint['setup']['model']:
+ print(f'warning: did not find model name entry in setup, using pitchpostfilter per default')
+ model_name = 'pitchpostfilter'
+else:
+ model_name = checkpoint['setup']['model']['name']
+
+model = model_dict[model_name](*checkpoint['setup']['model']['args'], **checkpoint['setup']['model']['kwargs'])
+
+model.load_state_dict(checkpoint['state_dict'])
+
+# generate model input
+setup = checkpoint['setup']
+testdata = load_lpcnet_features(input_folder)
+features = testdata['features']
+periods = testdata['periods']
+
+if args.debug:
+ endoscopy.init()
+
+start = time()
+output = model.process(features, periods, debug=args.debug)
+elapsed = time() - start
+print(f"[timing] inference took {elapsed * 1000} ms")
+
+wavfile.write(output_file, 16000, output.cpu().numpy())
+
+if args.debug:
+ endoscopy.close()
diff --git a/dnn/torch/osce/train_model.py b/dnn/torch/osce/train_model.py
new file mode 100644
index 00000000..e8f94dcc
--- /dev/null
+++ b/dnn/torch/osce/train_model.py
@@ -0,0 +1,307 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+seed=1888
+
+import os
+import argparse
+import sys
+import random
+random.seed(seed)
+
+import yaml
+
+try:
+ import git
+ has_git = True
+except:
+ has_git = False
+
+import torch
+torch.manual_seed(seed)
+torch.backends.cudnn.benchmark = False
+from torch.optim.lr_scheduler import LambdaLR
+
+import numpy as np
+np.random.seed(seed)
+
+from scipy.io import wavfile
+
+import pesq
+
+from data import SilkEnhancementSet
+from models import model_dict
+from engine.engine import train_one_epoch, evaluate
+
+
+from utils.silk_features import load_inference_data
+from utils.misc import count_parameters, count_nonzero_parameters
+
+from losses.stft_loss import MRSTFTLoss, MRLogMelLoss
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('setup', type=str, help='setup yaml file')
+parser.add_argument('output', type=str, help='output path')
+parser.add_argument('--device', type=str, help='compute device', default=None)
+parser.add_argument('--initial-checkpoint', type=str, help='initial checkpoint', default=None)
+parser.add_argument('--testdata', type=str, help='path to features and signal for testing', default=None)
+parser.add_argument('--no-redirect', action='store_true', help='disables re-direction of stdout')
+
+args = parser.parse_args()
+
+
+
+torch.set_num_threads(4)
+
+with open(args.setup, 'r') as f:
+ setup = yaml.load(f.read(), yaml.FullLoader)
+
+checkpoint_prefix = 'checkpoint'
+output_prefix = 'output'
+setup_name = 'setup.yml'
+output_file='out.txt'
+
+
+# check model
+if not 'name' in setup['model']:
+ print(f'warning: did not find model entry in setup, using default PitchPostFilter')
+ model_name = 'pitchpostfilter'
+else:
+ model_name = setup['model']['name']
+
+# prepare output folder
+if os.path.exists(args.output):
+ print("warning: output folder exists")
+
+ reply = input('continue? (y/n): ')
+ while reply not in {'y', 'n'}:
+ reply = input('continue? (y/n): ')
+
+ if reply == 'n':
+ os._exit(0)
+else:
+ os.makedirs(args.output, exist_ok=True)
+
+checkpoint_dir = os.path.join(args.output, 'checkpoints')
+os.makedirs(checkpoint_dir, exist_ok=True)
+
+# add repo info to setup
+if has_git:
+ working_dir = os.path.split(__file__)[0]
+ try:
+ repo = git.Repo(working_dir, search_parent_directories=True)
+ setup['repo'] = dict()
+ hash = repo.head.object.hexsha
+ urls = list(repo.remote().urls)
+ is_dirty = repo.is_dirty()
+
+ if is_dirty:
+ print("warning: repo is dirty")
+ with open(os.path.join(args.output, 'repo.diff'), "w") as f:
+ f.write(repo.git.execute(["git", "diff"]))
+
+ setup['repo']['hash'] = hash
+ setup['repo']['urls'] = urls
+ setup['repo']['dirty'] = is_dirty
+ except:
+ has_git = False
+
+# dump setup
+with open(os.path.join(args.output, setup_name), 'w') as f:
+ yaml.dump(setup, f)
+
+ref = None
+if args.testdata is not None:
+
+ testsignal, features, periods, numbits = load_inference_data(args.testdata, **setup['data'])
+
+ inference_test = True
+ inference_folder = os.path.join(args.output, 'inference_test')
+ os.makedirs(os.path.join(args.output, 'inference_test'), exist_ok=True)
+
+ try:
+ ref = np.fromfile(os.path.join(args.testdata, 'clean.s16'), dtype=np.int16)
+ except:
+ pass
+else:
+ inference_test = False
+
+# training parameters
+batch_size = setup['training']['batch_size']
+epochs = setup['training']['epochs']
+lr = setup['training']['lr']
+lr_decay_factor = setup['training']['lr_decay_factor']
+
+# load training dataset
+data_config = setup['data']
+data = SilkEnhancementSet(setup['dataset'], **data_config)
+
+# load validation dataset if given
+if 'validation_dataset' in setup:
+ validation_data = SilkEnhancementSet(setup['validation_dataset'], **data_config)
+
+ validation_dataloader = torch.utils.data.DataLoader(validation_data, batch_size=batch_size, drop_last=True, num_workers=8)
+
+ run_validation = True
+else:
+ run_validation = False
+
+# create model
+model = model_dict[model_name](*setup['model']['args'], **setup['model']['kwargs'])
+
+if args.initial_checkpoint is not None:
+ print(f"loading state dict from {args.initial_checkpoint}...")
+ chkpt = torch.load(args.initial_checkpoint, map_location='cpu')
+ model.load_state_dict(chkpt['state_dict'])
+
+# set compute device
+if type(args.device) == type(None):
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+else:
+ device = torch.device(args.device)
+
+# push model to device
+model.to(device)
+
+# dataloader
+dataloader = torch.utils.data.DataLoader(data, batch_size=batch_size, drop_last=True, shuffle=True, num_workers=8)
+
+# optimizer is introduced to trainable parameters
+parameters = [p for p in model.parameters() if p.requires_grad]
+optimizer = torch.optim.Adam(parameters, lr=lr)
+
+# learning rate scheduler
+scheduler = LambdaLR(optimizer=optimizer, lr_lambda=lambda x : 1 / (1 + lr_decay_factor * x))
+
+# loss
+w_l1 = setup['training']['loss']['w_l1']
+w_lm = setup['training']['loss']['w_lm']
+w_slm = setup['training']['loss']['w_slm']
+w_sc = setup['training']['loss']['w_sc']
+w_logmel = setup['training']['loss']['w_logmel']
+w_wsc = setup['training']['loss']['w_wsc']
+w_xcorr = setup['training']['loss']['w_xcorr']
+w_sxcorr = setup['training']['loss']['w_sxcorr']
+w_l2 = setup['training']['loss']['w_l2']
+
+w_sum = w_l1 + w_lm + w_sc + w_logmel + w_wsc + w_slm + w_xcorr + w_sxcorr + w_l2
+
+stftloss = MRSTFTLoss(sc_weight=w_sc, log_mag_weight=w_lm, wsc_weight=w_wsc, smooth_log_mag_weight=w_slm, sxcorr_weight=w_sxcorr).to(device)
+logmelloss = MRLogMelLoss().to(device)
+
+def xcorr_loss(y_true, y_pred):
+ dims = list(range(1, len(y_true.shape)))
+
+ loss = 1 - torch.sum(y_true * y_pred, dim=dims) / torch.sqrt(torch.sum(y_true ** 2, dim=dims) * torch.sum(y_pred ** 2, dim=dims) + 1e-9)
+
+ return torch.mean(loss)
+
+def td_l2_norm(y_true, y_pred):
+ dims = list(range(1, len(y_true.shape)))
+
+ loss = torch.mean((y_true - y_pred) ** 2, dim=dims) / (torch.mean(y_pred ** 2, dim=dims) ** .5 + 1e-6)
+
+ return loss.mean()
+
+def td_l1(y_true, y_pred, pow=0):
+ dims = list(range(1, len(y_true.shape)))
+ tmp = torch.mean(torch.abs(y_true - y_pred), dim=dims) / ((torch.mean(torch.abs(y_pred), dim=dims) + 1e-9) ** pow)
+
+ return torch.mean(tmp)
+
+def criterion(x, y):
+
+ return (w_l1 * td_l1(x, y, pow=1) + stftloss(x, y) + w_logmel * logmelloss(x, y)
+ + w_xcorr * xcorr_loss(x, y) + w_l2 * td_l2_norm(x, y)) / w_sum
+
+
+
+# model checkpoint
+checkpoint = {
+ 'setup' : setup,
+ 'state_dict' : model.state_dict(),
+ 'loss' : -1
+}
+
+
+
+
+if not args.no_redirect:
+ print(f"re-directing output to {os.path.join(args.output, output_file)}")
+ sys.stdout = open(os.path.join(args.output, output_file), "w")
+
+print("summary:")
+
+print(f"{count_parameters(model.cpu()) / 1e6:5.3f} M parameters")
+if hasattr(model, 'flop_count'):
+ print(f"{model.flop_count(16000) / 1e6:5.3f} MFLOPS")
+
+if ref is not None:
+ noisy = np.fromfile(os.path.join(args.testdata, 'noisy.s16'), dtype=np.int16)
+ initial_mos = pesq.pesq(16000, ref, noisy, mode='wb')
+ print(f"initial MOS (PESQ): {initial_mos}")
+
+best_loss = 1e9
+
+for ep in range(1, epochs + 1):
+ print(f"training epoch {ep}...")
+ new_loss = train_one_epoch(model, criterion, optimizer, dataloader, device, scheduler)
+
+
+ # save checkpoint
+ checkpoint['state_dict'] = model.state_dict()
+ checkpoint['loss'] = new_loss
+
+ if run_validation:
+ print("running validation...")
+ validation_loss = evaluate(model, criterion, validation_dataloader, device)
+ checkpoint['validation_loss'] = validation_loss
+
+ if validation_loss < best_loss:
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_best.pth'))
+ best_loss = validation_loss
+
+ if inference_test:
+ print("running inference test...")
+ out = model.process(testsignal, features, periods, numbits).cpu().numpy()
+ wavfile.write(os.path.join(inference_folder, f'{model_name}_epoch_{ep}.wav'), 16000, out)
+ if ref is not None:
+ mos = pesq.pesq(16000, ref, out, mode='wb')
+ print(f"MOS (PESQ): {mos}")
+
+
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_epoch_{ep}.pth'))
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_last.pth'))
+
+
+ print(f"non-zero parameters: {count_nonzero_parameters(model)}\n")
+
+print('Done')
diff --git a/dnn/torch/osce/train_vocoder.py b/dnn/torch/osce/train_vocoder.py
new file mode 100644
index 00000000..590e6d1a
--- /dev/null
+++ b/dnn/torch/osce/train_vocoder.py
@@ -0,0 +1,287 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+import sys
+
+import yaml
+
+try:
+ import git
+ has_git = True
+except:
+ has_git = False
+
+import torch
+from torch.optim.lr_scheduler import LambdaLR
+
+from scipy.io import wavfile
+
+import pesq
+
+from data import LPCNetVocodingDataset
+from models import model_dict
+from engine.vocoder_engine import train_one_epoch, evaluate
+
+
+from utils.lpcnet_features import load_lpcnet_features
+from utils.misc import count_parameters
+
+from losses.stft_loss import MRSTFTLoss, MRLogMelLoss
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('setup', type=str, help='setup yaml file')
+parser.add_argument('output', type=str, help='output path')
+parser.add_argument('--device', type=str, help='compute device', default=None)
+parser.add_argument('--initial-checkpoint', type=str, help='initial checkpoint', default=None)
+parser.add_argument('--test-features', type=str, help='path to features for testing', default=None)
+parser.add_argument('--no-redirect', action='store_true', help='disables re-direction of stdout')
+
+args = parser.parse_args()
+
+
+torch.set_num_threads(4)
+
+with open(args.setup, 'r') as f:
+ setup = yaml.load(f.read(), yaml.FullLoader)
+
+checkpoint_prefix = 'checkpoint'
+output_prefix = 'output'
+setup_name = 'setup.yml'
+output_file='out.txt'
+
+
+# check model
+if not 'name' in setup['model']:
+ print(f'warning: did not find model entry in setup, using default PitchPostFilter')
+ model_name = 'pitchpostfilter'
+else:
+ model_name = setup['model']['name']
+
+# prepare output folder
+if os.path.exists(args.output):
+ print("warning: output folder exists")
+
+ reply = input('continue? (y/n): ')
+ while reply not in {'y', 'n'}:
+ reply = input('continue? (y/n): ')
+
+ if reply == 'n':
+ os._exit()
+else:
+ os.makedirs(args.output, exist_ok=True)
+
+checkpoint_dir = os.path.join(args.output, 'checkpoints')
+os.makedirs(checkpoint_dir, exist_ok=True)
+
+# add repo info to setup
+if has_git:
+ working_dir = os.path.split(__file__)[0]
+ try:
+ repo = git.Repo(working_dir, search_parent_directories=True)
+ setup['repo'] = dict()
+ hash = repo.head.object.hexsha
+ urls = list(repo.remote().urls)
+ is_dirty = repo.is_dirty()
+
+ if is_dirty:
+ print("warning: repo is dirty")
+
+ setup['repo']['hash'] = hash
+ setup['repo']['urls'] = urls
+ setup['repo']['dirty'] = is_dirty
+ except:
+ has_git = False
+
+# dump setup
+with open(os.path.join(args.output, setup_name), 'w') as f:
+ yaml.dump(setup, f)
+
+ref = None
+# prepare inference test if wanted
+inference_test = False
+if type(args.test_features) != type(None):
+ test_features = load_lpcnet_features(args.test_features)
+ features = test_features['features']
+ periods = test_features['periods']
+ inference_folder = os.path.join(args.output, 'inference_test')
+ os.makedirs(inference_folder, exist_ok=True)
+ inference_test = True
+
+
+# training parameters
+batch_size = setup['training']['batch_size']
+epochs = setup['training']['epochs']
+lr = setup['training']['lr']
+lr_decay_factor = setup['training']['lr_decay_factor']
+
+# load training dataset
+data_config = setup['data']
+data = LPCNetVocodingDataset(setup['dataset'], **data_config)
+
+# load validation dataset if given
+if 'validation_dataset' in setup:
+ validation_data = LPCNetVocodingDataset(setup['validation_dataset'], **data_config)
+
+ validation_dataloader = torch.utils.data.DataLoader(validation_data, batch_size=batch_size, drop_last=True, num_workers=8)
+
+ run_validation = True
+else:
+ run_validation = False
+
+# create model
+model = model_dict[model_name](*setup['model']['args'], **setup['model']['kwargs'])
+
+if args.initial_checkpoint is not None:
+ print(f"loading state dict from {args.initial_checkpoint}...")
+ chkpt = torch.load(args.initial_checkpoint, map_location='cpu')
+ model.load_state_dict(chkpt['state_dict'])
+
+# set compute device
+if type(args.device) == type(None):
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+else:
+ device = torch.device(args.device)
+
+# push model to device
+model.to(device)
+
+# dataloader
+dataloader = torch.utils.data.DataLoader(data, batch_size=batch_size, drop_last=True, shuffle=True, num_workers=8)
+
+# optimizer is introduced to trainable parameters
+parameters = [p for p in model.parameters() if p.requires_grad]
+optimizer = torch.optim.Adam(parameters, lr=lr)
+
+# learning rate scheduler
+scheduler = LambdaLR(optimizer=optimizer, lr_lambda=lambda x : 1 / (1 + lr_decay_factor * x))
+
+# loss
+w_l1 = setup['training']['loss']['w_l1']
+w_lm = setup['training']['loss']['w_lm']
+w_slm = setup['training']['loss']['w_slm']
+w_sc = setup['training']['loss']['w_sc']
+w_logmel = setup['training']['loss']['w_logmel']
+w_wsc = setup['training']['loss']['w_wsc']
+w_xcorr = setup['training']['loss']['w_xcorr']
+w_sxcorr = setup['training']['loss']['w_sxcorr']
+w_l2 = setup['training']['loss']['w_l2']
+
+w_sum = w_l1 + w_lm + w_sc + w_logmel + w_wsc + w_slm + w_xcorr + w_sxcorr + w_l2
+
+stftloss = MRSTFTLoss(sc_weight=w_sc, log_mag_weight=w_lm, wsc_weight=w_wsc, smooth_log_mag_weight=w_slm, sxcorr_weight=w_sxcorr).to(device)
+logmelloss = MRLogMelLoss().to(device)
+
+def xcorr_loss(y_true, y_pred):
+ dims = list(range(1, len(y_true.shape)))
+
+ loss = 1 - torch.sum(y_true * y_pred, dim=dims) / torch.sqrt(torch.sum(y_true ** 2, dim=dims) * torch.sum(y_pred ** 2, dim=dims) + 1e-9)
+
+ return torch.mean(loss)
+
+def td_l2_norm(y_true, y_pred):
+ dims = list(range(1, len(y_true.shape)))
+
+ loss = torch.mean((y_true - y_pred) ** 2, dim=dims) / (torch.mean(y_pred ** 2, dim=dims) ** .5 + 1e-6)
+
+ return loss.mean()
+
+def td_l1(y_true, y_pred, pow=0):
+ dims = list(range(1, len(y_true.shape)))
+ tmp = torch.mean(torch.abs(y_true - y_pred), dim=dims) / ((torch.mean(torch.abs(y_pred), dim=dims) + 1e-9) ** pow)
+
+ return torch.mean(tmp)
+
+def criterion(x, y):
+
+ return (w_l1 * td_l1(x, y, pow=1) + stftloss(x, y) + w_logmel * logmelloss(x, y)
+ + w_xcorr * xcorr_loss(x, y) + w_l2 * td_l2_norm(x, y)) / w_sum
+
+
+
+# model checkpoint
+checkpoint = {
+ 'setup' : setup,
+ 'state_dict' : model.state_dict(),
+ 'loss' : -1
+}
+
+
+if not args.no_redirect:
+ print(f"re-directing output to {os.path.join(args.output, output_file)}")
+ sys.stdout = open(os.path.join(args.output, output_file), "w")
+
+print("summary:")
+
+print(f"{count_parameters(model.cpu()) / 1e6:5.3f} M parameters")
+if hasattr(model, 'flop_count'):
+ print(f"{model.flop_count(16000) / 1e6:5.3f} MFLOPS")
+
+if ref is not None:
+ pass
+
+best_loss = 1e9
+
+for ep in range(1, epochs + 1):
+ print(f"training epoch {ep}...")
+ new_loss = train_one_epoch(model, criterion, optimizer, dataloader, device, scheduler)
+
+
+ # save checkpoint
+ checkpoint['state_dict'] = model.state_dict()
+ checkpoint['loss'] = new_loss
+
+ if run_validation:
+ print("running validation...")
+ validation_loss = evaluate(model, criterion, validation_dataloader, device)
+ checkpoint['validation_loss'] = validation_loss
+
+ if validation_loss < best_loss:
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_best.pth'))
+ best_loss = validation_loss
+
+ if inference_test:
+ print("running inference test...")
+ out = model.process(features, periods).cpu().numpy()
+ wavfile.write(os.path.join(inference_folder, f'{model_name}_epoch_{ep}.wav'), 16000, out)
+ if ref is not None:
+ mos = pesq.pesq(16000, ref, out, mode='wb')
+ print(f"MOS (PESQ): {mos}")
+
+
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_epoch_{ep}.pth'))
+ torch.save(checkpoint, os.path.join(checkpoint_dir, checkpoint_prefix + f'_last.pth'))
+
+
+ print()
+
+print('Done')
diff --git a/dnn/torch/osce/utils/ada_conv.py b/dnn/torch/osce/utils/ada_conv.py
new file mode 100644
index 00000000..b5b93f87
--- /dev/null
+++ b/dnn/torch/osce/utils/ada_conv.py
@@ -0,0 +1,71 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jean-Marc Valin */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+# x is (batch, nb_in_channels, nb_frames*frame_size)
+# kernels is (batch, nb_out_channels, nb_in_channels, nb_frames, coeffs)
+def adaconv_kernel(x, kernels, half_window, fft_size=256):
+ device=x.device
+ overlap_size=half_window.size(-1)
+ nb_frames=kernels.size(3)
+ nb_batches=kernels.size(0)
+ nb_out_channels=kernels.size(1)
+ nb_in_channels=kernels.size(2)
+ kernel_size = kernels.size(-1)
+ x = x.reshape(nb_batches, 1, nb_in_channels, nb_frames, -1)
+ frame_size = x.size(-1)
+ # build window: [zeros, rising window, ones, falling window, zeros]
+ window = torch.cat(
+ [
+ torch.zeros(frame_size, device=device),
+ half_window,
+ torch.ones(frame_size - overlap_size, device=device),
+ 1 - half_window,
+ torch.zeros(fft_size - 2 * frame_size - overlap_size,device=device)
+ ])
+ x_prev = torch.cat([torch.zeros_like(x[:, :, :, :1, :]), x[:, :, :, :-1, :]], dim=-2)
+ x_next = torch.cat([x[:, :, :, 1:, :overlap_size], torch.zeros_like(x[:, :, :, -1:, :overlap_size])], dim=-2)
+ x_padded = torch.cat([x_prev, x, x_next, torch.zeros(nb_batches, 1, nb_in_channels, nb_frames, fft_size - 2 * frame_size - overlap_size, device=device)], -1)
+ k_padded = torch.cat([torch.flip(kernels, [-1]), torch.zeros(nb_batches, nb_out_channels, nb_in_channels, nb_frames, fft_size-kernel_size, device=device)], dim=-1)
+
+ # compute convolution
+ X = torch.fft.rfft(x_padded, dim=-1)
+ K = torch.fft.rfft(k_padded, dim=-1)
+
+ out = torch.fft.irfft(X * K, dim=-1)
+ # combine in channels
+ out = torch.sum(out, dim=2)
+ # apply the cross-fading
+ out = window.reshape(1, 1, 1, -1)*out
+ crossfaded = out[:,:,:,frame_size:2*frame_size] + torch.cat([torch.zeros(nb_batches, nb_out_channels, 1, frame_size, device=device), out[:, :, :-1, 2*frame_size:3*frame_size]], dim=-2)
+
+ return crossfaded.reshape(nb_batches, nb_out_channels, -1) \ No newline at end of file
diff --git a/dnn/torch/osce/utils/complexity.py b/dnn/torch/osce/utils/complexity.py
new file mode 100644
index 00000000..4ee6e3f3
--- /dev/null
+++ b/dnn/torch/osce/utils/complexity.py
@@ -0,0 +1,8 @@
+
+
+def _conv1d_flop_count(layer, rate):
+ return 2 * ((layer.in_channels + 1) * layer.out_channels * rate / layer.stride[0] ) * layer.kernel_size[0]
+
+
+def _dense_flop_count(layer, rate):
+ return 2 * ((layer.in_features + 1) * layer.out_features * rate ) \ No newline at end of file
diff --git a/dnn/torch/osce/utils/endoscopy.py b/dnn/torch/osce/utils/endoscopy.py
new file mode 100644
index 00000000..05dd4750
--- /dev/null
+++ b/dnn/torch/osce/utils/endoscopy.py
@@ -0,0 +1,205 @@
+""" module for inspecting models during inference """
+
+import os
+
+import yaml
+import matplotlib.pyplot as plt
+import matplotlib.animation as animation
+
+import torch
+import numpy as np
+
+# stores entries {key : {'fid' : fid, 'fs' : fs, 'dim' : dim, 'dtype' : dtype}}
+_state = dict()
+_folder = 'endoscopy'
+
+def get_gru_gates(gru, input, state):
+ hidden_size = gru.hidden_size
+
+ direct = torch.matmul(gru.weight_ih_l0, input.squeeze())
+ recurrent = torch.matmul(gru.weight_hh_l0, state.squeeze())
+
+ # reset gate
+ start, stop = 0 * hidden_size, 1 * hidden_size
+ reset_gate = torch.sigmoid(direct[start : stop] + gru.bias_ih_l0[start : stop] + recurrent[start : stop] + gru.bias_hh_l0[start : stop])
+
+ # update gate
+ start, stop = 1 * hidden_size, 2 * hidden_size
+ update_gate = torch.sigmoid(direct[start : stop] + gru.bias_ih_l0[start : stop] + recurrent[start : stop] + gru.bias_hh_l0[start : stop])
+
+ # new gate
+ start, stop = 2 * hidden_size, 3 * hidden_size
+ new_gate = torch.tanh(direct[start : stop] + gru.bias_ih_l0[start : stop] + reset_gate * (recurrent[start : stop] + gru.bias_hh_l0[start : stop]))
+
+ return {'reset_gate' : reset_gate, 'update_gate' : update_gate, 'new_gate' : new_gate}
+
+
+def init(folder='endoscopy'):
+ """ sets up output folder for endoscopy data """
+
+ global _folder
+ _folder = folder
+
+ if not os.path.exists(folder):
+ os.makedirs(folder)
+ else:
+ print(f"warning: endoscopy folder {folder} exists. Content may be lost or inconsistent results may occur.")
+
+def write_data(key, data, fs):
+ """ appends data to previous data written under key """
+
+ global _state
+
+ # convert to numpy if torch.Tensor is given
+ if isinstance(data, torch.Tensor):
+ data = data.detach().numpy()
+
+ if not key in _state:
+ _state[key] = {
+ 'fid' : open(os.path.join(_folder, key + '.bin'), 'wb'),
+ 'fs' : fs,
+ 'dim' : tuple(data.shape),
+ 'dtype' : str(data.dtype)
+ }
+
+ with open(os.path.join(_folder, key + '.yml'), 'w') as f:
+ f.write(yaml.dump({'fs' : fs, 'dim' : tuple(data.shape), 'dtype' : str(data.dtype).split('.')[-1]}))
+ else:
+ if _state[key]['fs'] != fs:
+ raise ValueError(f"fs changed for key {key}: {_state[key]['fs']} vs. {fs}")
+ if _state[key]['dtype'] != str(data.dtype):
+ raise ValueError(f"dtype changed for key {key}: {_state[key]['dtype']} vs. {str(data.dtype)}")
+ if _state[key]['dim'] != tuple(data.shape):
+ raise ValueError(f"dim changed for key {key}: {_state[key]['dim']} vs. {tuple(data.shape)}")
+
+ _state[key]['fid'].write(data.tobytes())
+
+def close(folder='endoscopy'):
+ """ clean up """
+ for key in _state.keys():
+ _state[key]['fid'].close()
+
+
+def read_data(folder='endoscopy'):
+ """ retrieves written data as numpy arrays """
+
+
+ keys = [name[:-4] for name in os.listdir(folder) if name.endswith('.yml')]
+
+ return_dict = dict()
+
+ for key in keys:
+ with open(os.path.join(folder, key + '.yml'), 'r') as f:
+ value = yaml.load(f.read(), yaml.FullLoader)
+
+ with open(os.path.join(folder, key + '.bin'), 'rb') as f:
+ data = np.frombuffer(f.read(), dtype=value['dtype'])
+
+ value['data'] = data.reshape((-1,) + value['dim'])
+
+ return_dict[key] = value
+
+ return return_dict
+
+def get_best_reshape(shape, target_ratio=1):
+ """ calculated the best 2d reshape of shape given the target ratio (rows/cols)"""
+
+ if len(shape) > 1:
+ pixel_count = 1
+ for s in shape:
+ pixel_count *= s
+ else:
+ pixel_count = shape[0]
+
+ if pixel_count == 1:
+ return (1,)
+
+ num_columns = int((pixel_count / target_ratio)**.5)
+
+ while (pixel_count % num_columns):
+ num_columns -= 1
+
+ num_rows = pixel_count // num_columns
+
+ return (num_rows, num_columns)
+
+def get_type_and_shape(shape):
+
+ # can happen if data is one dimensional
+ if len(shape) == 0:
+ shape = (1,)
+
+ # calculate pixel count
+ if len(shape) > 1:
+ pixel_count = 1
+ for s in shape:
+ pixel_count *= s
+ else:
+ pixel_count = shape[0]
+
+ if pixel_count == 1:
+ return 'plot', (1, )
+
+ # stay with shape if already 2-dimensional
+ if len(shape) == 2:
+ if (shape[0] != pixel_count) or (shape[1] != pixel_count):
+ return 'image', shape
+
+ return 'image', get_best_reshape(shape)
+
+def make_animation(data, filename, start_index=80, stop_index=-80, interval=20, half_signal_window_length=80):
+
+ # determine plot setup
+ num_keys = len(data.keys())
+
+ num_rows = int((num_keys * 3/4) ** .5)
+
+ num_cols = (num_keys + num_rows - 1) // num_rows
+
+ fig, axs = plt.subplots(num_rows, num_cols)
+ fig.set_size_inches(num_cols * 5, num_rows * 5)
+
+ display = dict()
+
+ fs_max = max([val['fs'] for val in data.values()])
+
+ num_samples = max([val['data'].shape[0] for val in data.values()])
+
+ keys = sorted(data.keys())
+
+ # inspect data
+ for i, key in enumerate(keys):
+ axs[i // num_cols, i % num_cols].title.set_text(key)
+
+ display[key] = dict()
+
+ display[key]['type'], display[key]['shape'] = get_type_and_shape(data[key]['dim'])
+ display[key]['down_factor'] = data[key]['fs'] / fs_max
+
+ start_index = max(start_index, half_signal_window_length)
+ while stop_index < 0:
+ stop_index += num_samples
+
+ stop_index = min(stop_index, num_samples - half_signal_window_length)
+
+ # actual plotting
+ frames = []
+ for index in range(start_index, stop_index):
+ ims = []
+ for i, key in enumerate(keys):
+ feature_index = int(round(index * display[key]['down_factor']))
+
+ if display[key]['type'] == 'plot':
+ ims.append(axs[i // num_cols, i % num_cols].plot(data[key]['data'][index - half_signal_window_length : index + half_signal_window_length], marker='P', markevery=[half_signal_window_length], animated=True, color='blue')[0])
+
+ elif display[key]['type'] == 'image':
+ ims.append(axs[i // num_cols, i % num_cols].imshow(data[key]['data'][index].reshape(display[key]['shape']), animated=True))
+
+ frames.append(ims)
+
+ ani = animation.ArtistAnimation(fig, frames, interval=interval, blit=True, repeat_delay=1000)
+
+ if not filename.endswith('.mp4'):
+ filename += '.mp4'
+
+ ani.save(filename) \ No newline at end of file
diff --git a/dnn/torch/osce/utils/layers/limited_adaptive_comb1d.py b/dnn/torch/osce/utils/layers/limited_adaptive_comb1d.py
new file mode 100644
index 00000000..0d87ca19
--- /dev/null
+++ b/dnn/torch/osce/utils/layers/limited_adaptive_comb1d.py
@@ -0,0 +1,230 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+from utils.endoscopy import write_data
+from utils.softquant import soft_quant
+
+class LimitedAdaptiveComb1d(nn.Module):
+ COUNTER = 1
+
+ def __init__(self,
+ kernel_size,
+ feature_dim,
+ frame_size=160,
+ overlap_size=40,
+ padding=None,
+ max_lag=256,
+ name=None,
+ gain_limit_db=10,
+ global_gain_limits_db=[-6, 6],
+ norm_p=2,
+ softquant=False,
+ apply_weight_norm=False,
+ **kwargs):
+ """
+
+ Parameters:
+ -----------
+
+ feature_dim : int
+ dimension of features from which kernels, biases and gains are computed
+
+ frame_size : int, optional
+ frame size, defaults to 160
+
+ overlap_size : int, optional
+ overlap size for filter cross-fade. Cross-fade is done on the first overlap_size samples of every frame, defaults to 40
+
+ use_bias : bool, optional
+ if true, biases will be added to output channels. Defaults to True
+
+ padding : List[int, int], optional
+ left and right padding. Defaults to [(kernel_size - 1) // 2, kernel_size - 1 - (kernel_size - 1) // 2]
+
+ max_lag : int, optional
+ maximal pitch lag, defaults to 256
+
+ have_a0 : bool, optional
+ If true, the filter coefficient a0 will be learned as a positive gain (requires in_channels == out_channels). Otherwise, a0 is set to 0. Defaults to False
+
+ name: str or None, optional
+ specifies a name attribute for the module. If None the name is auto generated as comb_1d_COUNT, where COUNT is an instance counter for LimitedAdaptiveComb1d
+
+ """
+
+ super(LimitedAdaptiveComb1d, self).__init__()
+
+ self.in_channels = 1
+ self.out_channels = 1
+ self.feature_dim = feature_dim
+ self.kernel_size = kernel_size
+ self.frame_size = frame_size
+ self.overlap_size = overlap_size
+ self.max_lag = max_lag
+ self.limit_db = gain_limit_db
+ self.norm_p = norm_p
+
+ if name is None:
+ self.name = "limited_adaptive_comb1d_" + str(LimitedAdaptiveComb1d.COUNTER)
+ LimitedAdaptiveComb1d.COUNTER += 1
+ else:
+ self.name = name
+
+ norm = torch.nn.utils.weight_norm if apply_weight_norm else lambda x, name=None: x
+
+ # network for generating convolution weights
+ self.conv_kernel = norm(nn.Linear(feature_dim, kernel_size))
+
+ if softquant:
+ self.conv_kernel = soft_quant(self.conv_kernel)
+
+
+ # comb filter gain
+ self.filter_gain = norm(nn.Linear(feature_dim, 1))
+ self.log_gain_limit = gain_limit_db * 0.11512925464970229
+ with torch.no_grad():
+ self.filter_gain.bias[:] = max(0.1, 4 + self.log_gain_limit)
+
+ self.global_filter_gain = norm(nn.Linear(feature_dim, 1))
+ log_min, log_max = global_gain_limits_db[0] * 0.11512925464970229, global_gain_limits_db[1] * 0.11512925464970229
+ self.filter_gain_a = (log_max - log_min) / 2
+ self.filter_gain_b = (log_max + log_min) / 2
+
+ if type(padding) == type(None):
+ self.padding = [kernel_size // 2, kernel_size - 1 - kernel_size // 2]
+ else:
+ self.padding = padding
+
+ self.overlap_win = nn.Parameter(.5 + .5 * torch.cos((torch.arange(self.overlap_size) + 0.5) * torch.pi / overlap_size), requires_grad=False)
+
+ def forward(self, x, features, lags, debug=False):
+ """ adaptive 1d convolution
+
+
+ Parameters:
+ -----------
+ x : torch.tensor
+ input signal of shape (batch_size, in_channels, num_samples)
+
+ feathres : torch.tensor
+ frame-wise features of shape (batch_size, num_frames, feature_dim)
+
+ lags: torch.LongTensor
+ frame-wise lags for comb-filtering
+
+ """
+
+ batch_size = x.size(0)
+ num_frames = features.size(1)
+ num_samples = x.size(2)
+ frame_size = self.frame_size
+ overlap_size = self.overlap_size
+ kernel_size = self.kernel_size
+ win1 = torch.flip(self.overlap_win, [0])
+ win2 = self.overlap_win
+
+ if num_samples // self.frame_size != num_frames:
+ raise ValueError('non matching sizes in AdaptiveConv1d.forward')
+
+ conv_kernels = self.conv_kernel(features).reshape((batch_size, num_frames, self.out_channels, self.in_channels, self.kernel_size))
+ conv_kernels = conv_kernels / (1e-6 + torch.norm(conv_kernels, p=self.norm_p, dim=-1, keepdim=True))
+
+ conv_gains = torch.exp(- torch.relu(self.filter_gain(features).permute(0, 2, 1)) + self.log_gain_limit)
+ # calculate gains
+ global_conv_gains = torch.exp(self.filter_gain_a * torch.tanh(self.global_filter_gain(features).permute(0, 2, 1)) + self.filter_gain_b)
+
+ if debug and batch_size == 1:
+ key = self.name + "_gains"
+ write_data(key, conv_gains.detach().squeeze().cpu().numpy(), 16000 // self.frame_size)
+ key = self.name + "_kernels"
+ write_data(key, conv_kernels.detach().squeeze().cpu().numpy(), 16000 // self.frame_size)
+ key = self.name + "_lags"
+ write_data(key, lags.detach().squeeze().cpu().numpy(), 16000 // self.frame_size)
+ key = self.name + "_global_conv_gains"
+ write_data(key, global_conv_gains.detach().squeeze().cpu().numpy(), 16000 // self.frame_size)
+
+
+ # frame-wise convolution with overlap-add
+ output_frames = []
+ overlap_mem = torch.zeros((batch_size, self.out_channels, self.overlap_size), device=x.device)
+ x = F.pad(x, self.padding)
+ x = F.pad(x, [self.max_lag, self.overlap_size])
+
+ idx = torch.arange(frame_size + kernel_size - 1 + overlap_size).to(x.device).view(1, 1, -1)
+ idx = torch.repeat_interleave(idx, batch_size, 0)
+ idx = torch.repeat_interleave(idx, self.in_channels, 1)
+
+
+ for i in range(num_frames):
+
+ cidx = idx + i * frame_size + self.max_lag - lags[..., i].view(batch_size, 1, 1)
+ xx = torch.gather(x, -1, cidx).reshape((1, batch_size * self.in_channels, -1))
+
+ new_chunk = torch.conv1d(xx, conv_kernels[:, i, ...].reshape((batch_size * self.out_channels, self.in_channels, self.kernel_size)), groups=batch_size).reshape(batch_size, self.out_channels, -1)
+
+ offset = self.max_lag + self.padding[0]
+ new_chunk = global_conv_gains[:, :, i : i + 1] * (new_chunk * conv_gains[:, :, i : i + 1] + x[..., offset + i * frame_size : offset + (i + 1) * frame_size + overlap_size])
+
+ # overlapping part
+ output_frames.append(new_chunk[:, :, : overlap_size] * win1 + overlap_mem * win2)
+
+ # non-overlapping part
+ output_frames.append(new_chunk[:, :, overlap_size : frame_size])
+
+ # mem for next frame
+ overlap_mem = new_chunk[:, :, frame_size :]
+
+ # concatenate chunks
+ output = torch.cat(output_frames, dim=-1)
+
+ return output
+
+ def flop_count(self, rate):
+ frame_rate = rate / self.frame_size
+ overlap = self.overlap_size
+ overhead = overlap / self.frame_size
+
+ count = 0
+
+ # kernel computation and filtering
+ count += 2 * (frame_rate * self.feature_dim * self.kernel_size)
+ count += 2 * (self.in_channels * self.out_channels * self.kernel_size * (1 + overhead) * rate)
+ count += 2 * (frame_rate * self.feature_dim * self.out_channels) + rate * (1 + overhead) * self.out_channels
+
+ # a0 computation
+ count += 2 * (frame_rate * self.feature_dim * self.out_channels) + rate * (1 + overhead) * self.out_channels
+
+ # windowing
+ count += overlap * frame_rate * 3 * self.out_channels
+
+ return count
diff --git a/dnn/torch/osce/utils/layers/limited_adaptive_conv1d.py b/dnn/torch/osce/utils/layers/limited_adaptive_conv1d.py
new file mode 100644
index 00000000..55df8c14
--- /dev/null
+++ b/dnn/torch/osce/utils/layers/limited_adaptive_conv1d.py
@@ -0,0 +1,200 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+from utils.endoscopy import write_data
+
+from utils.ada_conv import adaconv_kernel
+from utils.softquant import soft_quant
+
+class LimitedAdaptiveConv1d(nn.Module):
+ COUNTER = 1
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ kernel_size,
+ feature_dim,
+ frame_size=160,
+ overlap_size=40,
+ padding=None,
+ name=None,
+ gain_limits_db=[-6, 6],
+ shape_gain_db=0,
+ norm_p=2,
+ softquant=False,
+ apply_weight_norm=False,
+ **kwargs):
+ """
+
+ Parameters:
+ -----------
+
+ in_channels : int
+ number of input channels
+
+ out_channels : int
+ number of output channels
+
+ feature_dim : int
+ dimension of features from which kernels, biases and gains are computed
+
+ frame_size : int
+ frame size
+
+ overlap_size : int
+ overlap size for filter cross-fade. Cross-fade is done on the first overlap_size samples of every frame
+
+ use_bias : bool
+ if true, biases will be added to output channels
+
+
+ padding : List[int, int]
+
+ """
+
+ super(LimitedAdaptiveConv1d, self).__init__()
+
+
+
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.feature_dim = feature_dim
+ self.kernel_size = kernel_size
+ self.frame_size = frame_size
+ self.overlap_size = overlap_size
+ self.gain_limits_db = gain_limits_db
+ self.shape_gain_db = shape_gain_db
+ self.norm_p = norm_p
+
+ if name is None:
+ self.name = "limited_adaptive_conv1d_" + str(LimitedAdaptiveConv1d.COUNTER)
+ LimitedAdaptiveConv1d.COUNTER += 1
+ else:
+ self.name = name
+
+ norm = torch.nn.utils.weight_norm if apply_weight_norm else lambda x, name=None: x
+
+ # network for generating convolution weights
+ self.conv_kernel = norm(nn.Linear(feature_dim, in_channels * out_channels * kernel_size))
+ if softquant:
+ self.conv_kernel = soft_quant(self.conv_kernel)
+
+ self.shape_gain = min(1, 10**(shape_gain_db / 20))
+
+ self.filter_gain = norm(nn.Linear(feature_dim, out_channels))
+ log_min, log_max = gain_limits_db[0] * 0.11512925464970229, gain_limits_db[1] * 0.11512925464970229
+ self.filter_gain_a = (log_max - log_min) / 2
+ self.filter_gain_b = (log_max + log_min) / 2
+
+ if type(padding) == type(None):
+ self.padding = [kernel_size // 2, kernel_size - 1 - kernel_size // 2]
+ else:
+ self.padding = padding
+
+ self.overlap_win = nn.Parameter(.5 + .5 * torch.cos((torch.arange(self.overlap_size) + 0.5) * torch.pi / overlap_size), requires_grad=False)
+
+
+ def flop_count(self, rate):
+ frame_rate = rate / self.frame_size
+ overlap = self.overlap_size
+ overhead = overlap / self.frame_size
+
+ count = 0
+
+ # kernel computation and filtering
+ count += 2 * (frame_rate * self.feature_dim * self.kernel_size)
+ count += 2 * (self.in_channels * self.out_channels * self.kernel_size * (1 + overhead) * rate)
+
+ # gain computation
+
+ count += 2 * (frame_rate * self.feature_dim * self.out_channels) + rate * (1 + overhead) * self.out_channels
+
+ # windowing
+ count += 3 * overlap * frame_rate * self.out_channels
+
+ return count
+
+ def forward(self, x, features, debug=False):
+ """ adaptive 1d convolution
+
+
+ Parameters:
+ -----------
+ x : torch.tensor
+ input signal of shape (batch_size, in_channels, num_samples)
+
+ feathres : torch.tensor
+ frame-wise features of shape (batch_size, num_frames, feature_dim)
+
+ """
+
+ batch_size = x.size(0)
+ num_frames = features.size(1)
+ num_samples = x.size(2)
+ frame_size = self.frame_size
+ overlap_size = self.overlap_size
+ kernel_size = self.kernel_size
+ win1 = torch.flip(self.overlap_win, [0])
+ win2 = self.overlap_win
+
+ if num_samples // self.frame_size != num_frames:
+ raise ValueError('non matching sizes in AdaptiveConv1d.forward')
+
+ conv_kernels = self.conv_kernel(features).reshape((batch_size, num_frames, self.out_channels, self.in_channels, self.kernel_size))
+
+ # normalize kernels (TODO: switch to L1 and normalize over kernel and input channel dimension)
+ conv_kernels = conv_kernels / (1e-6 + torch.norm(conv_kernels, p=self.norm_p, dim=[-2, -1], keepdim=True))
+
+ # limit shape
+ id_kernels = torch.zeros_like(conv_kernels)
+ id_kernels[..., self.padding[1]] = 1
+
+ conv_kernels = self.shape_gain * conv_kernels + (1 - self.shape_gain) * id_kernels
+
+ # calculate gains
+ conv_gains = torch.exp(self.filter_gain_a * torch.tanh(self.filter_gain(features)) + self.filter_gain_b)
+ if debug and batch_size == 1:
+ key = self.name + "_gains"
+ write_data(key, conv_gains.permute(0, 2, 1).detach().squeeze().cpu().numpy(), 16000 // self.frame_size)
+ key = self.name + "_kernels"
+ write_data(key, conv_kernels.detach().squeeze().cpu().numpy(), 16000 // self.frame_size)
+
+
+ conv_kernels = conv_kernels * conv_gains.view(batch_size, num_frames, self.out_channels, 1, 1)
+
+ conv_kernels = conv_kernels.permute(0, 2, 3, 1, 4)
+
+ output = adaconv_kernel(x, conv_kernels, win1, fft_size=256)
+
+
+ return output \ No newline at end of file
diff --git a/dnn/torch/osce/utils/layers/noise_shaper.py b/dnn/torch/osce/utils/layers/noise_shaper.py
new file mode 100644
index 00000000..ba8a3af3
--- /dev/null
+++ b/dnn/torch/osce/utils/layers/noise_shaper.py
@@ -0,0 +1,100 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+from utils.complexity import _conv1d_flop_count
+
+class NoiseShaper(nn.Module):
+
+ def __init__(self,
+ feature_dim,
+ frame_size=160
+ ):
+ """
+
+ Parameters:
+ -----------
+
+ feature_dim : int
+ dimension of input features
+
+ frame_size : int
+ frame size
+
+ """
+
+ super().__init__()
+
+ self.feature_dim = feature_dim
+ self.frame_size = frame_size
+
+ # feature transform
+ self.feature_alpha1 = nn.Conv1d(self.feature_dim, frame_size, 2)
+ self.feature_alpha2 = nn.Conv1d(frame_size, frame_size, 2)
+
+
+ def flop_count(self, rate):
+
+ frame_rate = rate / self.frame_size
+
+ shape_flops = sum([_conv1d_flop_count(x, frame_rate) for x in (self.feature_alpha1, self.feature_alpha2)]) + 11 * frame_rate * self.frame_size
+
+ return shape_flops
+
+
+ def forward(self, features):
+ """ creates temporally shaped noise
+
+
+ Parameters:
+ -----------
+ features : torch.tensor
+ frame-wise features of shape (batch_size, num_frames, feature_dim)
+
+ """
+
+ batch_size = features.size(0)
+ num_frames = features.size(1)
+ frame_size = self.frame_size
+ num_samples = num_frames * frame_size
+
+ # feature path
+ f = F.pad(features.permute(0, 2, 1), [1, 0])
+ alpha = F.leaky_relu(self.feature_alpha1(f), 0.2)
+ alpha = torch.exp(self.feature_alpha2(F.pad(alpha, [1, 0])))
+ alpha = alpha.permute(0, 2, 1)
+
+ # signal generation
+ y = torch.randn((batch_size, num_frames, frame_size), dtype=features.dtype, device=features.device)
+ y = alpha * y
+
+ return y.reshape(batch_size, 1, num_samples)
diff --git a/dnn/torch/osce/utils/layers/pitch_auto_correlator.py b/dnn/torch/osce/utils/layers/pitch_auto_correlator.py
new file mode 100644
index 00000000..ef58ae8e
--- /dev/null
+++ b/dnn/torch/osce/utils/layers/pitch_auto_correlator.py
@@ -0,0 +1,84 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+
+class PitchAutoCorrelator(nn.Module):
+ def __init__(self,
+ frame_size=80,
+ pitch_min=32,
+ pitch_max=300,
+ radius=2):
+
+ super().__init__()
+
+ self.frame_size = frame_size
+ self.pitch_min = pitch_min
+ self.pitch_max = pitch_max
+ self.radius = radius
+
+
+ def forward(self, x, periods):
+ # x of shape (batch_size, channels, num_samples)
+ # periods of shape (batch_size, num_frames)
+
+ num_frames = periods.size(1)
+ batch_size = periods.size(0)
+ num_samples = self.frame_size * num_frames
+ channels = x.size(1)
+
+ assert num_samples == x.size(-1)
+
+ range = torch.arange(-self.radius, self.radius + 1, device=x.device)
+ idx = torch.arange(self.frame_size * num_frames, device=x.device)
+ p_up = torch.repeat_interleave(periods, self.frame_size, 1)
+ lookup = idx + self.pitch_max - p_up
+ lookup = lookup.unsqueeze(-1) + range
+ lookup = lookup.unsqueeze(1)
+
+ # padding
+ x_pad = F.pad(x, [self.pitch_max, 0])
+ x_ext = torch.repeat_interleave(x_pad.unsqueeze(-1), 2 * self.radius + 1, -1)
+
+ # framing
+ x_select = torch.gather(x_ext, 2, lookup)
+ x_frames = x_pad[..., self.pitch_max : ].reshape(batch_size, channels, num_frames, self.frame_size, 1)
+ lag_frames = x_select.reshape(batch_size, 1, num_frames, self.frame_size, -1)
+
+ # calculate auto-correlation
+ dotp = torch.sum(x_frames * lag_frames, dim=-2)
+ frame_nrg = torch.sum(x_frames * x_frames, dim=-2)
+ lag_frame_nrg = torch.sum(lag_frames * lag_frames, dim=-2)
+
+ acorr = dotp / torch.sqrt(frame_nrg * lag_frame_nrg + 1e-9)
+
+ return acorr
diff --git a/dnn/torch/osce/utils/layers/silk_upsampler.py b/dnn/torch/osce/utils/layers/silk_upsampler.py
new file mode 100644
index 00000000..0d20b8a6
--- /dev/null
+++ b/dnn/torch/osce/utils/layers/silk_upsampler.py
@@ -0,0 +1,167 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+""" This module implements the SILK upsampler from 16kHz to 24 or 48 kHz """
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+import numpy as np
+
+frac_fir = np.array(
+ [
+ [189, -600, 617, 30567, 2996, -1375, 425, -46],
+ [117, -159, -1070, 29704, 5784, -2143, 611, -71],
+ [52, 221, -2392, 28276, 8798, -2865, 773, -91],
+ [-4, 529, -3350, 26341, 11950, -3487, 896, -103],
+ [-48, 758, -3956, 23973, 15143, -3957, 967, -107],
+ [-80, 905, -4235, 21254, 18278, -4222, 972, -99],
+ [-99, 972, -4222, 18278, 21254, -4235, 905, -80],
+ [-107, 967, -3957, 15143, 23973, -3956, 758, -48],
+ [-103, 896, -3487, 11950, 26341, -3350, 529, -4],
+ [-91, 773, -2865, 8798, 28276, -2392, 221, 52],
+ [-71, 611, -2143, 5784, 29704, -1070, -159, 117],
+ [-46, 425, -1375, 2996, 30567, 617, -600, 189]
+ ],
+ dtype=np.float32
+) / 2**15
+
+
+hq_2x_up_c_even = [x / 2**16 for x in [1746, 14986, 39083 - 65536]]
+hq_2x_up_c_odd = [x / 2**16 for x in [6854, 25769, 55542 - 65536]]
+
+
+def get_impz(coeffs, n):
+ s = 3*[0]
+ y = np.zeros(n)
+ x = 1
+
+ for i in range(n):
+ Y = x - s[0]
+ X = Y * coeffs[0]
+ tmp1 = s[0] + X
+ s[0] = x + X
+
+ Y = tmp1 - s[1]
+ X = Y * coeffs[1]
+ tmp2 = s[1] + X
+ s[1] = tmp1 + X
+
+ Y = tmp2 - s[2]
+ X = Y * (1 + coeffs[2])
+ tmp3 = s[2] + X
+ s[2] = tmp2 + X
+
+ y[i] = tmp3
+ x = 0
+
+ return y
+
+
+
+class SilkUpsampler(nn.Module):
+ SUPPORTED_TARGET_RATES = {24000, 48000}
+ SUPPORTED_SOURCE_RATES = {16000}
+ def __init__(self,
+ fs_in=16000,
+ fs_out=48000):
+
+ super().__init__()
+ self.fs_in = fs_in
+ self.fs_out = fs_out
+
+ if fs_in not in self.SUPPORTED_SOURCE_RATES:
+ raise ValueError(f'SilkUpsampler currently only supports upsampling from {self.SUPPORTED_SOURCE_RATES} Hz')
+
+
+ if fs_out not in self.SUPPORTED_TARGET_RATES:
+ raise ValueError(f'SilkUpsampler currently only supports upsampling to {self.SUPPORTED_TARGET_RATES} Hz')
+
+
+ # hq 2x upsampler as FIR approximation
+ hq_2x_up_even = get_impz(hq_2x_up_c_even, 128)[::-1].copy()
+ hq_2x_up_odd = get_impz(hq_2x_up_c_odd , 128)[::-1].copy()
+
+ self.hq_2x_up_even = nn.Parameter(torch.from_numpy(hq_2x_up_even).float().view(1, 1, -1), requires_grad=False)
+ self.hq_2x_up_odd = nn.Parameter(torch.from_numpy(hq_2x_up_odd ).float().view(1, 1, -1), requires_grad=False)
+ self.hq_2x_up_padding = [127, 0]
+
+ # interpolation filters
+ frac_01_24 = frac_fir[0]
+ frac_17_24 = frac_fir[8]
+ frac_09_24 = frac_fir[4]
+
+ self.frac_01_24 = nn.Parameter(torch.from_numpy(frac_01_24).view(1, 1, -1), requires_grad=False)
+ self.frac_17_24 = nn.Parameter(torch.from_numpy(frac_17_24).view(1, 1, -1), requires_grad=False)
+ self.frac_09_24 = nn.Parameter(torch.from_numpy(frac_09_24).view(1, 1, -1), requires_grad=False)
+
+ self.stride = 1 if fs_out == 48000 else 2
+
+ def hq_2x_up(self, x):
+
+ num_channels = x.size(1)
+
+ weight_even = torch.repeat_interleave(self.hq_2x_up_even, num_channels, 0)
+ weight_odd = torch.repeat_interleave(self.hq_2x_up_odd , num_channels, 0)
+
+ x_pad = F.pad(x, self.hq_2x_up_padding)
+ y_even = F.conv1d(x_pad, weight_even, groups=num_channels)
+ y_odd = F.conv1d(x_pad, weight_odd , groups=num_channels)
+
+ y = torch.cat((y_even.unsqueeze(-1), y_odd.unsqueeze(-1)), dim=-1).flatten(2)
+
+ return y
+
+ def interpolate_3_2(self, x):
+
+ num_channels = x.size(1)
+
+ weight_01_24 = torch.repeat_interleave(self.frac_01_24, num_channels, 0)
+ weight_17_24 = torch.repeat_interleave(self.frac_17_24, num_channels, 0)
+ weight_09_24 = torch.repeat_interleave(self.frac_09_24, num_channels, 0)
+
+ x_pad = F.pad(x, [8, 0])
+ y_01_24 = F.conv1d(x_pad, weight_01_24, stride=2, groups=num_channels)
+ y_17_24 = F.conv1d(x_pad, weight_17_24, stride=2, groups=num_channels)
+ y_09_24_sh1 = F.conv1d(torch.roll(x_pad, -1, -1), weight_09_24, stride=2, groups=num_channels)
+
+
+ y = torch.cat(
+ (y_01_24.unsqueeze(-1), y_17_24.unsqueeze(-1), y_09_24_sh1.unsqueeze(-1)),
+ dim=-1).flatten(2)
+
+ return y[..., :-3]
+
+ def forward(self, x):
+
+ y_2x = self.hq_2x_up(x)
+ y_3x = self.interpolate_3_2(y_2x)
+
+ return y_3x[:, :, ::self.stride]
diff --git a/dnn/torch/osce/utils/layers/td_shaper.py b/dnn/torch/osce/utils/layers/td_shaper.py
new file mode 100644
index 00000000..fa7bf348
--- /dev/null
+++ b/dnn/torch/osce/utils/layers/td_shaper.py
@@ -0,0 +1,145 @@
+import torch
+from torch import nn
+import torch.nn.functional as F
+
+from utils.complexity import _conv1d_flop_count
+from utils.softquant import soft_quant
+
+class TDShaper(nn.Module):
+ COUNTER = 1
+
+ def __init__(self,
+ feature_dim,
+ frame_size=160,
+ avg_pool_k=4,
+ innovate=False,
+ pool_after=False,
+ softquant=False,
+ apply_weight_norm=False
+ ):
+ """
+
+ Parameters:
+ -----------
+
+
+ feature_dim : int
+ dimension of input features
+
+ frame_size : int
+ frame size
+
+ avg_pool_k : int, optional
+ kernel size and stride for avg pooling
+
+ padding : List[int, int]
+
+ """
+
+ super().__init__()
+
+
+ self.feature_dim = feature_dim
+ self.frame_size = frame_size
+ self.avg_pool_k = avg_pool_k
+ self.innovate = innovate
+ self.pool_after = pool_after
+
+ assert frame_size % avg_pool_k == 0
+ self.env_dim = frame_size // avg_pool_k + 1
+
+ norm = torch.nn.utils.weight_norm if apply_weight_norm else lambda x, name=None: x
+
+ # feature transform
+ self.feature_alpha1_f = norm(nn.Conv1d(self.feature_dim, frame_size, 2))
+ self.feature_alpha1_t = norm(nn.Conv1d(self.env_dim, frame_size, 2))
+ self.feature_alpha2 = norm(nn.Conv1d(frame_size, frame_size, 2))
+
+ if softquant:
+ self.feature_alpha1_f = soft_quant(self.feature_alpha1_f)
+
+ if self.innovate:
+ self.feature_alpha1b = norm(nn.Conv1d(self.feature_dim + self.env_dim, frame_size, 2))
+ self.feature_alpha1c = norm(nn.Conv1d(self.feature_dim + self.env_dim, frame_size, 2))
+
+ self.feature_alpha2b = norm(nn.Conv1d(frame_size, frame_size, 2))
+ self.feature_alpha2c = norm(nn.Conv1d(frame_size, frame_size, 2))
+
+
+ def flop_count(self, rate):
+
+ frame_rate = rate / self.frame_size
+
+ shape_flops = sum([_conv1d_flop_count(x, frame_rate) for x in (self.feature_alpha1_f, self.feature_alpha1_t, self.feature_alpha2)]) + 11 * frame_rate * self.frame_size
+
+ if self.innovate:
+ inno_flops = sum([_conv1d_flop_count(x, frame_rate) for x in (self.feature_alpha1b, self.feature_alpha2b, self.feature_alpha1c, self.feature_alpha2c)]) + 22 * frame_rate * self.frame_size
+ else:
+ inno_flops = 0
+
+ return shape_flops + inno_flops
+
+ def envelope_transform(self, x):
+
+ x = torch.abs(x)
+ if self.pool_after:
+ x = torch.log(x + .5**16)
+ x = F.avg_pool1d(x, self.avg_pool_k, self.avg_pool_k)
+ else:
+ x = F.avg_pool1d(x, self.avg_pool_k, self.avg_pool_k)
+ x = torch.log(x + .5**16)
+
+ x = x.reshape(x.size(0), -1, self.env_dim - 1)
+ avg_x = torch.mean(x, -1, keepdim=True)
+
+ x = torch.cat((x - avg_x, avg_x), dim=-1)
+
+ return x
+
+ def forward(self, x, features, debug=False):
+ """ innovate signal parts with temporal shaping
+
+
+ Parameters:
+ -----------
+ x : torch.tensor
+ input signal of shape (batch_size, 1, num_samples)
+
+ features : torch.tensor
+ frame-wise features of shape (batch_size, num_frames, feature_dim)
+
+ """
+
+ batch_size = x.size(0)
+ num_frames = features.size(1)
+ num_samples = x.size(2)
+ frame_size = self.frame_size
+
+ # generate temporal envelope
+ tenv = self.envelope_transform(x)
+
+ # feature path
+ f = F.pad(features.permute(0, 2, 1), [1, 0])
+ t = F.pad(tenv.permute(0, 2, 1), [1, 0])
+ alpha = self.feature_alpha1_f(f) + self.feature_alpha1_t(t)
+ alpha = F.leaky_relu(alpha, 0.2)
+ alpha = torch.exp(self.feature_alpha2(F.pad(alpha, [1, 0])))
+ alpha = alpha.permute(0, 2, 1)
+
+ if self.innovate:
+ inno_alpha = F.leaky_relu(self.feature_alpha1b(f), 0.2)
+ inno_alpha = torch.exp(self.feature_alpha2b(F.pad(inno_alpha, [1, 0])))
+ inno_alpha = inno_alpha.permute(0, 2, 1)
+
+ inno_x = F.leaky_relu(self.feature_alpha1c(f), 0.2)
+ inno_x = torch.tanh(self.feature_alpha2c(F.pad(inno_x, [1, 0])))
+ inno_x = inno_x.permute(0, 2, 1)
+
+ # signal path
+ y = x.reshape(batch_size, num_frames, -1)
+ y = alpha * y
+
+ if self.innovate:
+ y = y + inno_alpha * inno_x
+
+ return y.reshape(batch_size, 1, num_samples)
diff --git a/dnn/torch/osce/utils/lpcnet_features.py b/dnn/torch/osce/utils/lpcnet_features.py
new file mode 100644
index 00000000..3d109fd3
--- /dev/null
+++ b/dnn/torch/osce/utils/lpcnet_features.py
@@ -0,0 +1,112 @@
+import os
+
+import torch
+import numpy as np
+
+def load_lpcnet_features(feature_file, version=2):
+ if version == 2:
+ layout = {
+ 'cepstrum': [0,18],
+ 'periods': [18, 19],
+ 'pitch_corr': [19, 20],
+ 'lpc': [20, 36]
+ }
+ frame_length = 36
+
+ elif version == 1:
+ layout = {
+ 'cepstrum': [0,18],
+ 'periods': [36, 37],
+ 'pitch_corr': [37, 38],
+ 'lpc': [39, 55],
+ }
+ frame_length = 55
+ else:
+ raise ValueError(f'unknown feature version: {version}')
+
+
+ raw_features = torch.from_numpy(np.fromfile(feature_file, dtype='float32'))
+ raw_features = raw_features.reshape((-1, frame_length))
+
+ features = torch.cat(
+ [
+ raw_features[:, layout['cepstrum'][0] : layout['cepstrum'][1]],
+ raw_features[:, layout['pitch_corr'][0] : layout['pitch_corr'][1]]
+ ],
+ dim=1
+ )
+
+ lpcs = raw_features[:, layout['lpc'][0] : layout['lpc'][1]]
+ periods = (0.1 + 50 * raw_features[:, layout['periods'][0] : layout['periods'][1]] + 100).long()
+
+ return {'features' : features, 'periods' : periods, 'lpcs' : lpcs}
+
+
+
+def create_new_data(signal_path, reference_data_path, new_data_path, offset=320, preemph_factor=0.85):
+ ref_data = np.memmap(reference_data_path, dtype=np.int16)
+ signal = np.memmap(signal_path, dtype=np.int16)
+
+ signal_preemph_path = os.path.splitext(signal_path)[0] + '_preemph.raw'
+ signal_preemph = np.memmap(signal_preemph_path, dtype=np.int16, mode='write', shape=signal.shape)
+
+
+ assert len(signal) % 160 == 0
+ num_frames = len(signal) // 160
+ mem = np.zeros(1)
+ for fr in range(len(signal)//160):
+ signal_preemph[fr * 160 : (fr + 1) * 160] = np.convolve(np.concatenate((mem, signal[fr * 160 : (fr + 1) * 160])), [1, -preemph_factor], mode='valid')
+ mem = signal[(fr + 1) * 160 - 1 : (fr + 1) * 160]
+
+ new_data = np.memmap(new_data_path, dtype=np.int16, mode='write', shape=ref_data.shape)
+
+ new_data[:] = 0
+ N = len(signal) - offset
+ new_data[1 : 2*N + 1: 2] = signal_preemph[offset:]
+ new_data[2 : 2*N + 2: 2] = signal_preemph[offset:]
+
+
+def parse_warpq_scores(output_file):
+ """ extracts warpq scores from output file """
+
+ with open(output_file, "r") as f:
+ lines = f.readlines()
+
+ scores = [float(line.split("WARP-Q score:")[-1]) for line in lines if line.startswith("WARP-Q score:")]
+
+ return scores
+
+
+def parse_stats_file(file):
+
+ with open(file, "r") as f:
+ lines = f.readlines()
+
+ mean = float(lines[0].split(":")[-1])
+ bt_mean = float(lines[1].split(":")[-1])
+ top_mean = float(lines[2].split(":")[-1])
+
+ return mean, bt_mean, top_mean
+
+def collect_test_stats(test_folder):
+ """ collects statistics for all discovered metrics from test folder """
+
+ metrics = {'pesq', 'warpq', 'pitch_error', 'voicing_error'}
+
+ results = dict()
+
+ content = os.listdir(test_folder)
+
+ stats_files = [file for file in content if file.startswith('stats_')]
+
+ for file in stats_files:
+ metric = file[len("stats_") : -len(".txt")]
+
+ if metric not in metrics:
+ print(f"warning: unknown metric {metric}")
+
+ mean, bt_mean, top_mean = parse_stats_file(os.path.join(test_folder, file))
+
+ results[metric] = [mean, bt_mean, top_mean]
+
+ return results
diff --git a/dnn/torch/osce/utils/misc.py b/dnn/torch/osce/utils/misc.py
new file mode 100644
index 00000000..68ee4bfd
--- /dev/null
+++ b/dnn/torch/osce/utils/misc.py
@@ -0,0 +1,95 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+from torch.nn.utils import remove_weight_norm
+
+def count_parameters(model, verbose=False):
+ total = 0
+ for name, p in model.named_parameters():
+ count = torch.ones_like(p).sum().item()
+
+ if verbose:
+ print(f"{name}: {count} parameters")
+
+ total += count
+
+ return total
+
+def count_nonzero_parameters(model, verbose=False):
+ total = 0
+ for name, p in model.named_parameters():
+ count = torch.count_nonzero(p).item()
+
+ if verbose:
+ print(f"{name}: {count} non-zero parameters")
+
+ total += count
+
+ return total
+def retain_grads(module):
+ for p in module.parameters():
+ if p.requires_grad:
+ p.retain_grad()
+
+def get_grad_norm(module, p=2):
+ norm = 0
+ for param in module.parameters():
+ if param.requires_grad:
+ norm = norm + (torch.abs(param.grad) ** p).sum()
+
+ return norm ** (1/p)
+
+def create_weights(s_real, s_gen, alpha):
+ weights = []
+ with torch.no_grad():
+ for sr, sg in zip(s_real, s_gen):
+ weight = torch.exp(alpha * (sr[-1] - sg[-1]))
+ weights.append(weight)
+
+ return weights
+
+
+def _get_candidates(module: torch.nn.Module):
+ candidates = []
+ for key in module.__dict__.keys():
+ if hasattr(module, key + '_v'):
+ candidates.append(key)
+ return candidates
+
+def remove_all_weight_norm(model : torch.nn.Module, verbose=False):
+ for name, m in model.named_modules():
+ candidates = _get_candidates(m)
+
+ for candidate in candidates:
+ try:
+ remove_weight_norm(m, name=candidate)
+ if verbose: print(f'removed weight norm on weight {name}.{candidate}')
+ except:
+ pass
diff --git a/dnn/torch/osce/utils/moc.py b/dnn/torch/osce/utils/moc.py
new file mode 100644
index 00000000..3dffcfd0
--- /dev/null
+++ b/dnn/torch/osce/utils/moc.py
@@ -0,0 +1,153 @@
+import numpy as np
+import scipy.signal
+
+def compute_vad_mask(x, fs, stop_db=-70):
+
+ frame_length = (fs + 49) // 50
+ x = x[: frame_length * (len(x) // frame_length)]
+
+ frames = x.reshape(-1, frame_length)
+ frame_energy = np.sum(frames ** 2, axis=1)
+ frame_energy_smooth = np.convolve(frame_energy, np.ones(5) / 5, mode='same')
+
+ max_threshold = frame_energy.max() * 10 ** (stop_db/20)
+ vactive = np.ones_like(frames)
+ vactive[frame_energy_smooth < max_threshold, :] = 0
+ vactive = vactive.reshape(-1)
+
+ filter = np.sin(np.arange(frame_length) * np.pi / (frame_length - 1))
+ filter = filter / filter.sum()
+
+ mask = np.convolve(vactive, filter, mode='same')
+
+ return x, mask
+
+def convert_mask(mask, num_frames, frame_size=160, hop_size=40):
+ num_samples = frame_size + (num_frames - 1) * hop_size
+ if len(mask) < num_samples:
+ mask = np.concatenate((mask, np.zeros(num_samples - len(mask))), dtype=mask.dtype)
+ else:
+ mask = mask[:num_samples]
+
+ new_mask = np.array([np.mean(mask[i*hop_size : i*hop_size + frame_size]) for i in range(num_frames)])
+
+ return new_mask
+
+def power_spectrum(x, window_size=160, hop_size=40, window='hamming'):
+ num_spectra = (len(x) - window_size - hop_size) // hop_size
+ window = scipy.signal.get_window(window, window_size)
+ N = window_size // 2
+
+ frames = np.concatenate([x[np.newaxis, i * hop_size : i * hop_size + window_size] for i in range(num_spectra)]) * window
+ psd = np.abs(np.fft.fft(frames, axis=1)[:, :N + 1]) ** 2
+
+ return psd
+
+
+def frequency_mask(num_bands, up_factor, down_factor):
+
+ up_mask = np.zeros((num_bands, num_bands))
+ down_mask = np.zeros((num_bands, num_bands))
+
+ for i in range(num_bands):
+ up_mask[i, : i + 1] = up_factor ** np.arange(i, -1, -1)
+ down_mask[i, i :] = down_factor ** np.arange(num_bands - i)
+
+ return down_mask @ up_mask
+
+
+def rect_fb(band_limits, num_bins=None):
+ num_bands = len(band_limits) - 1
+ if num_bins is None:
+ num_bins = band_limits[-1]
+
+ fb = np.zeros((num_bands, num_bins))
+ for i in range(num_bands):
+ fb[i, band_limits[i]:band_limits[i+1]] = 1
+
+ return fb
+
+
+def compare(x, y, apply_vad=False):
+ """ Modified version of opus_compare for 16 kHz mono signals
+
+ Args:
+ x (np.ndarray): reference input signal scaled to [-1, 1]
+ y (np.ndarray): test signal scaled to [-1, 1]
+
+ Returns:
+ float: perceptually weighted error
+ """
+ # filter bank: bark scale with minimum-2-bin bands and cutoff at 7.5 kHz
+ band_limits = [0, 2, 4, 6, 7, 9, 11, 13, 15, 18, 22, 26, 31, 36, 43, 51, 60, 75]
+ num_bands = len(band_limits) - 1
+ fb = rect_fb(band_limits, num_bins=81)
+
+ # trim samples to same size
+ num_samples = min(len(x), len(y))
+ x = x[:num_samples] * 2**15
+ y = y[:num_samples] * 2**15
+
+ psd_x = power_spectrum(x) + 100000
+ psd_y = power_spectrum(y) + 100000
+
+ num_frames = psd_x.shape[0]
+
+ # average band energies
+ be_x = (psd_x @ fb.T) / np.sum(fb, axis=1)
+
+ # frequecy masking
+ f_mask = frequency_mask(num_bands, 0.1, 0.03)
+ mask_x = be_x @ f_mask.T
+
+ # temporal masking
+ for i in range(1, num_frames):
+ mask_x[i, :] += 0.5 * mask_x[i-1, :]
+
+ # apply mask
+ masked_psd_x = psd_x + 0.1 * (mask_x @ fb)
+ masked_psd_y = psd_y + 0.1 * (mask_x @ fb)
+
+ # 2-frame average
+ masked_psd_x = masked_psd_x[1:] + masked_psd_x[:-1]
+ masked_psd_y = masked_psd_y[1:] + masked_psd_y[:-1]
+
+ # distortion metric
+ re = masked_psd_y / masked_psd_x
+ im = np.log(re) ** 2
+ Eb = ((im @ fb.T) / np.sum(fb, axis=1))
+ Ef = np.mean(Eb , axis=1)
+
+ if apply_vad:
+ _, mask = compute_vad_mask(x, 16000)
+ mask = convert_mask(mask, Ef.shape[0])
+ else:
+ mask = np.ones_like(Ef)
+
+ err = np.mean(np.abs(Ef[mask > 1e-6]) ** 3) ** (1/6)
+
+ return float(err)
+
+if __name__ == "__main__":
+ import argparse
+ from scipy.io import wavfile
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('ref', type=str, help='reference wav file')
+ parser.add_argument('deg', type=str, help='degraded wav file')
+ parser.add_argument('--apply-vad', action='store_true')
+ args = parser.parse_args()
+
+
+ fs1, x = wavfile.read(args.ref)
+ fs2, y = wavfile.read(args.deg)
+
+ if max(fs1, fs2) != 16000:
+ raise ValueError('error: encountered sampling frequency diffrent from 16kHz')
+
+ x = x.astype(np.float32) / 2**15
+ y = y.astype(np.float32) / 2**15
+
+ err = compare(x, y, apply_vad=args.apply_vad)
+
+ print(f"MOC: {err}")
diff --git a/dnn/torch/osce/utils/pitch.py b/dnn/torch/osce/utils/pitch.py
new file mode 100644
index 00000000..2a233812
--- /dev/null
+++ b/dnn/torch/osce/utils/pitch.py
@@ -0,0 +1,122 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import numpy as np
+
+def hangover(lags, num_frames=10):
+ lags = lags.copy()
+ count = 0
+ last_lag = 0
+
+ for i in range(len(lags)):
+ lag = lags[i]
+
+ if lag == 0:
+ if count < num_frames:
+ lags[i] = last_lag
+ count += 1
+ else:
+ count = 0
+ last_lag = lag
+
+ return lags
+
+
+def smooth_pitch_lags(lags, d=2):
+
+ assert d < 4
+
+ num_silk_frames = len(lags) // 4
+
+ smoothed_lags = lags.copy()
+
+ tmp = np.arange(1, d+1)
+ kernel = np.concatenate((tmp, [d+1], tmp[::-1]), dtype=np.float32)
+ kernel = kernel / np.sum(kernel)
+
+ last = lags[0:d][::-1]
+ for i in range(num_silk_frames):
+ frame = lags[i * 4: (i+1) * 4]
+
+ if np.max(np.abs(frame)) == 0:
+ last = frame[4-d:]
+ continue
+
+ if i == num_silk_frames - 1:
+ next = frame[4-d:][::-1]
+ else:
+ next = lags[(i+1) * 4 : (i+1) * 4 + d]
+
+ if np.max(np.abs(next)) == 0:
+ next = frame[4-d:][::-1]
+
+ if np.max(np.abs(last)) == 0:
+ last = frame[0:d][::-1]
+
+ smoothed_frame = np.convolve(np.concatenate((last, frame, next), dtype=np.float32), kernel, mode='valid')
+
+ smoothed_lags[i * 4: (i+1) * 4] = np.round(smoothed_frame)
+
+ last = frame[4-d:]
+
+ return smoothed_lags
+
+def calculate_acorr_window(x, frame_size, lags, history=None, max_lag=300, radius=2, add_double_lag_acorr=False, no_pitch_threshold=32):
+ eps = 1e-9
+
+ lag_multiplier = 2 if add_double_lag_acorr else 1
+
+ if history is None:
+ history = np.zeros(lag_multiplier * max_lag + radius, dtype=x.dtype)
+
+ offset = len(history)
+
+ assert offset >= max_lag + radius
+ assert len(x) % frame_size == 0
+
+ num_frames = len(x) // frame_size
+ lags = lags.copy()
+
+ x_ext = np.concatenate((history, x), dtype=x.dtype)
+
+ d = radius
+ num_acorrs = 2 * d + 1
+ acorrs = np.zeros((num_frames, lag_multiplier * num_acorrs), dtype=x.dtype)
+
+ for idx in range(num_frames):
+ lag = lags[idx].item()
+ frame = x_ext[offset + idx * frame_size : offset + (idx + 1) * frame_size]
+
+ for k in range(lag_multiplier):
+ lag1 = (k + 1) * lag if lag >= no_pitch_threshold else lag
+ for j in range(num_acorrs):
+ past = x_ext[offset + idx * frame_size - lag1 + j - d : offset + (idx + 1) * frame_size - lag1 + j - d]
+ acorrs[idx, j + k * num_acorrs] = np.dot(frame, past) / np.sqrt(np.dot(frame, frame) * np.dot(past, past) + eps)
+
+ return acorrs, lags \ No newline at end of file
diff --git a/dnn/torch/osce/utils/silk_features.py b/dnn/torch/osce/utils/silk_features.py
new file mode 100644
index 00000000..8c5dbf05
--- /dev/null
+++ b/dnn/torch/osce/utils/silk_features.py
@@ -0,0 +1,144 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+
+import numpy as np
+import torch
+
+import scipy
+import scipy.signal
+
+from utils.pitch import hangover, calculate_acorr_window
+from utils.spec import create_filter_bank, cepstrum, log_spectrum, log_spectrum_from_lpc
+
+def spec_from_lpc(a, n_fft=128, eps=1e-9):
+ order = a.shape[-1]
+ assert order + 1 < n_fft
+
+ x = np.zeros((*a.shape[:-1], n_fft ))
+ x[..., 0] = 1
+ x[..., 1:1 + order] = -a
+
+ X = np.fft.fft(x, axis=-1)
+ X = np.abs(X[..., :n_fft//2 + 1]) ** 2
+
+ S = 1 / (X + eps)
+
+ return S
+
+def silk_feature_factory(no_pitch_value=256,
+ acorr_radius=2,
+ pitch_hangover=8,
+ num_bands_clean_spec=64,
+ num_bands_noisy_spec=18,
+ noisy_spec_scale='opus',
+ noisy_apply_dct=True,
+ add_double_lag_acorr=False
+ ):
+
+ w = scipy.signal.windows.cosine(320)
+ fb_clean_spec = create_filter_bank(num_bands_clean_spec, 320, scale='erb', round_center_bins=True, normalize=True)
+ fb_noisy_spec = create_filter_bank(num_bands_noisy_spec, 320, scale=noisy_spec_scale, round_center_bins=True, normalize=True)
+
+ def create_features(noisy, noisy_history, lpcs, gains, ltps, periods):
+
+ periods = periods.copy()
+
+ if pitch_hangover > 0:
+ periods = hangover(periods, num_frames=pitch_hangover)
+
+ periods[periods == 0] = no_pitch_value
+
+ clean_spectrum = 0.3 * log_spectrum_from_lpc(lpcs, fb=fb_clean_spec, n_fft=320)
+
+ if noisy_apply_dct:
+ noisy_cepstrum = np.repeat(
+ cepstrum(np.concatenate((noisy_history[-160:], noisy), dtype=np.float32), 320, fb_noisy_spec, w), 2, 0)
+ else:
+ noisy_cepstrum = np.repeat(
+ log_spectrum(np.concatenate((noisy_history[-160:], noisy), dtype=np.float32), 320, fb_noisy_spec, w), 2, 0)
+
+ log_gains = np.log(gains + 1e-9).reshape(-1, 1)
+
+ acorr, _ = calculate_acorr_window(noisy, 80, periods, noisy_history, radius=acorr_radius, add_double_lag_acorr=add_double_lag_acorr)
+
+ features = np.concatenate((clean_spectrum, noisy_cepstrum, acorr, ltps, log_gains), axis=-1, dtype=np.float32)
+
+ return features, periods.astype(np.int64)
+
+ return create_features
+
+
+
+def load_inference_data(path,
+ no_pitch_value=256,
+ skip=92,
+ preemph=0.85,
+ acorr_radius=2,
+ pitch_hangover=8,
+ num_bands_clean_spec=64,
+ num_bands_noisy_spec=18,
+ noisy_spec_scale='opus',
+ noisy_apply_dct=True,
+ add_double_lag_acorr=False,
+ **kwargs):
+
+ print(f"[load_inference_data]: ignoring keyword arguments {kwargs.keys()}...")
+
+ lpcs = np.fromfile(os.path.join(path, 'features_lpc.f32'), dtype=np.float32).reshape(-1, 16)
+ ltps = np.fromfile(os.path.join(path, 'features_ltp.f32'), dtype=np.float32).reshape(-1, 5)
+ gains = np.fromfile(os.path.join(path, 'features_gain.f32'), dtype=np.float32)
+ periods = np.fromfile(os.path.join(path, 'features_period.s16'), dtype=np.int16)
+ num_bits = np.fromfile(os.path.join(path, 'features_num_bits.s32'), dtype=np.int32).astype(np.float32).reshape(-1, 1)
+ num_bits_smooth = np.fromfile(os.path.join(path, 'features_num_bits_smooth.f32'), dtype=np.float32).reshape(-1, 1)
+
+ # load signal, add back delay and pre-emphasize
+ signal = np.fromfile(os.path.join(path, 'noisy.s16'), dtype=np.int16).astype(np.float32) / (2 ** 15)
+ signal = np.concatenate((np.zeros(skip, dtype=np.float32), signal), dtype=np.float32)
+
+ create_features = silk_feature_factory(no_pitch_value, acorr_radius, pitch_hangover, num_bands_clean_spec, num_bands_noisy_spec, noisy_spec_scale, noisy_apply_dct, add_double_lag_acorr)
+
+ num_frames = min((len(signal) // 320) * 4, len(lpcs))
+ signal = signal[: num_frames * 80]
+ lpcs = lpcs[: num_frames]
+ ltps = ltps[: num_frames]
+ gains = gains[: num_frames]
+ periods = periods[: num_frames]
+ num_bits = num_bits[: num_frames // 4]
+ num_bits_smooth = num_bits[: num_frames // 4]
+
+ numbits = np.repeat(np.concatenate((num_bits, num_bits_smooth), axis=-1, dtype=np.float32), 4, axis=0)
+
+ features, periods = create_features(signal, np.zeros(350, dtype=signal.dtype), lpcs, gains, ltps, periods)
+
+ if preemph > 0:
+ signal[1:] -= preemph * signal[:-1]
+
+ return torch.from_numpy(signal), torch.from_numpy(features), torch.from_numpy(periods), torch.from_numpy(numbits)
diff --git a/dnn/torch/osce/utils/softquant.py b/dnn/torch/osce/utils/softquant.py
new file mode 100644
index 00000000..5fca5b2a
--- /dev/null
+++ b/dnn/torch/osce/utils/softquant.py
@@ -0,0 +1,110 @@
+import torch
+
+@torch.no_grad()
+def compute_optimal_scale(weight):
+ with torch.no_grad():
+ n_out, n_in = weight.shape
+ assert n_in % 4 == 0
+ if n_out % 8:
+ # add padding
+ pad = n_out - n_out % 8
+ weight = torch.cat((weight, torch.zeros((pad, n_in), dtype=weight.dtype, device=weight.device)), dim=0)
+
+ weight_max_abs, _ = torch.max(torch.abs(weight), dim=1)
+ weight_max_sum, _ = torch.max(torch.abs(weight[:, : n_in : 2] + weight[:, 1 : n_in : 2]), dim=1)
+ scale_max = weight_max_abs / 127
+ scale_sum = weight_max_sum / 129
+
+ scale = torch.maximum(scale_max, scale_sum)
+
+ return scale[:n_out]
+
+@torch.no_grad()
+def q_scaled_noise(module, weight):
+ if isinstance(module, torch.nn.Conv1d):
+ w = weight.permute(0, 2, 1).flatten(1)
+ noise = torch.rand_like(w) - 0.5
+ scale = compute_optimal_scale(w)
+ noise = noise * scale.unsqueeze(-1)
+ noise = noise.reshape(weight.size(0), weight.size(2), weight.size(1)).permute(0, 2, 1)
+ elif isinstance(module, torch.nn.ConvTranspose1d):
+ i, o, k = weight.shape
+ w = weight.permute(2, 1, 0).reshape(k * o, i)
+ noise = torch.rand_like(w) - 0.5
+ scale = compute_optimal_scale(w)
+ noise = noise * scale.unsqueeze(-1)
+ noise = noise.reshape(k, o, i).permute(2, 1, 0)
+ elif len(weight.shape) == 2:
+ noise = torch.rand_like(weight) - 0.5
+ scale = compute_optimal_scale(weight)
+ noise = noise * scale.unsqueeze(-1)
+ else:
+ raise ValueError('unknown quantization setting')
+
+ return noise
+
+class SoftQuant:
+ name: str
+
+ def __init__(self, names: str, scale: float) -> None:
+ self.names = names
+ self.quantization_noise = None
+ self.scale = scale
+
+ def __call__(self, module, inputs, *args, before=True):
+ if not module.training: return
+
+ if before:
+ self.quantization_noise = dict()
+ for name in self.names:
+ weight = getattr(module, name)
+ if self.scale is None:
+ self.quantization_noise[name] = q_scaled_noise(module, weight)
+ else:
+ self.quantization_noise[name] = \
+ self.scale * weight.abs().max() * (torch.rand_like(weight) - 0.5)
+ with torch.no_grad():
+ weight.data[:] = weight + self.quantization_noise[name]
+ else:
+ for name in self.names:
+ weight = getattr(module, name)
+ with torch.no_grad():
+ weight.data[:] = weight - self.quantization_noise[name]
+ self.quantization_noise = None
+
+ def apply(module, names=['weight'], scale=None):
+ fn = SoftQuant(names, scale)
+
+ for name in names:
+ if not hasattr(module, name):
+ raise ValueError("")
+
+ fn_before = lambda *x : fn(*x, before=True)
+ fn_after = lambda *x : fn(*x, before=False)
+ setattr(fn_before, 'sqm', fn)
+ setattr(fn_after, 'sqm', fn)
+
+
+ module.register_forward_pre_hook(fn_before)
+ module.register_forward_hook(fn_after)
+
+ module
+
+ return fn
+
+
+def soft_quant(module, names=['weight'], scale=None):
+ fn = SoftQuant.apply(module, names, scale)
+ return module
+
+def remove_soft_quant(module, names=['weight']):
+ for k, hook in module._forward_pre_hooks.items():
+ if hasattr(hook, 'sqm'):
+ if isinstance(hook.sqm, SoftQuant) and hook.sqm.names == names:
+ del module._forward_pre_hooks[k]
+ for k, hook in module._forward_hooks.items():
+ if hasattr(hook, 'sqm'):
+ if isinstance(hook.sqm, SoftQuant) and hook.sqm.names == names:
+ del module._forward_hooks[k]
+
+ return module \ No newline at end of file
diff --git a/dnn/torch/osce/utils/spec.py b/dnn/torch/osce/utils/spec.py
new file mode 100644
index 00000000..59f53538
--- /dev/null
+++ b/dnn/torch/osce/utils/spec.py
@@ -0,0 +1,210 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import math as m
+import numpy as np
+import scipy
+import scipy.fftpack
+import torch
+
+def erb(f):
+ return 24.7 * (4.37 * f + 1)
+
+def inv_erb(e):
+ return (e / 24.7 - 1) / 4.37
+
+def bark(f):
+ return 6 * m.asinh(f/600)
+
+def inv_bark(b):
+ return 600 * m.sinh(b / 6)
+
+
+scale_dict = {
+ 'bark': [bark, inv_bark],
+ 'erb': [erb, inv_erb]
+}
+
+def gen_filterbank(N, Fs=16000, keep_size=False):
+ in_freq = (np.arange(N+1, dtype='float32')/N*Fs/2)[None,:]
+ M = N + 1 if keep_size else N
+ out_freq = (np.arange(M, dtype='float32')/N*Fs/2)[:,None]
+ #ERB from B.C.J Moore, An Introduction to the Psychology of Hearing, 5th Ed., page 73.
+ ERB_N = 24.7 + .108*in_freq
+ delta = np.abs(in_freq-out_freq)/ERB_N
+ center = (delta<.5).astype('float32')
+ R = -12*center*delta**2 + (1-center)*(3-12*delta)
+ RE = 10.**(R/10.)
+ norm = np.sum(RE, axis=1)
+ RE = RE/norm[:, np.newaxis]
+ return torch.from_numpy(RE)
+
+def create_filter_bank(num_bands, n_fft=320, fs=16000, scale='bark', round_center_bins=False, return_upper=False, normalize=False):
+
+ f0 = 0
+ num_bins = n_fft // 2 + 1
+ f1 = fs / n_fft * (num_bins - 1)
+ fstep = fs / n_fft
+
+ if scale == 'opus':
+ bins_5ms = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 34, 40]
+ fac = 1000 * n_fft / fs / 5
+ if num_bands != 18:
+ print("warning: requested Opus filter bank with num_bands != 18. Adjusting num_bands.")
+ num_bands = 18
+ center_bins = np.array([fac * bin for bin in bins_5ms])
+ else:
+ to_scale, from_scale = scale_dict[scale]
+
+ s0 = to_scale(f0)
+ s1 = to_scale(f1)
+
+ center_freqs = np.array([f0] + [from_scale(s0 + i * (s1 - s0) / (num_bands)) for i in range(1, num_bands - 1)] + [f1])
+ center_bins = (center_freqs - f0) / fstep
+
+ if round_center_bins:
+ center_bins = np.round(center_bins)
+
+ filter_bank = np.zeros((num_bands, num_bins))
+
+ band = 0
+ for bin in range(num_bins):
+ # update band index
+ if bin > center_bins[band + 1]:
+ band += 1
+
+ # calculate filter coefficients
+ frac = (center_bins[band + 1] - bin) / (center_bins[band + 1] - center_bins[band])
+ filter_bank[band][bin] = frac
+ filter_bank[band + 1][bin] = 1 - frac
+
+ if return_upper:
+ extend = n_fft - num_bins
+ filter_bank = np.concatenate((filter_bank, np.fliplr(filter_bank[:, 1:extend+1])), axis=1)
+
+ if normalize:
+ filter_bank = filter_bank / np.sum(filter_bank, axis=1).reshape(-1, 1)
+
+ return filter_bank
+
+
+def compressed_log_spec(pspec):
+
+ lpspec = np.zeros_like(pspec)
+ num_bands = pspec.shape[-1]
+
+ log_max = -2
+ follow = -2
+
+ for i in range(num_bands):
+ tmp = np.log10(pspec[i] + 1e-9)
+ tmp = max(log_max, max(follow - 2.5, tmp))
+ lpspec[i] = tmp
+ log_max = max(log_max, tmp)
+ follow = max(follow - 2.5, tmp)
+
+ return lpspec
+
+def log_spectrum_from_lpc(a, fb=None, n_fft=320, eps=1e-9, gamma=1, compress=False, power=1):
+ """ calculates cepstrum from SILK lpcs """
+ order = a.shape[-1]
+ assert order + 1 < n_fft
+
+ a = a * (gamma ** (1 + np.arange(order))).astype(np.float32)
+
+ x = np.zeros((*a.shape[:-1], n_fft ))
+ x[..., 0] = 1
+ x[..., 1:1 + order] = -a
+
+ X = np.fft.fft(x, axis=-1)
+ X = np.abs(X[..., :n_fft//2 + 1]) ** power
+
+ S = 1 / (X + eps)
+
+ if fb is None:
+ Sf = S
+ else:
+ Sf = np.matmul(S, fb.T)
+
+ if compress:
+ Sf = np.apply_along_axis(compressed_log_spec, -1, Sf)
+ else:
+ Sf = np.log(Sf + eps)
+
+ return Sf
+
+def cepstrum_from_lpc(a, fb=None, n_fft=320, eps=1e-9, gamma=1, compress=False):
+ """ calculates cepstrum from SILK lpcs """
+
+ Sf = log_spectrum_from_lpc(a, fb, n_fft, eps, gamma, compress)
+
+ cepstrum = scipy.fftpack.dct(Sf, 2, norm='ortho')
+
+ return cepstrum
+
+
+
+def log_spectrum(x, frame_size, fb=None, window=None, power=1):
+ """ calculate cepstrum on 50% overlapping frames """
+
+ assert(2*len(x)) % frame_size == 0
+ assert frame_size % 2 == 0
+
+ n = len(x)
+ num_even = n // frame_size
+ num_odd = (n - frame_size // 2) // frame_size
+ num_bins = frame_size // 2 + 1
+
+ x_even = x[:num_even * frame_size].reshape(-1, frame_size)
+ x_odd = x[frame_size//2 : frame_size//2 + frame_size * num_odd].reshape(-1, frame_size)
+
+ x_unfold = np.empty((x_even.size + x_odd.size), dtype=x.dtype).reshape((-1, frame_size))
+ x_unfold[::2, :] = x_even
+ x_unfold[1::2, :] = x_odd
+
+ if window is not None:
+ x_unfold *= window.reshape(1, -1)
+
+ X = np.abs(np.fft.fft(x_unfold, n=frame_size, axis=-1))[:, :num_bins] ** power
+
+ if fb is not None:
+ X = np.matmul(X, fb.T)
+
+
+ return np.log(X + 1e-9)
+
+
+def cepstrum(x, frame_size, fb=None, window=None):
+ """ calculate cepstrum on 50% overlapping frames """
+
+ X = log_spectrum(x, frame_size, fb, window)
+
+ cepstrum = scipy.fftpack.dct(X, 2, norm='ortho')
+
+ return cepstrum \ No newline at end of file
diff --git a/dnn/torch/osce/utils/templates.py b/dnn/torch/osce/utils/templates.py
new file mode 100644
index 00000000..5fc84ef1
--- /dev/null
+++ b/dnn/torch/osce/utils/templates.py
@@ -0,0 +1,347 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+
+setup_dict = dict()
+
+lace_setup = {
+ 'dataset': '/local/datasets/silk_enhancement_v2_full_6to64kbps/training',
+ 'validation_dataset': '/local/datasets/silk_enhancement_v2_full_6to64kbps/validation',
+ 'model': {
+ 'name': 'lace',
+ 'args': [],
+ 'kwargs': {
+ 'comb_gain_limit_db': 10,
+ 'cond_dim': 128,
+ 'conv_gain_limits_db': [-12, 12],
+ 'global_gain_limits_db': [-6, 6],
+ 'hidden_feature_dim': 96,
+ 'kernel_size': 15,
+ 'num_features': 93,
+ 'numbits_embedding_dim': 8,
+ 'numbits_range': [50, 650],
+ 'partial_lookahead': True,
+ 'pitch_embedding_dim': 64,
+ 'pitch_max': 300,
+ 'preemph': 0.85,
+ 'skip': 91,
+ 'softquant': True,
+ 'sparsify': False,
+ 'sparsification_density': 0.4,
+ 'sparsification_schedule': [10000, 40000, 200]
+ }
+ },
+ 'data': {
+ 'frames_per_sample': 100,
+ 'no_pitch_value': 7,
+ 'preemph': 0.85,
+ 'skip': 91,
+ 'pitch_hangover': 8,
+ 'acorr_radius': 2,
+ 'num_bands_clean_spec': 64,
+ 'num_bands_noisy_spec': 18,
+ 'noisy_spec_scale': 'opus',
+ 'pitch_hangover': 0,
+ },
+ 'training': {
+ 'batch_size': 256,
+ 'lr': 5.e-4,
+ 'lr_decay_factor': 2.5e-5,
+ 'epochs': 50,
+ 'loss': {
+ 'w_l1': 0,
+ 'w_lm': 0,
+ 'w_logmel': 0,
+ 'w_sc': 0,
+ 'w_wsc': 0,
+ 'w_xcorr': 0,
+ 'w_sxcorr': 1,
+ 'w_l2': 10,
+ 'w_slm': 2
+ }
+ }
+}
+
+
+nolace_setup = {
+ 'dataset': '/local/datasets/silk_enhancement_v2_full_6to64kbps/training',
+ 'validation_dataset': '/local/datasets/silk_enhancement_v2_full_6to64kbps/validation',
+ 'model': {
+ 'name': 'nolace',
+ 'args': [],
+ 'kwargs': {
+ 'avg_pool_k': 4,
+ 'comb_gain_limit_db': 10,
+ 'cond_dim': 256,
+ 'conv_gain_limits_db': [-12, 12],
+ 'global_gain_limits_db': [-6, 6],
+ 'hidden_feature_dim': 96,
+ 'kernel_size': 15,
+ 'num_features': 93,
+ 'numbits_embedding_dim': 8,
+ 'numbits_range': [50, 650],
+ 'partial_lookahead': True,
+ 'pitch_embedding_dim': 64,
+ 'pitch_max': 300,
+ 'preemph': 0.85,
+ 'skip': 91,
+ 'softquant': True,
+ 'sparsify': False,
+ 'sparsification_density': 0.4,
+ 'sparsification_schedule': [10000, 40000, 200]
+ }
+ },
+ 'data': {
+ 'frames_per_sample': 100,
+ 'no_pitch_value': 7,
+ 'preemph': 0.85,
+ 'skip': 91,
+ 'pitch_hangover': 8,
+ 'acorr_radius': 2,
+ 'num_bands_clean_spec': 64,
+ 'num_bands_noisy_spec': 18,
+ 'noisy_spec_scale': 'opus',
+ 'pitch_hangover': 0,
+ },
+ 'training': {
+ 'batch_size': 256,
+ 'lr': 5.e-4,
+ 'lr_decay_factor': 2.5e-5,
+ 'epochs': 50,
+ 'loss': {
+ 'w_l1': 0,
+ 'w_lm': 0,
+ 'w_logmel': 0,
+ 'w_sc': 0,
+ 'w_wsc': 0,
+ 'w_xcorr': 0,
+ 'w_sxcorr': 1,
+ 'w_l2': 10,
+ 'w_slm': 2
+ }
+ }
+}
+
+nolace_setup_adv = {
+ 'dataset': '/local/datasets/silk_enhancement_v2_full_6to64kbps/training',
+ 'model': {
+ 'name': 'nolace',
+ 'args': [],
+ 'kwargs': {
+ 'avg_pool_k': 4,
+ 'comb_gain_limit_db': 10,
+ 'cond_dim': 256,
+ 'conv_gain_limits_db': [-12, 12],
+ 'global_gain_limits_db': [-6, 6],
+ 'hidden_feature_dim': 96,
+ 'kernel_size': 15,
+ 'num_features': 93,
+ 'numbits_embedding_dim': 8,
+ 'numbits_range': [50, 650],
+ 'partial_lookahead': True,
+ 'pitch_embedding_dim': 64,
+ 'pitch_max': 300,
+ 'preemph': 0.85,
+ 'skip': 91,
+ 'softquant': True,
+ 'sparsify': False,
+ 'sparsification_density': 0.4,
+ 'sparsification_schedule': [0, 0, 200]
+ }
+ },
+ 'data': {
+ 'frames_per_sample': 100,
+ 'no_pitch_value': 7,
+ 'preemph': 0.85,
+ 'skip': 91,
+ 'pitch_hangover': 8,
+ 'acorr_radius': 2,
+ 'num_bands_clean_spec': 64,
+ 'num_bands_noisy_spec': 18,
+ 'noisy_spec_scale': 'opus',
+ 'pitch_hangover': 0,
+ },
+ 'discriminator': {
+ 'args': [],
+ 'kwargs': {
+ 'architecture': 'free',
+ 'design': 'f_down',
+ 'fft_sizes_16k': [
+ 64,
+ 128,
+ 256,
+ 512,
+ 1024,
+ 2048,
+ ],
+ 'freq_roi': [0, 7400],
+ 'fs': 16000,
+ 'max_channels': 256,
+ 'noise_gain': 0.0,
+ },
+ 'name': 'fdmresdisc',
+ },
+ 'training': {
+ 'adv_target': 'target_orig',
+ 'batch_size': 64,
+ 'epochs': 50,
+ 'gen_lr_reduction': 1,
+ 'lambda_feat': 1.0,
+ 'lambda_reg': 0.6,
+ 'loss': {
+ 'w_l1': 0,
+ 'w_l2': 10,
+ 'w_lm': 0,
+ 'w_logmel': 0,
+ 'w_sc': 0,
+ 'w_slm': 20,
+ 'w_sxcorr': 1,
+ 'w_wsc': 0,
+ 'w_xcorr': 0,
+ },
+ 'lr': 0.0001,
+ 'lr_decay_factor': 2.5e-09,
+ }
+}
+
+
+lavoce_setup = {
+ 'data': {
+ 'frames_per_sample': 100,
+ 'target': 'signal'
+ },
+ 'dataset': '/local/datasets/lpcnet_large/training',
+ 'model': {
+ 'args': [],
+ 'kwargs': {
+ 'comb_gain_limit_db': 10,
+ 'cond_dim': 256,
+ 'conv_gain_limits_db': [-12, 12],
+ 'global_gain_limits_db': [-6, 6],
+ 'kernel_size': 15,
+ 'num_features': 19,
+ 'pitch_embedding_dim': 64,
+ 'pitch_max': 300,
+ 'preemph': 0.85,
+ 'pulses': True
+ },
+ 'name': 'lavoce'
+ },
+ 'training': {
+ 'batch_size': 256,
+ 'epochs': 50,
+ 'loss': {
+ 'w_l1': 0,
+ 'w_l2': 0,
+ 'w_lm': 0,
+ 'w_logmel': 0,
+ 'w_sc': 0,
+ 'w_slm': 2,
+ 'w_sxcorr': 1,
+ 'w_wsc': 0,
+ 'w_xcorr': 0
+ },
+ 'lr': 0.0005,
+ 'lr_decay_factor': 2.5e-05
+ },
+ 'validation_dataset': '/local/datasets/lpcnet_large/validation'
+}
+
+lavoce_setup_adv = {
+ 'data': {
+ 'frames_per_sample': 100,
+ 'target': 'signal'
+ },
+ 'dataset': '/local/datasets/lpcnet_large/training',
+ 'discriminator': {
+ 'args': [],
+ 'kwargs': {
+ 'architecture': 'free',
+ 'design': 'f_down',
+ 'fft_sizes_16k': [
+ 64,
+ 128,
+ 256,
+ 512,
+ 1024,
+ 2048,
+ ],
+ 'freq_roi': [0, 7400],
+ 'fs': 16000,
+ 'max_channels': 256,
+ 'noise_gain': 0.0,
+ },
+ 'name': 'fdmresdisc',
+ },
+ 'model': {
+ 'args': [],
+ 'kwargs': {
+ 'comb_gain_limit_db': 10,
+ 'cond_dim': 256,
+ 'conv_gain_limits_db': [-12, 12],
+ 'global_gain_limits_db': [-6, 6],
+ 'kernel_size': 15,
+ 'num_features': 19,
+ 'pitch_embedding_dim': 64,
+ 'pitch_max': 300,
+ 'preemph': 0.85,
+ 'pulses': True
+ },
+ 'name': 'lavoce'
+ },
+ 'training': {
+ 'batch_size': 64,
+ 'epochs': 50,
+ 'gen_lr_reduction': 1,
+ 'lambda_feat': 1.0,
+ 'lambda_reg': 0.6,
+ 'loss': {
+ 'w_l1': 0,
+ 'w_l2': 0,
+ 'w_lm': 0,
+ 'w_logmel': 0,
+ 'w_sc': 0,
+ 'w_slm': 2,
+ 'w_sxcorr': 1,
+ 'w_wsc': 0,
+ 'w_xcorr': 0
+ },
+ 'lr': 0.0001,
+ 'lr_decay_factor': 2.5e-09
+ },
+}
+
+
+setup_dict = {
+ 'lace': lace_setup,
+ 'nolace': nolace_setup,
+ 'nolace_adv': nolace_setup_adv,
+ 'lavoce': lavoce_setup,
+ 'lavoce_adv': lavoce_setup_adv
+}
diff --git a/dnn/torch/plc/export_plc.py b/dnn/torch/plc/export_plc.py
new file mode 100644
index 00000000..7f153c4c
--- /dev/null
+++ b/dnn/torch/plc/export_plc.py
@@ -0,0 +1,100 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../weight-exchange'))
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('checkpoint', type=str, help='model checkpoint')
+parser.add_argument('output_dir', type=str, help='output folder')
+
+args = parser.parse_args()
+
+import torch
+import numpy as np
+
+import plc
+from wexchange.torch import dump_torch_weights
+from wexchange.c_export import CWriter, print_vector
+
+def c_export(args, model):
+
+ message = f"Auto generated from checkpoint {os.path.basename(args.checkpoint)}"
+
+ writer = CWriter(os.path.join(args.output_dir, "plc_data"), message=message, model_struct_name='PLCModel')
+ writer.header.write(
+f"""
+#include "opus_types.h"
+"""
+ )
+
+ dense_layers = [
+ ('dense_in', "plc_dense_in"),
+ ('dense_out', "plc_dense_out")
+ ]
+
+
+ for name, export_name in dense_layers:
+ layer = model.get_submodule(name)
+ dump_torch_weights(writer, layer, name=export_name, verbose=True, quantize=False, scale=None)
+
+
+ gru_layers = [
+ ("gru1", "plc_gru1"),
+ ("gru2", "plc_gru2"),
+ ]
+
+ max_rnn_units = max([dump_torch_weights(writer, model.get_submodule(name), export_name, verbose=True, input_sparse=False, quantize=True, scale=None, recurrent_scale=None)
+ for name, export_name in gru_layers])
+
+ writer.header.write(
+f"""
+
+#define PLC_MAX_RNN_UNITS {max_rnn_units}
+
+"""
+ )
+
+ writer.close()
+
+
+if __name__ == "__main__":
+
+ os.makedirs(args.output_dir, exist_ok=True)
+ checkpoint = torch.load(args.checkpoint, map_location='cpu')
+ model = plc.PLC(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+ model.load_state_dict(checkpoint['state_dict'], strict=False)
+ #checkpoint = torch.load(args.checkpoint, map_location='cpu')
+ #model.load_state_dict(checkpoint['state_dict'])
+ c_export(args, model)
diff --git a/dnn/torch/plc/plc.py b/dnn/torch/plc/plc.py
new file mode 100644
index 00000000..f08e564d
--- /dev/null
+++ b/dnn/torch/plc/plc.py
@@ -0,0 +1,144 @@
+import numpy as np
+import torch
+from torch import nn
+import torch.nn.functional as F
+from torch.nn.utils import weight_norm
+import math
+
+fid_dict = {}
+def dump_signal(x, filename):
+ return
+ if filename in fid_dict:
+ fid = fid_dict[filename]
+ else:
+ fid = open(filename, "w")
+ fid_dict[filename] = fid
+ x = x.detach().numpy().astype('float32')
+ x.tofile(fid)
+
+
+class IDCT(nn.Module):
+ def __init__(self, N, device=None):
+ super(IDCT, self).__init__()
+
+ self.N = N
+ n = torch.arange(N, device=device)
+ k = torch.arange(N, device=device)
+ self.table = torch.cos(torch.pi/N * (n[:,None]+.5) * k[None,:])
+ self.table[:,0] = self.table[:,0] * math.sqrt(.5)
+ self.table = self.table / math.sqrt(N/2)
+
+ def forward(self, x):
+ return F.linear(x, self.table, None)
+
+def plc_loss(N, device=None, alpha=1.0, bias=1.):
+ idct = IDCT(18, device=device)
+ def loss(y_true,y_pred):
+ mask = y_true[:,:,-1:]
+ y_true = y_true[:,:,:-1]
+ e = (y_pred - y_true)*mask
+ e_bands = idct(e[:,:,:-2])
+ bias_mask = torch.clamp(4*y_true[:,:,-1:], min=0., max=1.)
+ l1_loss = torch.mean(torch.abs(e))
+ ceps_loss = torch.mean(torch.abs(e[:,:,:-2]))
+ band_loss = torch.mean(torch.abs(e_bands))
+ biased_loss = torch.mean(bias_mask*torch.clamp(e_bands, min=0.))
+ pitch_loss1 = torch.mean(torch.clamp(torch.abs(e[:,:,18:19]),max=1.))
+ pitch_loss = torch.mean(torch.clamp(torch.abs(e[:,:,18:19]),max=.4))
+ voice_bias = torch.mean(torch.clamp(-e[:,:,-1:], min=0.))
+ tot = l1_loss + 0.1*voice_bias + alpha*(band_loss + bias*biased_loss) + pitch_loss1 + 8*pitch_loss
+ return tot, l1_loss, ceps_loss, band_loss, pitch_loss
+ return loss
+
+
+# weight initialization and clipping
+def init_weights(module):
+ if isinstance(module, nn.GRU):
+ for p in module.named_parameters():
+ if p[0].startswith('weight_hh_'):
+ nn.init.orthogonal_(p[1])
+
+
+class GLU(nn.Module):
+ def __init__(self, feat_size):
+ super(GLU, self).__init__()
+
+ torch.manual_seed(5)
+
+ self.gate = weight_norm(nn.Linear(feat_size, feat_size, bias=False))
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d)\
+ or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x):
+
+ out = x * torch.sigmoid(self.gate(x))
+
+ return out
+
+class FWConv(nn.Module):
+ def __init__(self, in_size, out_size, kernel_size=2):
+ super(FWConv, self).__init__()
+
+ torch.manual_seed(5)
+
+ self.in_size = in_size
+ self.kernel_size = kernel_size
+ self.conv = weight_norm(nn.Linear(in_size*self.kernel_size, out_size, bias=False))
+ self.glu = GLU(out_size)
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d)\
+ or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x, state):
+ xcat = torch.cat((state, x), -1)
+ out = self.glu(torch.tanh(self.conv(xcat)))
+ return out, xcat[:,self.in_size:]
+
+def n(x):
+ return torch.clamp(x + (1./127.)*(torch.rand_like(x)-.5), min=-1., max=1.)
+
+class PLC(nn.Module):
+ def __init__(self, features_in=57, features_out=20, cond_size=128, gru_size=128):
+ super(PLC, self).__init__()
+
+ self.features_in = features_in
+ self.features_out = features_out
+ self.cond_size = cond_size
+ self.gru_size = gru_size
+
+ self.dense_in = nn.Linear(self.features_in, self.cond_size)
+ self.gru1 = nn.GRU(self.cond_size, self.gru_size, batch_first=True)
+ self.gru2 = nn.GRU(self.gru_size, self.gru_size, batch_first=True)
+ self.dense_out = nn.Linear(self.gru_size, features_out)
+
+ self.apply(init_weights)
+ nb_params = sum(p.numel() for p in self.parameters())
+ print(f"plc model: {nb_params} weights")
+
+ def forward(self, features, lost, states=None):
+ device = features.device
+ batch_size = features.size(0)
+ if states is None:
+ gru1_state = torch.zeros((1, batch_size, self.gru_size), device=device)
+ gru2_state = torch.zeros((1, batch_size, self.gru_size), device=device)
+ else:
+ gru1_state = states[0]
+ gru2_state = states[1]
+ x = torch.cat([features, lost], dim=-1)
+ x = torch.tanh(self.dense_in(x))
+ gru1_out, gru1_state = self.gru1(x, gru1_state)
+ gru2_out, gru2_state = self.gru2(gru1_out, gru2_state)
+ return self.dense_out(gru2_out), [gru1_state, gru2_state]
diff --git a/dnn/torch/plc/plc_dataset.py b/dnn/torch/plc/plc_dataset.py
new file mode 100644
index 00000000..2dfaaaf2
--- /dev/null
+++ b/dnn/torch/plc/plc_dataset.py
@@ -0,0 +1,60 @@
+import torch
+import numpy as np
+
+class PLCDataset(torch.utils.data.Dataset):
+ def __init__(self,
+ feature_file,
+ loss_file,
+ sequence_length=1000,
+ nb_features=20,
+ nb_burg_features=36,
+ lpc_order=16):
+
+ self.features_in = nb_features + nb_burg_features
+ self.nb_burg_features = nb_burg_features
+ total_features = self.features_in + lpc_order
+ self.sequence_length = sequence_length
+ self.nb_features = nb_features
+
+ self.features = np.memmap(feature_file, dtype='float32', mode='r')
+ self.lost = np.memmap(loss_file, dtype='int8', mode='r')
+ self.lost = self.lost.astype('float32')
+
+ self.nb_sequences = self.features.shape[0]//self.sequence_length//total_features
+
+ self.features = self.features[:self.nb_sequences*self.sequence_length*total_features]
+ self.features = self.features.reshape((self.nb_sequences, self.sequence_length, total_features))
+ self.features = self.features[:,:,:self.features_in]
+
+ #self.lost = self.lost[:(len(self.lost)//features.shape[1]-1)*features.shape[1]]
+ #self.lost = self.lost.reshape((-1, self.sequence_length))
+
+ def __len__(self):
+ return self.nb_sequences
+
+ def __getitem__(self, index):
+ features = self.features[index, :, :]
+ burg_lost = (np.random.rand(features.shape[0]) > .1).astype('float32')
+ burg_lost = np.reshape(burg_lost, (features.shape[0], 1))
+ burg_mask = np.tile(burg_lost, (1,self.nb_burg_features))
+
+ lost_offset = np.random.randint(0, high=self.lost.shape[0]-self.sequence_length)
+ lost = self.lost[lost_offset:lost_offset+self.sequence_length]
+ #randomly add a few 10-ms losses so that the model learns to handle them
+ lost = lost * (np.random.rand(lost.shape[-1]) > .02).astype('float32')
+ #randomly break long consecutive losses so we don't try too hard to predict them
+ lost = 1 - ((1-lost) * (np.random.rand(lost.shape[-1]) > .1).astype('float32'))
+ lost = np.reshape(lost, (features.shape[0], 1))
+ lost_mask = np.tile(lost, (1,features.shape[-1]))
+ in_features = features*lost_mask
+ in_features[:,:self.nb_burg_features] = in_features[:,:self.nb_burg_features]*burg_mask
+
+ #For the first frame after a loss, we don't have valid features, but the Burg estimate is valid.
+ #in_features[:,1:,self.nb_burg_features:] = in_features[:,1:,self.nb_burg_features:]*lost_mask[:,:-1,self.nb_burg_features:]
+ out_lost = np.copy(lost)
+ #out_lost[:,1:,:] = out_lost[:,1:,:]*out_lost[:,:-1,:]
+
+ out_features = np.concatenate([features[:,self.nb_burg_features:], 1.-out_lost], axis=-1)
+ burg_sign = 2*burg_lost - 1
+ # last dim is 1 for received packet, 0 for lost packet, and -1 when just the Burg info is missing
+ return in_features*lost_mask, lost*burg_sign, out_features
diff --git a/dnn/torch/plc/train_plc.py b/dnn/torch/plc/train_plc.py
new file mode 100644
index 00000000..12b31c4e
--- /dev/null
+++ b/dnn/torch/plc/train_plc.py
@@ -0,0 +1,145 @@
+import os
+import argparse
+import random
+import numpy as np
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+import tqdm
+
+import plc
+from plc_dataset import PLCDataset
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('features', type=str, help='path to feature file in .f32 format')
+parser.add_argument('loss', type=str, help='path to signal file in .s8 format')
+parser.add_argument('output', type=str, help='path to output folder')
+
+parser.add_argument('--suffix', type=str, help="model name suffix", default="")
+parser.add_argument('--cuda-visible-devices', type=str, help="comma separates list of cuda visible device indices, default: CUDA_VISIBLE_DEVICES", default=None)
+
+
+model_group = parser.add_argument_group(title="model parameters")
+model_group.add_argument('--cond-size', type=int, help="first conditioning size, default: 128", default=128)
+model_group.add_argument('--gru-size', type=int, help="GRU size, default: 128", default=128)
+
+training_group = parser.add_argument_group(title="training parameters")
+training_group.add_argument('--batch-size', type=int, help="batch size, default: 512", default=512)
+training_group.add_argument('--lr', type=float, help='learning rate, default: 1e-3', default=1e-3)
+training_group.add_argument('--epochs', type=int, help='number of training epochs, default: 20', default=20)
+training_group.add_argument('--sequence-length', type=int, help='sequence length, default: 15', default=15)
+training_group.add_argument('--lr-decay', type=float, help='learning rate decay factor, default: 1e-4', default=1e-4)
+training_group.add_argument('--initial-checkpoint', type=str, help='initial checkpoint to start training from, default: None', default=None)
+
+args = parser.parse_args()
+
+if args.cuda_visible_devices != None:
+ os.environ['CUDA_VISIBLE_DEVICES'] = args.cuda_visible_devices
+
+# checkpoints
+checkpoint_dir = os.path.join(args.output, 'checkpoints')
+checkpoint = dict()
+os.makedirs(checkpoint_dir, exist_ok=True)
+
+
+# training parameters
+batch_size = args.batch_size
+lr = args.lr
+epochs = args.epochs
+sequence_length = args.sequence_length
+lr_decay = args.lr_decay
+
+adam_betas = [0.8, 0.95]
+adam_eps = 1e-8
+features_file = args.features
+loss_file = args.loss
+
+# model parameters
+cond_size = args.cond_size
+
+
+checkpoint['batch_size'] = batch_size
+checkpoint['lr'] = lr
+checkpoint['lr_decay'] = lr_decay
+checkpoint['epochs'] = epochs
+checkpoint['sequence_length'] = sequence_length
+checkpoint['adam_betas'] = adam_betas
+
+
+device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
+
+checkpoint['model_args'] = ()
+checkpoint['model_kwargs'] = {'cond_size': cond_size, 'gru_size': args.gru_size}
+print(checkpoint['model_kwargs'])
+model = plc.PLC(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+
+if type(args.initial_checkpoint) != type(None):
+ checkpoint = torch.load(args.initial_checkpoint, map_location='cpu')
+ model.load_state_dict(checkpoint['state_dict'], strict=False)
+
+checkpoint['state_dict'] = model.state_dict()
+
+
+dataset = PLCDataset(features_file, loss_file, sequence_length=sequence_length)
+dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, drop_last=True, num_workers=4)
+
+
+optimizer = torch.optim.AdamW(model.parameters(), lr=lr, betas=adam_betas, eps=adam_eps)
+
+
+# learning rate scheduler
+scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer=optimizer, lr_lambda=lambda x : 1 / (1 + lr_decay * x))
+
+states = None
+
+plc_loss = plc.plc_loss(18, device=device)
+if __name__ == '__main__':
+ model.to(device)
+
+ for epoch in range(1, epochs + 1):
+
+ running_loss = 0
+ running_l1_loss = 0
+ running_ceps_loss = 0
+ running_band_loss = 0
+ running_pitch_loss = 0
+
+ print(f"training epoch {epoch}...")
+ with tqdm.tqdm(dataloader, unit='batch') as tepoch:
+ for i, (features, lost, target) in enumerate(tepoch):
+ optimizer.zero_grad()
+ features = features.to(device)
+ lost = lost.to(device)
+ target = target.to(device)
+
+ out, states = model(features, lost)
+
+ loss, l1_loss, ceps_loss, band_loss, pitch_loss = plc_loss(target, out)
+
+ loss.backward()
+ optimizer.step()
+
+ #model.clip_weights()
+
+ scheduler.step()
+
+ running_loss += loss.detach().cpu().item()
+ running_l1_loss += l1_loss.detach().cpu().item()
+ running_ceps_loss += ceps_loss.detach().cpu().item()
+ running_band_loss += band_loss.detach().cpu().item()
+ running_pitch_loss += pitch_loss.detach().cpu().item()
+ tepoch.set_postfix(loss=f"{running_loss/(i+1):8.5f}",
+ l1_loss=f"{running_l1_loss/(i+1):8.5f}",
+ ceps_loss=f"{running_ceps_loss/(i+1):8.5f}",
+ band_loss=f"{running_band_loss/(i+1):8.5f}",
+ pitch_loss=f"{running_pitch_loss/(i+1):8.5f}",
+ )
+
+ # save checkpoint
+ checkpoint_path = os.path.join(checkpoint_dir, f'plc{args.suffix}_{epoch}.pth')
+ checkpoint['state_dict'] = model.state_dict()
+ checkpoint['loss'] = running_loss / len(dataloader)
+ checkpoint['epoch'] = epoch
+ torch.save(checkpoint, checkpoint_path)
diff --git a/dnn/torch/rdovae/README.md b/dnn/torch/rdovae/README.md
new file mode 100644
index 00000000..14359d82
--- /dev/null
+++ b/dnn/torch/rdovae/README.md
@@ -0,0 +1,24 @@
+# Rate-Distortion-Optimized Variational Auto-Encoder
+
+## Setup
+The python code requires python >= 3.6 and has been tested with python 3.6 and python 3.10. To install requirements run
+```
+python -m pip install -r requirements.txt
+```
+
+## Training
+To generate training data use dump date from the main LPCNet repo
+```
+./dump_data -train 16khz_speech_input.s16 features.f32 data.s16
+```
+
+To train the model, simply run
+```
+python train_rdovae.py features.f32 output_folder
+```
+
+To train on CUDA device add `--cuda-visible-devices idx`.
+
+
+## ToDo
+- Upload checkpoints and add URLs
diff --git a/dnn/torch/rdovae/export_rdovae_weights.py b/dnn/torch/rdovae/export_rdovae_weights.py
new file mode 100644
index 00000000..3ef9fabd
--- /dev/null
+++ b/dnn/torch/rdovae/export_rdovae_weights.py
@@ -0,0 +1,365 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../weight-exchange'))
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('checkpoint', type=str, help='rdovae model checkpoint')
+parser.add_argument('output_dir', type=str, help='output folder')
+parser.add_argument('--format', choices=['C', 'numpy'], help='output format, default: C', default='C')
+
+args = parser.parse_args()
+
+import torch
+import numpy as np
+
+from rdovae import RDOVAE
+from wexchange.torch import dump_torch_weights
+from wexchange.c_export import CWriter, print_vector
+
+def print_xml(xmlout, val, param, anchor, name):
+ xmlout.write(
+f"""
+ <table anchor="{anchor}_{name}">
+ <name>{param} values for {name}</name>
+ <thead>
+ <tr><th>k</th><th>Q0</th><th>Q1</th><th>Q2</th><th>Q3</th><th>Q4</th><th>Q5</th><th>Q6</th><th>Q7</th><th>Q8</th><th>Q9</th><th>Q10</th><th>Q11</th><th>Q12</th><th>Q13</th><th>Q14</th><th>Q15</th></tr>
+ </thead>
+ <tbody>
+""")
+ for k in range(val.shape[1]):
+ xmlout.write(f" <tr><th>{k}</th>")
+ for j in range(val.shape[0]):
+ xmlout.write(f"<th>{val[j][k]}</th>")
+ xmlout.write("</tr>\n")
+ xmlout.write(
+f"""
+ </tbody>
+ </table>
+""")
+def dump_statistical_model(writer, w, name, xmlout):
+ levels = w.shape[0]
+
+ print("printing statistical model")
+ quant_scales = torch.nn.functional.softplus(w[:, 0, :]).numpy()
+ dead_zone = 0.05 * torch.nn.functional.softplus(w[:, 1, :]).numpy()
+ r = torch.sigmoid(w[:, 5 , :]).numpy()
+ p0 = torch.sigmoid(w[:, 4 , :]).numpy()
+ p0 = 1 - r ** (0.5 + 0.5 * p0)
+
+ scales_norm = 255./256./(1e-15+np.max(quant_scales,axis=0))
+ quant_scales = quant_scales*scales_norm
+ quant_scales_q8 = np.round(quant_scales * 2**8).astype(np.uint16)
+ dead_zone_q8 = np.clip(np.round(dead_zone * 2**8), 0, 255).astype(np.uint16)
+ r_q8 = np.clip(np.round(r * 2**8), 0, 255).astype(np.uint8)
+ p0_q8 = np.clip(np.round(p0 * 2**8), 0, 255).astype(np.uint16)
+
+ mask = (np.max(r_q8,axis=0) > 0) * (np.min(p0_q8,axis=0) < 255)
+ quant_scales_q8 = quant_scales_q8[:, mask]
+ dead_zone_q8 = dead_zone_q8[:, mask]
+ r_q8 = r_q8[:, mask]
+ p0_q8 = p0_q8[:, mask]
+ N = r_q8.shape[-1]
+
+ print_vector(writer.source, quant_scales_q8, f'dred_{name}_quant_scales_q8', dtype='opus_uint8', static=False)
+ print_vector(writer.source, dead_zone_q8, f'dred_{name}_dead_zone_q8', dtype='opus_uint8', static=False)
+ print_vector(writer.source, r_q8, f'dred_{name}_r_q8', dtype='opus_uint8', static=False)
+ print_vector(writer.source, p0_q8, f'dred_{name}_p0_q8', dtype='opus_uint8', static=False)
+
+ print_xml(xmlout, quant_scales_q8, "Scale", "scale", name)
+ print_xml(xmlout, dead_zone_q8, "Dead zone", "deadzone", name)
+ print_xml(xmlout, r_q8, "Decay (r)", "decay", name)
+ print_xml(xmlout, p0_q8, "P(0)", "p0", name)
+
+ writer.header.write(
+f"""
+extern const opus_uint8 dred_{name}_quant_scales_q8[{levels * N}];
+extern const opus_uint8 dred_{name}_dead_zone_q8[{levels * N}];
+extern const opus_uint8 dred_{name}_r_q8[{levels * N}];
+extern const opus_uint8 dred_{name}_p0_q8[{levels * N}];
+
+"""
+ )
+ return N, mask, torch.tensor(scales_norm[mask])
+
+
+def c_export(args, model):
+
+ message = f"Auto generated from checkpoint {os.path.basename(args.checkpoint)}"
+
+ enc_writer = CWriter(os.path.join(args.output_dir, "dred_rdovae_enc_data"), message=message, model_struct_name='RDOVAEEnc')
+ dec_writer = CWriter(os.path.join(args.output_dir, "dred_rdovae_dec_data"), message=message, model_struct_name='RDOVAEDec')
+ stats_writer = CWriter(os.path.join(args.output_dir, "dred_rdovae_stats_data"), message=message, enable_binary_blob=False)
+ constants_writer = CWriter(os.path.join(args.output_dir, "dred_rdovae_constants"), message=message, header_only=True, enable_binary_blob=False)
+ xmlout = open("stats.xml", "w")
+
+ # some custom includes
+ for writer in [enc_writer, dec_writer]:
+ writer.header.write(
+f"""
+#include "opus_types.h"
+
+#include "dred_rdovae.h"
+
+#include "dred_rdovae_constants.h"
+
+"""
+ )
+
+ stats_writer.header.write(
+f"""
+#include "opus_types.h"
+
+#include "dred_rdovae_constants.h"
+
+"""
+ )
+
+ latent_out = model.get_submodule('core_encoder.module.z_dense')
+ state_out = model.get_submodule('core_encoder.module.state_dense_2')
+ orig_latent_dim = latent_out.weight.shape[0]
+ orig_state_dim = state_out.weight.shape[0]
+ # statistical model
+ qembedding = model.statistical_model.quant_embedding.weight.detach()
+ levels = qembedding.shape[0]
+ qembedding = torch.reshape(qembedding, (levels, 6, -1))
+
+ latent_dim, latent_mask, latent_scale = dump_statistical_model(stats_writer, qembedding[:, :, :orig_latent_dim], 'latent', xmlout)
+ state_dim, state_mask, state_scale = dump_statistical_model(stats_writer, qembedding[:, :, orig_latent_dim:], 'state', xmlout)
+
+ padded_latent_dim = (latent_dim+7)//8*8
+ latent_pad = padded_latent_dim - latent_dim;
+ w = latent_out.weight[latent_mask,:]
+ w = w/latent_scale[:, None]
+ w = torch.cat([w, torch.zeros(latent_pad, w.shape[1])], dim=0)
+ b = latent_out.bias[latent_mask]
+ b = b/latent_scale
+ b = torch.cat([b, torch.zeros(latent_pad)], dim=0)
+ latent_out.weight = torch.nn.Parameter(w)
+ latent_out.bias = torch.nn.Parameter(b)
+
+ padded_state_dim = (state_dim+7)//8*8
+ state_pad = padded_state_dim - state_dim;
+ w = state_out.weight[state_mask,:]
+ w = w/state_scale[:, None]
+ w = torch.cat([w, torch.zeros(state_pad, w.shape[1])], dim=0)
+ b = state_out.bias[state_mask]
+ b = b/state_scale
+ b = torch.cat([b, torch.zeros(state_pad)], dim=0)
+ state_out.weight = torch.nn.Parameter(w)
+ state_out.bias = torch.nn.Parameter(b)
+
+ latent_in = model.get_submodule('core_decoder.module.dense_1')
+ state_in = model.get_submodule('core_decoder.module.hidden_init')
+ latent_in.weight = torch.nn.Parameter(latent_in.weight[:,latent_mask]*latent_scale)
+ state_in.weight = torch.nn.Parameter(state_in.weight[:,state_mask]*state_scale)
+
+ # encoder
+ encoder_dense_layers = [
+ ('core_encoder.module.dense_1' , 'enc_dense1', 'TANH', False,),
+ ('core_encoder.module.z_dense' , 'enc_zdense', 'LINEAR', True,),
+ ('core_encoder.module.state_dense_1' , 'gdense1' , 'TANH', True,),
+ ('core_encoder.module.state_dense_2' , 'gdense2' , 'TANH', True)
+ ]
+
+ for name, export_name, _, quantize in encoder_dense_layers:
+ layer = model.get_submodule(name)
+ dump_torch_weights(enc_writer, layer, name=export_name, verbose=True, quantize=quantize, scale=None)
+
+
+ encoder_gru_layers = [
+ ('core_encoder.module.gru1' , 'enc_gru1', 'TANH', True),
+ ('core_encoder.module.gru2' , 'enc_gru2', 'TANH', True),
+ ('core_encoder.module.gru3' , 'enc_gru3', 'TANH', True),
+ ('core_encoder.module.gru4' , 'enc_gru4', 'TANH', True),
+ ('core_encoder.module.gru5' , 'enc_gru5', 'TANH', True),
+ ]
+
+ enc_max_rnn_units = max([dump_torch_weights(enc_writer, model.get_submodule(name), export_name, verbose=True, input_sparse=True, quantize=quantize, scale=None, recurrent_scale=None)
+ for name, export_name, _, quantize in encoder_gru_layers])
+
+
+ encoder_conv_layers = [
+ ('core_encoder.module.conv1.conv' , 'enc_conv1', 'TANH', True),
+ ('core_encoder.module.conv2.conv' , 'enc_conv2', 'TANH', True),
+ ('core_encoder.module.conv3.conv' , 'enc_conv3', 'TANH', True),
+ ('core_encoder.module.conv4.conv' , 'enc_conv4', 'TANH', True),
+ ('core_encoder.module.conv5.conv' , 'enc_conv5', 'TANH', True),
+ ]
+
+ enc_max_conv_inputs = max([dump_torch_weights(enc_writer, model.get_submodule(name), export_name, verbose=True, quantize=quantize, scale=None) for name, export_name, _, quantize in encoder_conv_layers])
+
+
+ del enc_writer
+
+ # decoder
+ decoder_dense_layers = [
+ ('core_decoder.module.dense_1' , 'dec_dense1', 'TANH', False),
+ ('core_decoder.module.glu1.gate' , 'dec_glu1', 'TANH', True),
+ ('core_decoder.module.glu2.gate' , 'dec_glu2', 'TANH', True),
+ ('core_decoder.module.glu3.gate' , 'dec_glu3', 'TANH', True),
+ ('core_decoder.module.glu4.gate' , 'dec_glu4', 'TANH', True),
+ ('core_decoder.module.glu5.gate' , 'dec_glu5', 'TANH', True),
+ ('core_decoder.module.output' , 'dec_output', 'LINEAR', True),
+ ('core_decoder.module.hidden_init' , 'dec_hidden_init', 'TANH', False),
+ ('core_decoder.module.gru_init' , 'dec_gru_init','TANH', True),
+ ]
+
+ for name, export_name, _, quantize in decoder_dense_layers:
+ layer = model.get_submodule(name)
+ dump_torch_weights(dec_writer, layer, name=export_name, verbose=True, quantize=quantize, scale=None)
+
+
+ decoder_gru_layers = [
+ ('core_decoder.module.gru1' , 'dec_gru1', 'TANH', True),
+ ('core_decoder.module.gru2' , 'dec_gru2', 'TANH', True),
+ ('core_decoder.module.gru3' , 'dec_gru3', 'TANH', True),
+ ('core_decoder.module.gru4' , 'dec_gru4', 'TANH', True),
+ ('core_decoder.module.gru5' , 'dec_gru5', 'TANH', True),
+ ]
+
+ dec_max_rnn_units = max([dump_torch_weights(dec_writer, model.get_submodule(name), export_name, verbose=True, input_sparse=True, quantize=quantize, scale=None, recurrent_scale=None)
+ for name, export_name, _, quantize in decoder_gru_layers])
+
+ decoder_conv_layers = [
+ ('core_decoder.module.conv1.conv' , 'dec_conv1', 'TANH', True),
+ ('core_decoder.module.conv2.conv' , 'dec_conv2', 'TANH', True),
+ ('core_decoder.module.conv3.conv' , 'dec_conv3', 'TANH', True),
+ ('core_decoder.module.conv4.conv' , 'dec_conv4', 'TANH', True),
+ ('core_decoder.module.conv5.conv' , 'dec_conv5', 'TANH', True),
+ ]
+
+ dec_max_conv_inputs = max([dump_torch_weights(dec_writer, model.get_submodule(name), export_name, verbose=True, quantize=quantize, scale=None) for name, export_name, _, quantize in decoder_conv_layers])
+
+ del dec_writer
+
+ del stats_writer
+
+ # constants
+ constants_writer.header.write(
+f"""
+#define DRED_NUM_FEATURES {model.feature_dim}
+
+#define DRED_LATENT_DIM {latent_dim}
+
+#define DRED_STATE_DIM {state_dim}
+
+#define DRED_PADDED_LATENT_DIM {padded_latent_dim}
+
+#define DRED_PADDED_STATE_DIM {padded_state_dim}
+
+#define DRED_NUM_QUANTIZATION_LEVELS {model.quant_levels}
+
+#define DRED_MAX_RNN_NEURONS {max(enc_max_rnn_units, dec_max_rnn_units)}
+
+#define DRED_MAX_CONV_INPUTS {max(enc_max_conv_inputs, dec_max_conv_inputs)}
+
+#define DRED_ENC_MAX_RNN_NEURONS {enc_max_conv_inputs}
+
+#define DRED_ENC_MAX_CONV_INPUTS {enc_max_conv_inputs}
+
+#define DRED_DEC_MAX_RNN_NEURONS {dec_max_rnn_units}
+
+"""
+ )
+
+ del constants_writer
+
+
+def numpy_export(args, model):
+
+ exchange_name_to_name = {
+ 'encoder_stack_layer1_dense' : 'core_encoder.module.dense_1',
+ 'encoder_stack_layer3_dense' : 'core_encoder.module.dense_2',
+ 'encoder_stack_layer5_dense' : 'core_encoder.module.dense_3',
+ 'encoder_stack_layer7_dense' : 'core_encoder.module.dense_4',
+ 'encoder_stack_layer8_dense' : 'core_encoder.module.dense_5',
+ 'encoder_state_layer1_dense' : 'core_encoder.module.state_dense_1',
+ 'encoder_state_layer2_dense' : 'core_encoder.module.state_dense_2',
+ 'encoder_stack_layer2_gru' : 'core_encoder.module.gru_1',
+ 'encoder_stack_layer4_gru' : 'core_encoder.module.gru_2',
+ 'encoder_stack_layer6_gru' : 'core_encoder.module.gru_3',
+ 'encoder_stack_layer9_conv' : 'core_encoder.module.conv1',
+ 'statistical_model_embedding' : 'statistical_model.quant_embedding',
+ 'decoder_state1_dense' : 'core_decoder.module.gru_1_init',
+ 'decoder_state2_dense' : 'core_decoder.module.gru_2_init',
+ 'decoder_state3_dense' : 'core_decoder.module.gru_3_init',
+ 'decoder_stack_layer1_dense' : 'core_decoder.module.dense_1',
+ 'decoder_stack_layer3_dense' : 'core_decoder.module.dense_2',
+ 'decoder_stack_layer5_dense' : 'core_decoder.module.dense_3',
+ 'decoder_stack_layer7_dense' : 'core_decoder.module.dense_4',
+ 'decoder_stack_layer8_dense' : 'core_decoder.module.dense_5',
+ 'decoder_stack_layer9_dense' : 'core_decoder.module.output',
+ 'decoder_stack_layer2_gru' : 'core_decoder.module.gru_1',
+ 'decoder_stack_layer4_gru' : 'core_decoder.module.gru_2',
+ 'decoder_stack_layer6_gru' : 'core_decoder.module.gru_3'
+ }
+
+ name_to_exchange_name = {value : key for key, value in exchange_name_to_name.items()}
+
+ for name, exchange_name in name_to_exchange_name.items():
+ print(f"printing layer {name}...")
+ dump_torch_weights(os.path.join(args.output_dir, exchange_name), model.get_submodule(name))
+
+
+if __name__ == "__main__":
+
+
+ os.makedirs(args.output_dir, exist_ok=True)
+
+
+ # load model from checkpoint
+ checkpoint = torch.load(args.checkpoint, map_location='cpu')
+ model = RDOVAE(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+ missing_keys, unmatched_keys = model.load_state_dict(checkpoint['state_dict'], strict=False)
+ def _remove_weight_norm(m):
+ try:
+ torch.nn.utils.remove_weight_norm(m)
+ except ValueError: # this module didn't have weight norm
+ return
+ model.apply(_remove_weight_norm)
+
+
+ if len(missing_keys) > 0:
+ raise ValueError(f"error: missing keys in state dict")
+
+ if len(unmatched_keys) > 0:
+ print(f"warning: the following keys were unmatched {unmatched_keys}")
+
+ if args.format == 'C':
+ c_export(args, model)
+ elif args.format == 'numpy':
+ numpy_export(args, model)
+ else:
+ raise ValueError(f'error: unknown export format {args.format}')
diff --git a/dnn/torch/rdovae/fec_encoder.py b/dnn/torch/rdovae/fec_encoder.py
new file mode 100644
index 00000000..20ab4ac3
--- /dev/null
+++ b/dnn/torch/rdovae/fec_encoder.py
@@ -0,0 +1,212 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe and Jean-Marc Valin */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import subprocess
+import argparse
+
+os.environ['CUDA_VISIBLE_DEVICES'] = ""
+
+parser = argparse.ArgumentParser(description='Encode redundancy for Opus neural FEC. Designed for use with voip application and 20ms frames')
+
+parser.add_argument('input', metavar='<input signal>', help='audio input (.wav or .raw or .pcm as int16)')
+parser.add_argument('checkpoint', metavar='<weights>', help='model checkpoint')
+parser.add_argument('q0', metavar='<quant level 0>', type=int, help='quantization level for most recent frame')
+parser.add_argument('q1', metavar='<quant level 1>', type=int, help='quantization level for oldest frame')
+parser.add_argument('output', type=str, help='output file (will be extended with .fec)')
+
+parser.add_argument('--dump-data', type=str, default='./dump_data', help='path to dump data executable (default ./dump_data)')
+parser.add_argument('--num-redundancy-frames', default=52, type=int, help='number of redundancy frames per packet (default 52)')
+parser.add_argument('--extra-delay', default=0, type=int, help="last features in packet are calculated with the decoder aligned samples, use this option to add extra delay (in samples at 16kHz)")
+parser.add_argument('--lossfile', type=str, help='file containing loss trace (0 for frame received, 1 for lost)')
+parser.add_argument('--debug-output', action='store_true', help='if set, differently assembled features are written to disk')
+
+args = parser.parse_args()
+
+import numpy as np
+from scipy.io import wavfile
+import torch
+
+from rdovae import RDOVAE
+from packets import write_fec_packets
+
+torch.set_num_threads(4)
+
+checkpoint = torch.load(args.checkpoint, map_location="cpu")
+model = RDOVAE(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+model.load_state_dict(checkpoint['state_dict'], strict=False)
+model.to("cpu")
+
+lpc_order = 16
+
+## prepare input signal
+# SILK frame size is 20ms and LPCNet subframes are 10ms
+subframe_size = 160
+frame_size = 2 * subframe_size
+
+# 91 samples delay to align with SILK decoded frames
+silk_delay = 91
+
+# prepend zeros to have enough history to produce the first package
+zero_history = (args.num_redundancy_frames - 1) * frame_size
+
+# dump data has a (feature) delay of 10ms
+dump_data_delay = 160
+
+total_delay = silk_delay + zero_history + args.extra_delay - dump_data_delay
+
+# load signal
+if args.input.endswith('.raw') or args.input.endswith('.pcm'):
+ signal = np.fromfile(args.input, dtype='int16')
+
+elif args.input.endswith('.wav'):
+ fs, signal = wavfile.read(args.input)
+else:
+ raise ValueError(f'unknown input signal format: {args.input}')
+
+# fill up last frame with zeros
+padded_signal_length = len(signal) + total_delay
+tail = padded_signal_length % frame_size
+right_padding = (frame_size - tail) % frame_size
+
+signal = np.concatenate((np.zeros(total_delay, dtype=np.int16), signal, np.zeros(right_padding, dtype=np.int16)))
+
+padded_signal_file = os.path.splitext(args.input)[0] + '_padded.raw'
+signal.tofile(padded_signal_file)
+
+# write signal and call dump_data to create features
+
+feature_file = os.path.splitext(args.input)[0] + '_features.f32'
+command = f"{args.dump_data} -test {padded_signal_file} {feature_file}"
+r = subprocess.run(command, shell=True)
+if r.returncode != 0:
+ raise RuntimeError(f"command '{command}' failed with exit code {r.returncode}")
+
+# load features
+nb_features = model.feature_dim + lpc_order
+nb_used_features = model.feature_dim
+
+# load features
+features = np.fromfile(feature_file, dtype='float32')
+num_subframes = len(features) // nb_features
+num_subframes = 2 * (num_subframes // 2)
+num_frames = num_subframes // 2
+
+features = np.reshape(features, (1, -1, nb_features))
+features = features[:, :, :nb_used_features]
+features = features[:, :num_subframes, :]
+
+# quant_ids in reverse decoding order
+quant_ids = torch.round((args.q1 + (args.q0 - args.q1) * torch.arange(args.num_redundancy_frames // 2) / (args.num_redundancy_frames // 2 - 1))).long()
+
+print(f"using quantization levels {quant_ids}...")
+
+# convert input to torch tensors
+features = torch.from_numpy(features)
+
+
+# run encoder
+print("running fec encoder...")
+with torch.no_grad():
+
+ # encoding
+ z, states, state_size = model.encode(features)
+
+
+ # decoder on packet chunks
+ input_length = args.num_redundancy_frames // 2
+ offset = args.num_redundancy_frames - 1
+
+ packets = []
+ packet_sizes = []
+
+ for i in range(offset, num_frames):
+ print(f"processing frame {i - offset}...")
+ # quantize / unquantize latent vectors
+ zi = torch.clone(z[:, i - 2 * input_length + 2: i + 1 : 2, :])
+ zi, rates = model.quantize(zi, quant_ids)
+ zi = model.unquantize(zi, quant_ids)
+
+ features = model.decode(zi, states[:, i : i + 1, :])
+ packets.append(features.squeeze(0).numpy())
+ packet_size = 8 * int((torch.sum(rates) + 7 + state_size) / 8)
+ packet_sizes.append(packet_size)
+
+
+# write packets
+packet_file = args.output + '.fec' if not args.output.endswith('.fec') else args.output
+write_fec_packets(packet_file, packets, packet_sizes)
+
+
+print(f"average redundancy rate: {int(round(sum(packet_sizes) / len(packet_sizes) * 50 / 1000))} kbps")
+
+# assemble features according to loss file
+if args.lossfile != None:
+ num_packets = len(packets)
+ loss = np.loadtxt(args.lossfile, dtype='int16')
+ fec_out = np.zeros((num_packets * 2, packets[0].shape[-1]), dtype='float32')
+ foffset = -2
+ ptr = 0
+ count = 2
+ for i in range(num_packets):
+ if (loss[i] == 0) or (i == num_packets - 1):
+
+ fec_out[ptr:ptr+count,:] = packets[i][foffset:, :]
+
+ ptr += count
+ foffset = -2
+ count = 2
+ else:
+ count += 2
+ foffset -= 2
+
+ fec_out_full = np.zeros((fec_out.shape[0], 36), dtype=np.float32)
+ fec_out_full[:, : fec_out.shape[-1]] = fec_out
+
+ fec_out_full.tofile(packet_file[:-4] + f'_fec.f32')
+
+
+if args.debug_output:
+ import itertools
+
+ batches = [4]
+ offsets = [0, 2 * args.num_redundancy_frames - 4]
+
+ # sanity checks
+ # 1. concatenate features at offset 0
+ for batch, offset in itertools.product(batches, offsets):
+
+ stop = packets[0].shape[1] - offset
+ test_features = np.concatenate([packet[stop - batch: stop, :] for packet in packets[::batch//2]], axis=0)
+
+ test_features_full = np.zeros((test_features.shape[0], nb_features), dtype=np.float32)
+ test_features_full[:, :nb_used_features] = test_features[:, :]
+
+ print(f"writing debug output {packet_file[:-4] + f'_torch_batch{batch}_offset{offset}.f32'}")
+ test_features_full.tofile(packet_file[:-4] + f'_torch_batch{batch}_offset{offset}.f32')
diff --git a/dnn/torch/rdovae/import_rdovae_weights.py b/dnn/torch/rdovae/import_rdovae_weights.py
new file mode 100644
index 00000000..c824986d
--- /dev/null
+++ b/dnn/torch/rdovae/import_rdovae_weights.py
@@ -0,0 +1,143 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = ""
+
+import argparse
+
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('exchange_folder', type=str, help='exchange folder path')
+parser.add_argument('output', type=str, help='path to output model checkpoint')
+
+model_group = parser.add_argument_group(title="model parameters")
+model_group.add_argument('--num-features', type=int, help="number of features, default: 20", default=20)
+model_group.add_argument('--latent-dim', type=int, help="number of symbols produces by encoder, default: 80", default=80)
+model_group.add_argument('--cond-size', type=int, help="first conditioning size, default: 256", default=256)
+model_group.add_argument('--cond-size2', type=int, help="second conditioning size, default: 256", default=256)
+model_group.add_argument('--state-dim', type=int, help="dimensionality of transfered state, default: 24", default=24)
+model_group.add_argument('--quant-levels', type=int, help="number of quantization levels, default: 40", default=40)
+
+args = parser.parse_args()
+
+import torch
+from rdovae import RDOVAE
+from wexchange.torch import load_torch_weights
+
+exchange_name_to_name = {
+ 'encoder_stack_layer1_dense' : 'core_encoder.module.dense_1',
+ 'encoder_stack_layer3_dense' : 'core_encoder.module.dense_2',
+ 'encoder_stack_layer5_dense' : 'core_encoder.module.dense_3',
+ 'encoder_stack_layer7_dense' : 'core_encoder.module.dense_4',
+ 'encoder_stack_layer8_dense' : 'core_encoder.module.dense_5',
+ 'encoder_state_layer1_dense' : 'core_encoder.module.state_dense_1',
+ 'encoder_state_layer2_dense' : 'core_encoder.module.state_dense_2',
+ 'encoder_stack_layer2_gru' : 'core_encoder.module.gru_1',
+ 'encoder_stack_layer4_gru' : 'core_encoder.module.gru_2',
+ 'encoder_stack_layer6_gru' : 'core_encoder.module.gru_3',
+ 'encoder_stack_layer9_conv' : 'core_encoder.module.conv1',
+ 'statistical_model_embedding' : 'statistical_model.quant_embedding',
+ 'decoder_state1_dense' : 'core_decoder.module.gru_1_init',
+ 'decoder_state2_dense' : 'core_decoder.module.gru_2_init',
+ 'decoder_state3_dense' : 'core_decoder.module.gru_3_init',
+ 'decoder_stack_layer1_dense' : 'core_decoder.module.dense_1',
+ 'decoder_stack_layer3_dense' : 'core_decoder.module.dense_2',
+ 'decoder_stack_layer5_dense' : 'core_decoder.module.dense_3',
+ 'decoder_stack_layer7_dense' : 'core_decoder.module.dense_4',
+ 'decoder_stack_layer8_dense' : 'core_decoder.module.dense_5',
+ 'decoder_stack_layer9_dense' : 'core_decoder.module.output',
+ 'decoder_stack_layer2_gru' : 'core_decoder.module.gru_1',
+ 'decoder_stack_layer4_gru' : 'core_decoder.module.gru_2',
+ 'decoder_stack_layer6_gru' : 'core_decoder.module.gru_3'
+}
+
+if __name__ == "__main__":
+ checkpoint = dict()
+
+ # parameters
+ num_features = args.num_features
+ latent_dim = args.latent_dim
+ quant_levels = args.quant_levels
+ cond_size = args.cond_size
+ cond_size2 = args.cond_size2
+ state_dim = args.state_dim
+
+
+ # model
+ checkpoint['model_args'] = (num_features, latent_dim, quant_levels, cond_size, cond_size2)
+ checkpoint['model_kwargs'] = {'state_dim': state_dim}
+ model = RDOVAE(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+
+ dense_layer_names = [
+ 'encoder_stack_layer1_dense',
+ 'encoder_stack_layer3_dense',
+ 'encoder_stack_layer5_dense',
+ 'encoder_stack_layer7_dense',
+ 'encoder_stack_layer8_dense',
+ 'encoder_state_layer1_dense',
+ 'encoder_state_layer2_dense',
+ 'decoder_state1_dense',
+ 'decoder_state2_dense',
+ 'decoder_state3_dense',
+ 'decoder_stack_layer1_dense',
+ 'decoder_stack_layer3_dense',
+ 'decoder_stack_layer5_dense',
+ 'decoder_stack_layer7_dense',
+ 'decoder_stack_layer8_dense',
+ 'decoder_stack_layer9_dense'
+ ]
+
+ gru_layer_names = [
+ 'encoder_stack_layer2_gru',
+ 'encoder_stack_layer4_gru',
+ 'encoder_stack_layer6_gru',
+ 'decoder_stack_layer2_gru',
+ 'decoder_stack_layer4_gru',
+ 'decoder_stack_layer6_gru'
+ ]
+
+ conv1d_layer_names = [
+ 'encoder_stack_layer9_conv'
+ ]
+
+ embedding_layer_names = [
+ 'statistical_model_embedding'
+ ]
+
+ for name in dense_layer_names + gru_layer_names + conv1d_layer_names + embedding_layer_names:
+ print(f"loading weights for layer {exchange_name_to_name[name]}")
+ layer = model.get_submodule(exchange_name_to_name[name])
+ load_torch_weights(os.path.join(args.exchange_folder, name), layer)
+
+ checkpoint['state_dict'] = model.state_dict()
+
+ torch.save(checkpoint, args.output) \ No newline at end of file
diff --git a/dnn/torch/rdovae/packets/__init__.py b/dnn/torch/rdovae/packets/__init__.py
new file mode 100644
index 00000000..fb71ab3d
--- /dev/null
+++ b/dnn/torch/rdovae/packets/__init__.py
@@ -0,0 +1 @@
+from .fec_packets import write_fec_packets, read_fec_packets \ No newline at end of file
diff --git a/dnn/torch/rdovae/packets/fec_packets.c b/dnn/torch/rdovae/packets/fec_packets.c
new file mode 100644
index 00000000..ee08ba95
--- /dev/null
+++ b/dnn/torch/rdovae/packets/fec_packets.c
@@ -0,0 +1,142 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include "fec_packets.h"
+
+int get_fec_frame(const char * const filename, float *features, int packet_index, int subframe_index)
+{
+
+ int16_t version;
+ int16_t header_size;
+ int16_t num_packets;
+ int16_t packet_size;
+ int16_t subframe_size;
+ int16_t subframes_per_packet;
+ int16_t num_features;
+ long offset;
+
+ FILE *fid = fopen(filename, "rb");
+
+ /* read header */
+ if (fread(&version, sizeof(version), 1, fid) != 1) goto error;
+ if (fread(&header_size, sizeof(header_size), 1, fid) != 1) goto error;
+ if (fread(&num_packets, sizeof(num_packets), 1, fid) != 1) goto error;
+ if (fread(&packet_size, sizeof(packet_size), 1, fid) != 1) goto error;
+ if (fread(&subframe_size, sizeof(subframe_size), 1, fid) != 1) goto error;
+ if (fread(&subframes_per_packet, sizeof(subframes_per_packet), 1, fid) != 1) goto error;
+ if (fread(&num_features, sizeof(num_features), 1, fid) != 1) goto error;
+
+ /* check if indices are valid */
+ if (packet_index >= num_packets || subframe_index >= subframes_per_packet)
+ {
+ fprintf(stderr, "get_fec_frame: index out of bounds\n");
+ goto error;
+ }
+
+ /* calculate offset in file (+ 2 is for rate) */
+ offset = header_size + packet_index * packet_size + 2 + subframe_index * subframe_size;
+ fseek(fid, offset, SEEK_SET);
+
+ /* read features */
+ if (fread(features, sizeof(*features), num_features, fid) != num_features) goto error;
+
+ fclose(fid);
+ return 0;
+
+error:
+ fclose(fid);
+ return 1;
+}
+
+int get_fec_rate(const char * const filename, int packet_index)
+{
+ int16_t version;
+ int16_t header_size;
+ int16_t num_packets;
+ int16_t packet_size;
+ int16_t subframe_size;
+ int16_t subframes_per_packet;
+ int16_t num_features;
+ long offset;
+ int16_t rate;
+
+ FILE *fid = fopen(filename, "rb");
+
+ /* read header */
+ if (fread(&version, sizeof(version), 1, fid) != 1) goto error;
+ if (fread(&header_size, sizeof(header_size), 1, fid) != 1) goto error;
+ if (fread(&num_packets, sizeof(num_packets), 1, fid) != 1) goto error;
+ if (fread(&packet_size, sizeof(packet_size), 1, fid) != 1) goto error;
+ if (fread(&subframe_size, sizeof(subframe_size), 1, fid) != 1) goto error;
+ if (fread(&subframes_per_packet, sizeof(subframes_per_packet), 1, fid) != 1) goto error;
+ if (fread(&num_features, sizeof(num_features), 1, fid) != 1) goto error;
+
+ /* check if indices are valid */
+ if (packet_index >= num_packets)
+ {
+ fprintf(stderr, "get_fec_rate: index out of bounds\n");
+ goto error;
+ }
+
+ /* calculate offset in file (+ 2 is for rate) */
+ offset = header_size + packet_index * packet_size;
+ fseek(fid, offset, SEEK_SET);
+
+ /* read rate */
+ if (fread(&rate, sizeof(rate), 1, fid) != 1) goto error;
+
+ fclose(fid);
+ return (int) rate;
+
+error:
+ fclose(fid);
+ return -1;
+}
+
+#if 0
+int main()
+{
+ float features[20];
+ int i;
+
+ if (get_fec_frame("../test.fec", &features[0], 0, 127))
+ {
+ return 1;
+ }
+
+ for (i = 0; i < 20; i ++)
+ {
+ printf("%d %f\n", i, features[i]);
+ }
+
+ printf("rate: %d\n", get_fec_rate("../test.fec", 0));
+
+}
+#endif \ No newline at end of file
diff --git a/dnn/torch/rdovae/packets/fec_packets.h b/dnn/torch/rdovae/packets/fec_packets.h
new file mode 100644
index 00000000..35d35542
--- /dev/null
+++ b/dnn/torch/rdovae/packets/fec_packets.h
@@ -0,0 +1,34 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef _FEC_PACKETS_H
+#define _FEC_PACKETS_H
+
+int get_fec_frame(const char * const filename, float *features, int packet_index, int subframe_index);
+int get_fec_rate(const char * const filename, int packet_index);
+
+#endif \ No newline at end of file
diff --git a/dnn/torch/rdovae/packets/fec_packets.py b/dnn/torch/rdovae/packets/fec_packets.py
new file mode 100644
index 00000000..f44c1a95
--- /dev/null
+++ b/dnn/torch/rdovae/packets/fec_packets.py
@@ -0,0 +1,108 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import numpy as np
+
+
+
+def write_fec_packets(filename, packets, rates=None):
+ """ writes packets in binary format """
+
+ assert np.dtype(np.float32).itemsize == 4
+ assert np.dtype(np.int16).itemsize == 2
+
+ # derive some sizes
+ num_packets = len(packets)
+ subframes_per_packet = packets[0].shape[-2]
+ num_features = packets[0].shape[-1]
+
+ # size of float is 4
+ subframe_size = num_features * 4
+ packet_size = subframe_size * subframes_per_packet + 2 # two bytes for rate
+
+ version = 1
+ # header size (version, header_size, num_packets, packet_size, subframe_size, subrames_per_packet, num_features)
+ header_size = 14
+
+ with open(filename, 'wb') as f:
+
+ # header
+ f.write(np.int16(version).tobytes())
+ f.write(np.int16(header_size).tobytes())
+ f.write(np.int16(num_packets).tobytes())
+ f.write(np.int16(packet_size).tobytes())
+ f.write(np.int16(subframe_size).tobytes())
+ f.write(np.int16(subframes_per_packet).tobytes())
+ f.write(np.int16(num_features).tobytes())
+
+ # packets
+ for i, packet in enumerate(packets):
+ if type(rates) == type(None):
+ rate = 0
+ else:
+ rate = rates[i]
+
+ f.write(np.int16(rate).tobytes())
+
+ features = np.flip(packet, axis=-2)
+ f.write(features.astype(np.float32).tobytes())
+
+
+def read_fec_packets(filename):
+ """ reads packets from binary format """
+
+ assert np.dtype(np.float32).itemsize == 4
+ assert np.dtype(np.int16).itemsize == 2
+
+ with open(filename, 'rb') as f:
+
+ # header
+ version = np.frombuffer(f.read(2), dtype=np.int16).item()
+ header_size = np.frombuffer(f.read(2), dtype=np.int16).item()
+ num_packets = np.frombuffer(f.read(2), dtype=np.int16).item()
+ packet_size = np.frombuffer(f.read(2), dtype=np.int16).item()
+ subframe_size = np.frombuffer(f.read(2), dtype=np.int16).item()
+ subframes_per_packet = np.frombuffer(f.read(2), dtype=np.int16).item()
+ num_features = np.frombuffer(f.read(2), dtype=np.int16).item()
+
+ dummy_features = np.zeros((subframes_per_packet, num_features), dtype=np.float32)
+
+ # packets
+ rates = []
+ packets = []
+ for i in range(num_packets):
+
+ rate = np.frombuffer(f.read(2), dtype=np.int16).item
+ rates.append(rate)
+
+ features = np.reshape(np.frombuffer(f.read(subframe_size * subframes_per_packet), dtype=np.float32), dummy_features.shape)
+ packet = np.flip(features, axis=-2)
+ packets.append(packet)
+
+ return packets \ No newline at end of file
diff --git a/dnn/torch/rdovae/rdovae/__init__.py b/dnn/torch/rdovae/rdovae/__init__.py
new file mode 100644
index 00000000..b945adde
--- /dev/null
+++ b/dnn/torch/rdovae/rdovae/__init__.py
@@ -0,0 +1,2 @@
+from .rdovae import RDOVAE, distortion_loss, hard_rate_estimate, soft_rate_estimate
+from .dataset import RDOVAEDataset
diff --git a/dnn/torch/rdovae/rdovae/dataset.py b/dnn/torch/rdovae/rdovae/dataset.py
new file mode 100644
index 00000000..cfb32b05
--- /dev/null
+++ b/dnn/torch/rdovae/rdovae/dataset.py
@@ -0,0 +1,67 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import torch
+import numpy as np
+
+class RDOVAEDataset(torch.utils.data.Dataset):
+ def __init__(self,
+ feature_file,
+ sequence_length,
+ num_used_features=20,
+ num_features=36,
+ lambda_min=0.0002,
+ lambda_max=0.0135,
+ quant_levels=16,
+ enc_stride=2):
+
+ self.sequence_length = sequence_length
+ self.lambda_min = lambda_min
+ self.lambda_max = lambda_max
+ self.enc_stride = enc_stride
+ self.quant_levels = quant_levels
+ self.denominator = (quant_levels - 1) / np.log(lambda_max / lambda_min)
+
+ if sequence_length % enc_stride:
+ raise ValueError(f"RDOVAEDataset.__init__: enc_stride {enc_stride} does not divide sequence length {sequence_length}")
+
+ self.features = np.reshape(np.fromfile(feature_file, dtype=np.float32), (-1, num_features))
+ self.features = self.features[:, :num_used_features]
+ self.num_sequences = self.features.shape[0] // sequence_length
+
+ def __len__(self):
+ return self.num_sequences
+
+ def __getitem__(self, index):
+ features = self.features[index * self.sequence_length: (index + 1) * self.sequence_length, :]
+ q_ids = np.random.randint(0, self.quant_levels, (1)).astype(np.int64)
+ q_ids = np.repeat(q_ids, self.sequence_length // self.enc_stride, axis=0)
+ rate_lambda = self.lambda_min * np.exp(q_ids.astype(np.float32) / self.denominator).astype(np.float32)
+
+ return features, rate_lambda, q_ids
diff --git a/dnn/torch/rdovae/rdovae/rdovae.py b/dnn/torch/rdovae/rdovae/rdovae.py
new file mode 100644
index 00000000..cdb07b46
--- /dev/null
+++ b/dnn/torch/rdovae/rdovae/rdovae.py
@@ -0,0 +1,719 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+""" Pytorch implementations of rate distortion optimized variational autoencoder """
+
+import math as m
+
+import torch
+from torch import nn
+import torch.nn.functional as F
+import sys
+import os
+source_dir = os.path.split(os.path.abspath(__file__))[0]
+sys.path.append(os.path.join(source_dir, "../../lpcnet/"))
+from utils.sparsification import GRUSparsifier
+from torch.nn.utils import weight_norm
+
+# Quantization and rate related utily functions
+
+def soft_pvq(x, k):
+ """ soft pyramid vector quantizer """
+
+ # L2 normalization
+ x_norm2 = x / (1e-15 + torch.norm(x, dim=-1, keepdim=True))
+
+
+ with torch.no_grad():
+ # quantization loop, no need to track gradients here
+ x_norm1 = x / torch.sum(torch.abs(x), dim=-1, keepdim=True)
+
+ # set initial scaling factor to k
+ scale_factor = k
+ x_scaled = scale_factor * x_norm1
+ x_quant = torch.round(x_scaled)
+
+ # we aim for ||x_quant||_L1 = k
+ for _ in range(10):
+ # remove signs and calculate L1 norm
+ abs_x_quant = torch.abs(x_quant)
+ abs_x_scaled = torch.abs(x_scaled)
+ l1_x_quant = torch.sum(abs_x_quant, axis=-1)
+
+ # increase, where target is too small and decrease, where target is too large
+ plus = 1.0001 * torch.min((abs_x_quant + 0.5) / (abs_x_scaled + 1e-15), dim=-1).values
+ minus = 0.9999 * torch.max((abs_x_quant - 0.5) / (abs_x_scaled + 1e-15), dim=-1).values
+ factor = torch.where(l1_x_quant > k, minus, plus)
+ factor = torch.where(l1_x_quant == k, torch.ones_like(factor), factor)
+ scale_factor = scale_factor * factor.unsqueeze(-1)
+
+ # update x
+ x_scaled = scale_factor * x_norm1
+ x_quant = torch.round(x_quant)
+
+ # L2 normalization of quantized x
+ x_quant_norm2 = x_quant / (1e-15 + torch.norm(x_quant, dim=-1, keepdim=True))
+ quantization_error = x_quant_norm2 - x_norm2
+
+ return x_norm2 + quantization_error.detach()
+
+def cache_parameters(func):
+ cache = dict()
+ def cached_func(*args):
+ if args in cache:
+ return cache[args]
+ else:
+ cache[args] = func(*args)
+
+ return cache[args]
+ return cached_func
+
+@cache_parameters
+def pvq_codebook_size(n, k):
+
+ if k == 0:
+ return 1
+
+ if n == 0:
+ return 0
+
+ return pvq_codebook_size(n - 1, k) + pvq_codebook_size(n, k - 1) + pvq_codebook_size(n - 1, k - 1)
+
+
+def soft_rate_estimate(z, r, reduce=True):
+ """ rate approximation with dependent theta Eq. (7)"""
+
+ rate = torch.sum(
+ - torch.log2((1 - r)/(1 + r) * r ** torch.abs(z) + 1e-6),
+ dim=-1
+ )
+
+ if reduce:
+ rate = torch.mean(rate)
+
+ return rate
+
+
+def hard_rate_estimate(z, r, theta, reduce=True):
+ """ hard rate approximation """
+
+ z_q = torch.round(z)
+ p0 = 1 - r ** (0.5 + 0.5 * theta)
+ alpha = torch.relu(1 - torch.abs(z_q)) ** 2
+ rate = - torch.sum(
+ (alpha * torch.log2(p0 * r ** torch.abs(z_q) + 1e-6)
+ + (1 - alpha) * torch.log2(0.5 * (1 - p0) * (1 - r) * r ** (torch.abs(z_q) - 1) + 1e-6)),
+ dim=-1
+ )
+
+ if reduce:
+ rate = torch.mean(rate)
+
+ return rate
+
+
+
+def soft_dead_zone(x, dead_zone):
+ """ approximates application of a dead zone to x """
+ d = dead_zone * 0.05
+ return x - d * torch.tanh(x / (0.1 + d))
+
+
+def hard_quantize(x):
+ """ round with copy gradient trick """
+ return x + (torch.round(x) - x).detach()
+
+
+def noise_quantize(x):
+ """ simulates quantization with addition of random uniform noise """
+ return x + (torch.rand_like(x) - 0.5)
+
+
+# loss functions
+
+
+def distortion_loss(y_true, y_pred, rate_lambda=None):
+ """ custom distortion loss for LPCNet features """
+
+ if y_true.size(-1) != 20:
+ raise ValueError('distortion loss is designed to work with 20 features')
+
+ ceps_error = y_pred[..., :18] - y_true[..., :18]
+ pitch_error = 2*(y_pred[..., 18:19] - y_true[..., 18:19])
+ corr_error = y_pred[..., 19:] - y_true[..., 19:]
+ pitch_weight = torch.relu(y_true[..., 19:] + 0.5) ** 2
+
+ loss = torch.mean(ceps_error ** 2 + (10/18) * torch.abs(pitch_error) * pitch_weight + (1/18) * corr_error ** 2, dim=-1)
+
+ if type(rate_lambda) != type(None):
+ loss = loss / torch.sqrt(rate_lambda)
+
+ loss = torch.mean(loss)
+
+ return loss
+
+
+# sampling functions
+
+import random
+
+
+def random_split(start, stop, num_splits=3, min_len=3):
+ get_min_len = lambda x : min([x[i+1] - x[i] for i in range(len(x) - 1)])
+ candidate = [start] + sorted([random.randint(start, stop-1) for i in range(num_splits)]) + [stop]
+
+ while get_min_len(candidate) < min_len:
+ candidate = [start] + sorted([random.randint(start, stop-1) for i in range(num_splits)]) + [stop]
+
+ return candidate
+
+
+
+# weight initialization and clipping
+def init_weights(module):
+
+ if isinstance(module, nn.GRU):
+ for p in module.named_parameters():
+ if p[0].startswith('weight_hh_'):
+ nn.init.orthogonal_(p[1])
+
+
+def weight_clip_factory(max_value):
+ """ weight clipping function concerning sum of abs values of adjecent weights """
+ def clip_weight_(w):
+ stop = w.size(1)
+ # omit last column if stop is odd
+ if stop % 2:
+ stop -= 1
+ max_values = max_value * torch.ones_like(w[:, :stop])
+ factor = max_value / torch.maximum(max_values,
+ torch.repeat_interleave(
+ torch.abs(w[:, :stop:2]) + torch.abs(w[:, 1:stop:2]),
+ 2,
+ 1))
+ with torch.no_grad():
+ w[:, :stop] *= factor
+
+ def clip_weights(module):
+ if isinstance(module, nn.GRU) or isinstance(module, nn.Linear):
+ for name, w in module.named_parameters():
+ if name.startswith('weight'):
+ clip_weight_(w)
+
+ return clip_weights
+
+def n(x):
+ return torch.clamp(x + (1./127.)*(torch.rand_like(x)-.5), min=-1., max=1.)
+
+# RDOVAE module and submodules
+
+sparsify_start = 12000
+sparsify_stop = 24000
+sparsify_interval = 100
+sparsify_exponent = 3
+#sparsify_start = 0
+#sparsify_stop = 0
+
+sparse_params1 = {
+# 'W_hr' : (1.0, [8, 4], True),
+# 'W_hz' : (1.0, [8, 4], True),
+# 'W_hn' : (1.0, [8, 4], True),
+ 'W_ir' : (0.6, [8, 4], False),
+ 'W_iz' : (0.4, [8, 4], False),
+ 'W_in' : (0.8, [8, 4], False)
+ }
+
+sparse_params2 = {
+# 'W_hr' : (1.0, [8, 4], True),
+# 'W_hz' : (1.0, [8, 4], True),
+# 'W_hn' : (1.0, [8, 4], True),
+ 'W_ir' : (0.3, [8, 4], False),
+ 'W_iz' : (0.2, [8, 4], False),
+ 'W_in' : (0.4, [8, 4], False)
+ }
+
+
+class MyConv(nn.Module):
+ def __init__(self, input_dim, output_dim, dilation=1):
+ super(MyConv, self).__init__()
+ self.input_dim = input_dim
+ self.output_dim = output_dim
+ self.dilation=dilation
+ self.conv = nn.Conv1d(input_dim, output_dim, kernel_size=2, padding='valid', dilation=dilation)
+ def forward(self, x, state=None):
+ device = x.device
+ conv_in = torch.cat([torch.zeros_like(x[:,0:self.dilation,:], device=device), x], -2).permute(0, 2, 1)
+ return torch.tanh(self.conv(conv_in)).permute(0, 2, 1)
+
+class GLU(nn.Module):
+ def __init__(self, feat_size):
+ super(GLU, self).__init__()
+
+ torch.manual_seed(5)
+
+ self.gate = weight_norm(nn.Linear(feat_size, feat_size, bias=False))
+
+ self.init_weights()
+
+ def init_weights(self):
+
+ for m in self.modules():
+ if isinstance(m, nn.Conv1d) or isinstance(m, nn.ConvTranspose1d)\
+ or isinstance(m, nn.Linear) or isinstance(m, nn.Embedding):
+ nn.init.orthogonal_(m.weight.data)
+
+ def forward(self, x):
+
+ out = x * torch.sigmoid(self.gate(x))
+
+ return out
+
+class CoreEncoder(nn.Module):
+ STATE_HIDDEN = 128
+ FRAMES_PER_STEP = 2
+ CONV_KERNEL_SIZE = 4
+
+ def __init__(self, feature_dim, output_dim, cond_size, cond_size2, state_size=24):
+ """ core encoder for RDOVAE
+
+ Computes latents, initial states, and rate estimates from features and lambda parameter
+
+ """
+
+ super(CoreEncoder, self).__init__()
+
+ # hyper parameters
+ self.feature_dim = feature_dim
+ self.output_dim = output_dim
+ self.cond_size = cond_size
+ self.cond_size2 = cond_size2
+ self.state_size = state_size
+
+ # derived parameters
+ self.input_dim = self.FRAMES_PER_STEP * self.feature_dim
+
+ # layers
+ self.dense_1 = nn.Linear(self.input_dim, 64)
+ self.gru1 = nn.GRU(64, 64, batch_first=True)
+ self.conv1 = MyConv(128, 96)
+ self.gru2 = nn.GRU(224, 64, batch_first=True)
+ self.conv2 = MyConv(288, 96, dilation=2)
+ self.gru3 = nn.GRU(384, 64, batch_first=True)
+ self.conv3 = MyConv(448, 96, dilation=2)
+ self.gru4 = nn.GRU(544, 64, batch_first=True)
+ self.conv4 = MyConv(608, 96, dilation=2)
+ self.gru5 = nn.GRU(704, 64, batch_first=True)
+ self.conv5 = MyConv(768, 96, dilation=2)
+
+ self.z_dense = nn.Linear(864, self.output_dim)
+
+
+ self.state_dense_1 = nn.Linear(864, self.STATE_HIDDEN)
+
+ self.state_dense_2 = nn.Linear(self.STATE_HIDDEN, self.state_size)
+ nb_params = sum(p.numel() for p in self.parameters())
+ print(f"encoder: {nb_params} weights")
+
+ # initialize weights
+ self.apply(init_weights)
+
+
+ def forward(self, features):
+
+ # reshape features
+ x = torch.reshape(features, (features.size(0), features.size(1) // self.FRAMES_PER_STEP, self.FRAMES_PER_STEP * features.size(2)))
+
+ batch = x.size(0)
+ device = x.device
+
+ # run encoding layer stack
+ x = n(torch.tanh(self.dense_1(x)))
+ x = torch.cat([x, n(self.gru1(x)[0])], -1)
+ x = torch.cat([x, n(self.conv1(x))], -1)
+ x = torch.cat([x, n(self.gru2(x)[0])], -1)
+ x = torch.cat([x, n(self.conv2(x))], -1)
+ x = torch.cat([x, n(self.gru3(x)[0])], -1)
+ x = torch.cat([x, n(self.conv3(x))], -1)
+ x = torch.cat([x, n(self.gru4(x)[0])], -1)
+ x = torch.cat([x, n(self.conv4(x))], -1)
+ x = torch.cat([x, n(self.gru5(x)[0])], -1)
+ x = torch.cat([x, n(self.conv5(x))], -1)
+ z = self.z_dense(x)
+
+ # init state for decoder
+ states = torch.tanh(self.state_dense_1(x))
+ states = self.state_dense_2(states)
+
+ return z, states
+
+
+
+
+class CoreDecoder(nn.Module):
+
+ FRAMES_PER_STEP = 4
+
+ def __init__(self, input_dim, output_dim, cond_size, cond_size2, state_size=24):
+ """ core decoder for RDOVAE
+
+ Computes features from latents, initial state, and quantization index
+
+ """
+
+ super(CoreDecoder, self).__init__()
+
+ # hyper parameters
+ self.input_dim = input_dim
+ self.output_dim = output_dim
+ self.cond_size = cond_size
+ self.cond_size2 = cond_size2
+ self.state_size = state_size
+
+ self.input_size = self.input_dim
+
+ # layers
+ self.dense_1 = nn.Linear(self.input_size, 96)
+ self.gru1 = nn.GRU(96, 96, batch_first=True)
+ self.conv1 = MyConv(192, 32)
+ self.gru2 = nn.GRU(224, 96, batch_first=True)
+ self.conv2 = MyConv(320, 32)
+ self.gru3 = nn.GRU(352, 96, batch_first=True)
+ self.conv3 = MyConv(448, 32)
+ self.gru4 = nn.GRU(480, 96, batch_first=True)
+ self.conv4 = MyConv(576, 32)
+ self.gru5 = nn.GRU(608, 96, batch_first=True)
+ self.conv5 = MyConv(704, 32)
+ self.output = nn.Linear(736, self.FRAMES_PER_STEP * self.output_dim)
+ self.glu1 = GLU(96)
+ self.glu2 = GLU(96)
+ self.glu3 = GLU(96)
+ self.glu4 = GLU(96)
+ self.glu5 = GLU(96)
+ self.hidden_init = nn.Linear(self.state_size, 128)
+ self.gru_init = nn.Linear(128, 480)
+
+ nb_params = sum(p.numel() for p in self.parameters())
+ print(f"decoder: {nb_params} weights")
+ # initialize weights
+ self.apply(init_weights)
+ self.sparsifier = []
+ self.sparsifier.append(GRUSparsifier([(self.gru1, sparse_params1)], sparsify_start, sparsify_stop, sparsify_interval, sparsify_exponent))
+ self.sparsifier.append(GRUSparsifier([(self.gru2, sparse_params1)], sparsify_start, sparsify_stop, sparsify_interval, sparsify_exponent))
+ self.sparsifier.append(GRUSparsifier([(self.gru3, sparse_params1)], sparsify_start, sparsify_stop, sparsify_interval, sparsify_exponent))
+ self.sparsifier.append(GRUSparsifier([(self.gru4, sparse_params2)], sparsify_start, sparsify_stop, sparsify_interval, sparsify_exponent))
+ self.sparsifier.append(GRUSparsifier([(self.gru5, sparse_params2)], sparsify_start, sparsify_stop, sparsify_interval, sparsify_exponent))
+
+ def sparsify(self):
+ for sparsifier in self.sparsifier:
+ sparsifier.step()
+
+ def forward(self, z, initial_state):
+
+ hidden = torch.tanh(self.hidden_init(initial_state))
+ gru_state = torch.tanh(self.gru_init(hidden).permute(1, 0, 2))
+ h1_state = gru_state[:,:,:96].contiguous()
+ h2_state = gru_state[:,:,96:192].contiguous()
+ h3_state = gru_state[:,:,192:288].contiguous()
+ h4_state = gru_state[:,:,288:384].contiguous()
+ h5_state = gru_state[:,:,384:].contiguous()
+
+ # run decoding layer stack
+ x = n(torch.tanh(self.dense_1(z)))
+
+ x = torch.cat([x, n(self.glu1(n(self.gru1(x, h1_state)[0])))], -1)
+ x = torch.cat([x, n(self.conv1(x))], -1)
+ x = torch.cat([x, n(self.glu2(n(self.gru2(x, h2_state)[0])))], -1)
+ x = torch.cat([x, n(self.conv2(x))], -1)
+ x = torch.cat([x, n(self.glu3(n(self.gru3(x, h3_state)[0])))], -1)
+ x = torch.cat([x, n(self.conv3(x))], -1)
+ x = torch.cat([x, n(self.glu4(n(self.gru4(x, h4_state)[0])))], -1)
+ x = torch.cat([x, n(self.conv4(x))], -1)
+ x = torch.cat([x, n(self.glu5(n(self.gru5(x, h5_state)[0])))], -1)
+ x = torch.cat([x, n(self.conv5(x))], -1)
+
+ # output layer and reshaping
+ x10 = self.output(x)
+ features = torch.reshape(x10, (x10.size(0), x10.size(1) * self.FRAMES_PER_STEP, x10.size(2) // self.FRAMES_PER_STEP))
+
+ return features
+
+
+class StatisticalModel(nn.Module):
+ def __init__(self, quant_levels, latent_dim, state_dim):
+ """ Statistical model for latent space
+
+ Computes scaling, deadzone, r, and theta
+
+ """
+
+ super(StatisticalModel, self).__init__()
+
+ # copy parameters
+ self.latent_dim = latent_dim
+ self.state_dim = state_dim
+ self.total_dim = latent_dim + state_dim
+ self.quant_levels = quant_levels
+ self.embedding_dim = 6 * self.total_dim
+
+ # quantization embedding
+ self.quant_embedding = nn.Embedding(quant_levels, self.embedding_dim)
+
+ # initialize embedding to 0
+ with torch.no_grad():
+ self.quant_embedding.weight[:] = 0
+
+
+ def forward(self, quant_ids):
+ """ takes quant_ids and returns statistical model parameters"""
+
+ x = self.quant_embedding(quant_ids)
+
+ # CAVE: theta_soft is not used anymore. Kick it out?
+ quant_scale = F.softplus(x[..., 0 * self.total_dim : 1 * self.total_dim])
+ dead_zone = F.softplus(x[..., 1 * self.total_dim : 2 * self.total_dim])
+ theta_soft = torch.sigmoid(x[..., 2 * self.total_dim : 3 * self.total_dim])
+ r_soft = torch.sigmoid(x[..., 3 * self.total_dim : 4 * self.total_dim])
+ theta_hard = torch.sigmoid(x[..., 4 * self.total_dim : 5 * self.total_dim])
+ r_hard = torch.sigmoid(x[..., 5 * self.total_dim : 6 * self.total_dim])
+
+
+ return {
+ 'quant_embedding' : x,
+ 'quant_scale' : quant_scale,
+ 'dead_zone' : dead_zone,
+ 'r_hard' : r_hard,
+ 'theta_hard' : theta_hard,
+ 'r_soft' : r_soft,
+ 'theta_soft' : theta_soft
+ }
+
+
+class RDOVAE(nn.Module):
+ def __init__(self,
+ feature_dim,
+ latent_dim,
+ quant_levels,
+ cond_size,
+ cond_size2,
+ state_dim=24,
+ split_mode='split',
+ clip_weights=False,
+ pvq_num_pulses=82,
+ state_dropout_rate=0):
+
+ super(RDOVAE, self).__init__()
+
+ self.feature_dim = feature_dim
+ self.latent_dim = latent_dim
+ self.quant_levels = quant_levels
+ self.cond_size = cond_size
+ self.cond_size2 = cond_size2
+ self.split_mode = split_mode
+ self.state_dim = state_dim
+ self.pvq_num_pulses = pvq_num_pulses
+ self.state_dropout_rate = state_dropout_rate
+
+ # submodules encoder and decoder share the statistical model
+ self.statistical_model = StatisticalModel(quant_levels, latent_dim, state_dim)
+ self.core_encoder = nn.DataParallel(CoreEncoder(feature_dim, latent_dim, cond_size, cond_size2, state_size=state_dim))
+ self.core_decoder = nn.DataParallel(CoreDecoder(latent_dim, feature_dim, cond_size, cond_size2, state_size=state_dim))
+
+ self.enc_stride = CoreEncoder.FRAMES_PER_STEP
+ self.dec_stride = CoreDecoder.FRAMES_PER_STEP
+
+ if clip_weights:
+ self.weight_clip_fn = weight_clip_factory(0.496)
+ else:
+ self.weight_clip_fn = None
+
+ if self.dec_stride % self.enc_stride != 0:
+ raise ValueError(f"get_decoder_chunks_generic: encoder stride does not divide decoder stride")
+
+ def clip_weights(self):
+ if not type(self.weight_clip_fn) == type(None):
+ self.apply(self.weight_clip_fn)
+
+ def sparsify(self):
+ #self.core_encoder.module.sparsify()
+ self.core_decoder.module.sparsify()
+
+ def get_decoder_chunks(self, z_frames, mode='split', chunks_per_offset = 4):
+
+ enc_stride = self.enc_stride
+ dec_stride = self.dec_stride
+
+ stride = dec_stride // enc_stride
+
+ chunks = []
+
+ for offset in range(stride):
+ # start is the smalles number = offset mod stride that decodes to a valid range
+ start = offset
+ while enc_stride * (start + 1) - dec_stride < 0:
+ start += stride
+
+ # check if start is a valid index
+ if start >= z_frames:
+ raise ValueError("get_decoder_chunks_generic: range too small")
+
+ # stop is the smallest number outside [0, num_enc_frames] that's congruent to offset mod stride
+ stop = z_frames - (z_frames % stride) + offset
+ while stop < z_frames:
+ stop += stride
+
+ # calculate split points
+ length = (stop - start)
+ if mode == 'split':
+ split_points = [start + stride * int(i * length / chunks_per_offset / stride) for i in range(chunks_per_offset)] + [stop]
+ elif mode == 'random_split':
+ split_points = [stride * x + start for x in random_split(0, (stop - start)//stride - 1, chunks_per_offset - 1, 1)]
+ else:
+ raise ValueError(f"get_decoder_chunks_generic: unknown mode {mode}")
+
+
+ for i in range(chunks_per_offset):
+ # (enc_frame_start, enc_frame_stop, enc_frame_stride, stride, feature_frame_start, feature_frame_stop)
+ # encoder range(i, j, stride) maps to feature range(enc_stride * (i + 1) - dec_stride, enc_stride * j)
+ # provided that i - j = 1 mod stride
+ chunks.append({
+ 'z_start' : split_points[i],
+ 'z_stop' : split_points[i + 1] - stride + 1,
+ 'z_stride' : stride,
+ 'features_start' : enc_stride * (split_points[i] + 1) - dec_stride,
+ 'features_stop' : enc_stride * (split_points[i + 1] - stride + 1)
+ })
+
+ return chunks
+
+
+ def forward(self, features, q_id):
+
+ # calculate statistical model from quantization ID
+ statistical_model = self.statistical_model(q_id)
+
+ # run encoder
+ z, states = self.core_encoder(features)
+
+ # scaling, dead-zone and quantization
+ z = z * statistical_model['quant_scale'][:,:,:self.latent_dim]
+ z = soft_dead_zone(z, statistical_model['dead_zone'][:,:,:self.latent_dim])
+
+ # quantization
+ z_q = hard_quantize(z) / statistical_model['quant_scale'][:,:,:self.latent_dim]
+ z_n = noise_quantize(z) / statistical_model['quant_scale'][:,:,:self.latent_dim]
+ #states_q = soft_pvq(states, self.pvq_num_pulses)
+ states = states * statistical_model['quant_scale'][:,:,self.latent_dim:]
+ states = soft_dead_zone(states, statistical_model['dead_zone'][:,:,self.latent_dim:])
+
+ states_q = hard_quantize(states) / statistical_model['quant_scale'][:,:,self.latent_dim:]
+ states_n = noise_quantize(states) / statistical_model['quant_scale'][:,:,self.latent_dim:]
+
+ if self.state_dropout_rate > 0:
+ drop = torch.rand(states_q.size(0)) < self.state_dropout_rate
+ mask = torch.ones_like(states_q)
+ mask[drop] = 0
+ states_q = states_q * mask
+
+ # decoder
+ chunks = self.get_decoder_chunks(z.size(1), mode=self.split_mode)
+
+ outputs_hq = []
+ outputs_sq = []
+ for chunk in chunks:
+ # decoder with hard quantized input
+ z_dec_reverse = torch.flip(z_q[..., chunk['z_start'] : chunk['z_stop'] : chunk['z_stride'], :], [1])
+ dec_initial_state = states_q[..., chunk['z_stop'] - 1 : chunk['z_stop'], :]
+ features_reverse = self.core_decoder(z_dec_reverse, dec_initial_state)
+ outputs_hq.append((torch.flip(features_reverse, [1]), chunk['features_start'], chunk['features_stop']))
+
+
+ # decoder with soft quantized input
+ z_dec_reverse = torch.flip(z_n[..., chunk['z_start'] : chunk['z_stop'] : chunk['z_stride'], :], [1])
+ dec_initial_state = states_n[..., chunk['z_stop'] - 1 : chunk['z_stop'], :]
+ features_reverse = self.core_decoder(z_dec_reverse, dec_initial_state)
+ outputs_sq.append((torch.flip(features_reverse, [1]), chunk['features_start'], chunk['features_stop']))
+
+ return {
+ 'outputs_hard_quant' : outputs_hq,
+ 'outputs_soft_quant' : outputs_sq,
+ 'z' : z,
+ 'states' : states,
+ 'statistical_model' : statistical_model
+ }
+
+ def encode(self, features):
+ """ encoder with quantization and rate estimation """
+
+ z, states = self.core_encoder(features)
+
+ # quantization of initial states
+ states = soft_pvq(states, self.pvq_num_pulses)
+ state_size = m.log2(pvq_codebook_size(self.state_dim, self.pvq_num_pulses))
+
+ return z, states, state_size
+
+ def decode(self, z, initial_state):
+ """ decoder (flips sequences by itself) """
+
+ z_reverse = torch.flip(z, [1])
+ features_reverse = self.core_decoder(z_reverse, initial_state)
+ features = torch.flip(features_reverse, [1])
+
+ return features
+
+ def quantize(self, z, q_ids):
+ """ quantization of latent vectors """
+
+ stats = self.statistical_model(q_ids)
+
+ zq = z * stats['quant_scale'][:self.latent_dim]
+ zq = soft_dead_zone(zq, stats['dead_zone'][:self.latent_dim])
+ zq = torch.round(zq)
+
+ sizes = hard_rate_estimate(zq, stats['r_hard'][:,:,:self.latent_dim], stats['theta_hard'][:,:,:self.latent_dim], reduce=False)
+
+ return zq, sizes
+
+ def unquantize(self, zq, q_ids):
+ """ re-scaling of latent vector """
+
+ stats = self.statistical_model(q_ids)
+
+ z = zq / stats['quant_scale'][:,:,:self.latent_dim]
+
+ return z
+
+ def freeze_model(self):
+
+ # freeze all parameters
+ for p in self.parameters():
+ p.requires_grad = False
+
+ for p in self.statistical_model.parameters():
+ p.requires_grad = True
diff --git a/dnn/torch/rdovae/requirements.txt b/dnn/torch/rdovae/requirements.txt
new file mode 100644
index 00000000..9225ea84
--- /dev/null
+++ b/dnn/torch/rdovae/requirements.txt
@@ -0,0 +1,4 @@
+numpy
+scipy
+torch
+tqdm \ No newline at end of file
diff --git a/dnn/torch/rdovae/train_rdovae.py b/dnn/torch/rdovae/train_rdovae.py
new file mode 100644
index 00000000..d9a43b33
--- /dev/null
+++ b/dnn/torch/rdovae/train_rdovae.py
@@ -0,0 +1,278 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import argparse
+
+import torch
+import tqdm
+
+from rdovae import RDOVAE, RDOVAEDataset, distortion_loss, hard_rate_estimate, soft_rate_estimate
+
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('features', type=str, help='path to feature file in .f32 format')
+parser.add_argument('output', type=str, help='path to output folder')
+
+parser.add_argument('--cuda-visible-devices', type=str, help="comma separates list of cuda visible device indices, default: ''", default="")
+
+
+model_group = parser.add_argument_group(title="model parameters")
+model_group.add_argument('--latent-dim', type=int, help="number of symbols produces by encoder, default: 80", default=80)
+model_group.add_argument('--cond-size', type=int, help="first conditioning size, default: 256", default=256)
+model_group.add_argument('--cond-size2', type=int, help="second conditioning size, default: 256", default=256)
+model_group.add_argument('--state-dim', type=int, help="dimensionality of transfered state, default: 24", default=24)
+model_group.add_argument('--quant-levels', type=int, help="number of quantization levels, default: 16", default=16)
+model_group.add_argument('--lambda-min', type=float, help="minimal value for rate lambda, default: 0.0002", default=2e-4)
+model_group.add_argument('--lambda-max', type=float, help="maximal value for rate lambda, default: 0.0104", default=0.0104)
+model_group.add_argument('--pvq-num-pulses', type=int, help="number of pulses for PVQ, default: 82", default=82)
+model_group.add_argument('--state-dropout-rate', type=float, help="state dropout rate, default: 0", default=0.0)
+
+training_group = parser.add_argument_group(title="training parameters")
+training_group.add_argument('--batch-size', type=int, help="batch size, default: 32", default=32)
+training_group.add_argument('--lr', type=float, help='learning rate, default: 3e-4', default=3e-4)
+training_group.add_argument('--epochs', type=int, help='number of training epochs, default: 100', default=100)
+training_group.add_argument('--sequence-length', type=int, help='sequence length, needs to be divisible by 4, default: 256', default=256)
+training_group.add_argument('--lr-decay-factor', type=float, help='learning rate decay factor, default: 2.5e-5', default=2.5e-5)
+training_group.add_argument('--split-mode', type=str, choices=['split', 'random_split'], help='splitting mode for decoder input, default: split', default='split')
+training_group.add_argument('--enable-first-frame-loss', action='store_true', default=False, help='enables dedicated distortion loss on first 4 decoder frames')
+training_group.add_argument('--initial-checkpoint', type=str, help='initial checkpoint to start training from, default: None', default=None)
+training_group.add_argument('--train-decoder-only', action='store_true', help='freeze encoder and statistical model and train decoder only')
+
+args = parser.parse_args()
+
+# set visible devices
+os.environ['CUDA_VISIBLE_DEVICES'] = args.cuda_visible_devices
+
+# checkpoints
+checkpoint_dir = os.path.join(args.output, 'checkpoints')
+checkpoint = dict()
+os.makedirs(checkpoint_dir, exist_ok=True)
+
+# training parameters
+batch_size = args.batch_size
+lr = args.lr
+epochs = args.epochs
+sequence_length = args.sequence_length
+lr_decay_factor = args.lr_decay_factor
+split_mode = args.split_mode
+# not exposed
+adam_betas = [0.8, 0.95]
+adam_eps = 1e-8
+
+checkpoint['batch_size'] = batch_size
+checkpoint['lr'] = lr
+checkpoint['lr_decay_factor'] = lr_decay_factor
+checkpoint['split_mode'] = split_mode
+checkpoint['epochs'] = epochs
+checkpoint['sequence_length'] = sequence_length
+checkpoint['adam_betas'] = adam_betas
+
+# logging
+log_interval = 10
+
+# device
+device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
+
+# model parameters
+cond_size = args.cond_size
+cond_size2 = args.cond_size2
+latent_dim = args.latent_dim
+quant_levels = args.quant_levels
+lambda_min = args.lambda_min
+lambda_max = args.lambda_max
+state_dim = args.state_dim
+# not expsed
+num_features = 20
+
+
+# training data
+feature_file = args.features
+
+# model
+checkpoint['model_args'] = (num_features, latent_dim, quant_levels, cond_size, cond_size2)
+checkpoint['model_kwargs'] = {'state_dim': state_dim, 'split_mode' : split_mode, 'pvq_num_pulses': args.pvq_num_pulses, 'state_dropout_rate': args.state_dropout_rate}
+model = RDOVAE(*checkpoint['model_args'], **checkpoint['model_kwargs'])
+
+if type(args.initial_checkpoint) != type(None):
+ checkpoint = torch.load(args.initial_checkpoint, map_location='cpu')
+ model.load_state_dict(checkpoint['state_dict'], strict=False)
+
+checkpoint['state_dict'] = model.state_dict()
+
+if args.train_decoder_only:
+ if args.initial_checkpoint is None:
+ print("warning: training decoder only without providing initial checkpoint")
+
+ for p in model.core_encoder.module.parameters():
+ p.requires_grad = False
+
+ for p in model.statistical_model.parameters():
+ p.requires_grad = False
+
+# dataloader
+checkpoint['dataset_args'] = (feature_file, sequence_length, num_features, 36)
+checkpoint['dataset_kwargs'] = {'lambda_min': lambda_min, 'lambda_max': lambda_max, 'enc_stride': model.enc_stride, 'quant_levels': quant_levels}
+dataset = RDOVAEDataset(*checkpoint['dataset_args'], **checkpoint['dataset_kwargs'])
+dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, drop_last=True, num_workers=4)
+
+
+
+# optimizer
+params = [p for p in model.parameters() if p.requires_grad]
+optimizer = torch.optim.Adam(params, lr=lr, betas=adam_betas, eps=adam_eps)
+
+
+# learning rate scheduler
+scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer=optimizer, lr_lambda=lambda x : 1 / (1 + lr_decay_factor * x))
+
+if __name__ == '__main__':
+
+ # push model to device
+ model.to(device)
+
+ # training loop
+
+ for epoch in range(1, epochs + 1):
+
+ print(f"training epoch {epoch}...")
+
+ # running stats
+ running_rate_loss = 0
+ running_soft_dist_loss = 0
+ running_hard_dist_loss = 0
+ running_hard_rate_loss = 0
+ running_soft_rate_loss = 0
+ running_total_loss = 0
+ running_rate_metric = 0
+ running_states_rate_metric = 0
+ previous_total_loss = 0
+ running_first_frame_loss = 0
+
+ with tqdm.tqdm(dataloader, unit='batch') as tepoch:
+ for i, (features, rate_lambda, q_ids) in enumerate(tepoch):
+
+ # zero out gradients
+ optimizer.zero_grad()
+
+ # push inputs to device
+ features = features.to(device)
+ q_ids = q_ids.to(device)
+ rate_lambda = rate_lambda.to(device)
+
+
+ rate_lambda_upsamp = torch.repeat_interleave(rate_lambda, 2, 1)
+
+ # run model
+ model_output = model(features, q_ids)
+
+ # collect outputs
+ z = model_output['z']
+ states = model_output['states']
+ outputs_hard_quant = model_output['outputs_hard_quant']
+ outputs_soft_quant = model_output['outputs_soft_quant']
+ statistical_model = model_output['statistical_model']
+
+ # rate loss
+ hard_rate = hard_rate_estimate(z, statistical_model['r_hard'][:,:,:latent_dim], statistical_model['theta_hard'][:,:,:latent_dim], reduce=False)
+ soft_rate = soft_rate_estimate(z, statistical_model['r_soft'][:,:,:latent_dim], reduce=False)
+ states_hard_rate = hard_rate_estimate(states, statistical_model['r_hard'][:,:,latent_dim:], statistical_model['theta_hard'][:,:,latent_dim:], reduce=False)
+ states_soft_rate = soft_rate_estimate(states, statistical_model['r_soft'][:,:,latent_dim:], reduce=False)
+ soft_rate_loss = torch.mean(torch.sqrt(rate_lambda) * (soft_rate + .02*states_soft_rate))
+ hard_rate_loss = torch.mean(torch.sqrt(rate_lambda) * (hard_rate + .02*states_hard_rate))
+ rate_loss = (soft_rate_loss + 0.1 * hard_rate_loss)
+ hard_rate_metric = torch.mean(hard_rate)
+ states_rate_metric = torch.mean(states_hard_rate)
+
+ ## distortion losses
+
+ # hard quantized decoder input
+ distortion_loss_hard_quant = torch.zeros_like(rate_loss)
+ for dec_features, start, stop in outputs_hard_quant:
+ distortion_loss_hard_quant += distortion_loss(features[..., start : stop, :], dec_features, rate_lambda_upsamp[..., start : stop]) / len(outputs_hard_quant)
+
+ first_frame_loss = torch.zeros_like(rate_loss)
+ for dec_features, start, stop in outputs_hard_quant:
+ first_frame_loss += distortion_loss(features[..., stop-4 : stop, :], dec_features[..., -4:, :], rate_lambda_upsamp[..., stop - 4 : stop]) / len(outputs_hard_quant)
+
+ # soft quantized decoder input
+ distortion_loss_soft_quant = torch.zeros_like(rate_loss)
+ for dec_features, start, stop in outputs_soft_quant:
+ distortion_loss_soft_quant += distortion_loss(features[..., start : stop, :], dec_features, rate_lambda_upsamp[..., start : stop]) / len(outputs_soft_quant)
+
+ # total loss
+ total_loss = rate_loss + (distortion_loss_hard_quant + distortion_loss_soft_quant) / 2
+
+ if args.enable_first_frame_loss:
+ total_loss = .97*total_loss + 0.03 * first_frame_loss
+
+
+ total_loss.backward()
+
+ optimizer.step()
+
+ model.clip_weights()
+ model.sparsify()
+
+ scheduler.step()
+
+ # collect running stats
+ running_hard_dist_loss += float(distortion_loss_hard_quant.detach().cpu())
+ running_soft_dist_loss += float(distortion_loss_soft_quant.detach().cpu())
+ running_rate_loss += float(rate_loss.detach().cpu())
+ running_rate_metric += float(hard_rate_metric.detach().cpu())
+ running_states_rate_metric += float(states_rate_metric.detach().cpu())
+ running_total_loss += float(total_loss.detach().cpu())
+ running_first_frame_loss += float(first_frame_loss.detach().cpu())
+ running_soft_rate_loss += float(soft_rate_loss.detach().cpu())
+ running_hard_rate_loss += float(hard_rate_loss.detach().cpu())
+
+ if (i + 1) % log_interval == 0:
+ current_loss = (running_total_loss - previous_total_loss) / log_interval
+ tepoch.set_postfix(
+ current_loss=current_loss,
+ total_loss=running_total_loss / (i + 1),
+ dist_hq=running_hard_dist_loss / (i + 1),
+ dist_sq=running_soft_dist_loss / (i + 1),
+ rate_loss=running_rate_loss / (i + 1),
+ rate=running_rate_metric / (i + 1),
+ states_rate=running_states_rate_metric / (i + 1),
+ ffloss=running_first_frame_loss / (i + 1),
+ rateloss_hard=running_hard_rate_loss / (i + 1),
+ rateloss_soft=running_soft_rate_loss / (i + 1)
+ )
+ previous_total_loss = running_total_loss
+
+ # save checkpoint
+ checkpoint_path = os.path.join(checkpoint_dir, f'checkpoint_epoch_{epoch}.pth')
+ checkpoint['state_dict'] = model.state_dict()
+ checkpoint['loss'] = running_total_loss / len(dataloader)
+ checkpoint['epoch'] = epoch
+ torch.save(checkpoint, checkpoint_path)
diff --git a/dnn/torch/testsuite/README.md b/dnn/torch/testsuite/README.md
new file mode 100644
index 00000000..cc76965e
--- /dev/null
+++ b/dnn/torch/testsuite/README.md
@@ -0,0 +1,46 @@
+# lpcnet-testsuite
+
+## setup
+The test script is written for Linux only. It requires sox to be installed and available.
+
+Setup is done as usual via
+
+```
+pip install -r requirements.txt
+```
+
+The test scrip run_warpq_test.py requires a setup file in yaml format, which specifies how
+to generate a wave file OUTPUT from a wave file INPUT sampled resampled to the specified
+sampling rate as a list of shell commands. This makes it easy to test other neural vocoders
+with it as well. Two examples are given in examples. INPUT and OUTPUT will be replaced by using
+the string.format(INPUT=input,OUTPUT=output) method.
+
+Here is one example:
+
+```
+test: "LPCNet reference test"
+processing:
+ - "sox {INPUT} {INPUT}.raw"
+ - "/local/code/LPCNet/lpcnet_demo -features {INPUT}.raw {INPUT}.features.f32"
+ - "/local/code/LPCNet/lpcnet_demo -synthesis {INPUT}.features.f32 {INPUT}.decoded.raw"
+ - "sox -r 16000 -L -e signed-integer -b 16 -c 1 {INPUT}.decoded.raw {OUTPUT}"
+```
+
+The structure of the output folder is as follows:
+
+```
+output_folder
++-- html
+ +-- index.html
+ +-- items
++-- processing
++-- setup.yml
++-- stats.txt
++-- scores.txt
+```
+
+scores.txt contains the WARP-Q scores in descending order (best to worse)
+stats.txt contains mean values over all, the 10 best and the 10 worst items
+setup.yml contains all information to repeat the run
+htms contains a self-contained website displaying the 10 best and 10 worst items
+processing contains processing output \ No newline at end of file
diff --git a/dnn/torch/testsuite/examples/lpcnet_c_example.yml b/dnn/torch/testsuite/examples/lpcnet_c_example.yml
new file mode 100644
index 00000000..2858309c
--- /dev/null
+++ b/dnn/torch/testsuite/examples/lpcnet_c_example.yml
@@ -0,0 +1,6 @@
+test: "LPCNet reference test"
+processing:
+ - "sox {INPUT} {INPUT}.raw"
+ - "/local/code/LPCNet/lpcnet_demo -features {INPUT}.raw {INPUT}.features.f32"
+ - "/local/code/LPCNet/lpcnet_demo -synthesis {INPUT}.features.f32 {INPUT}.decoded.raw"
+ - "sox -r 16000 -L -e signed-integer -b 16 -c 1 {INPUT}.decoded.raw {OUTPUT} trim 0.015" \ No newline at end of file
diff --git a/dnn/torch/testsuite/examples/lpcnet_c_plc_example.yml b/dnn/torch/testsuite/examples/lpcnet_c_plc_example.yml
new file mode 100644
index 00000000..b97b26d1
--- /dev/null
+++ b/dnn/torch/testsuite/examples/lpcnet_c_plc_example.yml
@@ -0,0 +1,5 @@
+test: "LPCNet reference test"
+processing:
+ - "sox {INPUT} {INPUT}.raw"
+ - "/local/code/LPCNet/lpcnet_demo -plc_file causal {PLCFILE} {INPUT}.raw {INPUT}.decoded.raw"
+ - "sox -r 16000 -L -e signed-integer -b 16 -c 1 {INPUT}.decoded.raw {OUTPUT}" \ No newline at end of file
diff --git a/dnn/torch/testsuite/examples/lpcnet_torch_example.yml b/dnn/torch/testsuite/examples/lpcnet_torch_example.yml
new file mode 100644
index 00000000..631cbfad
--- /dev/null
+++ b/dnn/torch/testsuite/examples/lpcnet_torch_example.yml
@@ -0,0 +1,5 @@
+test: "no noise test"
+processing:
+ - "sox {INPUT} {INPUT}.raw"
+ - "/home/ubuntu/bin/lpcnet_dump_data_v2 -test {INPUT}.raw {INPUT}.features.f32"
+ - "/home/ubuntu/opt/miniconda3/envs/torch/bin/python /local/code/lpcnext/test_lpcnet.py {INPUT}.features.f32 /local/experiments/noise_augmentation/output/lpcnet_384_2/checkpoints/checkpoint_epoch_20.pth {OUTPUT}" \ No newline at end of file
diff --git a/dnn/torch/testsuite/requirements.txt b/dnn/torch/testsuite/requirements.txt
new file mode 100644
index 00000000..af2d9916
--- /dev/null
+++ b/dnn/torch/testsuite/requirements.txt
@@ -0,0 +1,12 @@
+scipy
+librosa
+numpy
+scikit-image
+pyvad
+speechpy
+soundfile
+pyyaml
+pesq
+AMFM_decompy
+matplotlib
+multiprocess \ No newline at end of file
diff --git a/dnn/torch/testsuite/run_test.py b/dnn/torch/testsuite/run_test.py
new file mode 100644
index 00000000..69463ddb
--- /dev/null
+++ b/dnn/torch/testsuite/run_test.py
@@ -0,0 +1,375 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import multiprocess as multiprocessing
+import random
+import subprocess
+import argparse
+import shutil
+
+import yaml
+
+from utils.files import get_wave_file_list
+from utils.pesq import compute_PESQ
+from utils.pitch import compute_pitch_error
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('setup', type=str, help='setup yaml specifying end to end processing with model under test')
+parser.add_argument('input_folder', type=str, help='input folder path')
+parser.add_argument('output_folder', type=str, help='output folder path')
+parser.add_argument('--num-testitems', type=int, help="number of testitems to be processed (default 100)", default=100)
+parser.add_argument('--seed', type=int, help='seed for random item selection', default=None)
+parser.add_argument('--fs', type=int, help="sampling rate at which input is presented as wave file (defaults to 16000)", default=16000)
+parser.add_argument('--num-workers', type=int, help="number of subprocesses to be used (default=4)", default=4)
+parser.add_argument('--plc-suffix', type=str, default="_is_lost.txt", help="suffix of plc error pattern file: only relevant if command chain uses PLCFILE (default=_is_lost.txt)")
+parser.add_argument('--metrics', type=str, default='pesq', help='comma separated string of metrics, supported: {{"pesq", "pitch_error", "voicing_error"}}, default="pesq"')
+parser.add_argument('--verbose', action='store_true', help='enables printouts of all commands run in the pipeline')
+
+def check_for_sox_in_path():
+ r = subprocess.run("sox -h", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ return r.returncode == 0
+
+
+def run_save_sh(command, verbose=False):
+
+ if verbose:
+ print(f"[run_save_sh] running command {command}...")
+
+ r = subprocess.run(command, shell=True)
+ if r.returncode != 0:
+ raise RuntimeError(f"command '{command}' failed with exit code {r.returncode}")
+
+
+def run_processing_chain(input_path, output_path, model_commands, fs, metrics={'pesq'}, plc_suffix="_is_lost.txt", verbose=False):
+
+ # prepare model input
+ model_input = output_path + ".resamp.wav"
+ run_save_sh(f"sox {input_path} -r {fs} {model_input}", verbose=verbose)
+
+ plcfile = os.path.splitext(input_path)[0] + plc_suffix
+ if os.path.isfile(plcfile):
+ run_save_sh(f"cp {plcfile} {os.path.dirname(output_path)}")
+
+ # generate model output
+ for command in model_commands:
+ run_save_sh(command.format(INPUT=model_input, OUTPUT=output_path, PLCFILE=plcfile), verbose=verbose)
+
+ scores = dict()
+ cache = dict()
+ for metric in metrics:
+ if metric == 'pesq':
+ # run pesq
+ score = compute_PESQ(input_path, output_path, fs=fs)
+ elif metric == 'pitch_error':
+ if metric in cache:
+ score = cache[metric]
+ else:
+ rval = compute_pitch_error(input_path, output_path, fs=fs)
+ score = rval[metric]
+ cache['voicing_error'] = rval['voicing_error']
+ elif metric == 'voicing_error':
+ if metric in cache:
+ score = cache[metric]
+ else:
+ rval = compute_pitch_error(input_path, output_path, fs=fs)
+ score = rval[metric]
+ cache['pitch_error'] = rval['pitch_error']
+ else:
+ ValueError(f'error: unknown metric {metric}')
+
+ scores[metric] = score
+
+ return (output_path, scores)
+
+
+def get_output_path(root_folder, input, output_folder):
+
+ input_relpath = os.path.relpath(input, root_folder)
+
+ os.makedirs(os.path.join(output_folder, 'processing', os.path.dirname(input_relpath)), exist_ok=True)
+
+ output_path = os.path.join(output_folder, 'processing', input_relpath + '.output.wav')
+
+ return output_path
+
+
+def add_audio_table(f, html_folder, results, title, metric):
+
+ item_folder = os.path.join(html_folder, 'items')
+ os.makedirs(item_folder, exist_ok=True)
+
+ # table with results
+ f.write(f"""
+ <div>
+ <h2> {title} </h2>
+ <table>
+ <tr>
+ <th> Rank </th>
+ <th> Name </th>
+ <th> {metric.upper()} </th>
+ <th> Audio (out) </th>
+ <th> Audio (orig) </th>
+ </tr>
+ """)
+
+ for i, r in enumerate(results):
+ item, score = r
+ item_name = os.path.basename(item)
+ new_item_path = os.path.join(item_folder, item_name)
+ shutil.copyfile(item, new_item_path)
+ shutil.copyfile(item + '.resamp.wav', os.path.join(item_folder, item_name + '.orig.wav'))
+
+ f.write(f"""
+ <tr>
+ <td> {i + 1} </td>
+ <td> {item_name.split('.')[0]} </td>
+ <td> {score:.3f} </td>
+ <td>
+ <audio controls>
+ <source src="items/{item_name}">
+ </audio>
+ </td>
+ <td>
+ <audio controls>
+ <source src="items/{item_name + '.orig.wav'}">
+ </audio>
+ </td>
+ </tr>
+ """)
+
+ # footer
+ f.write("""
+ </table>
+ </div>
+ """)
+
+
+def create_html(output_folder, results, title, metric):
+
+ html_folder = output_folder
+ items_folder = os.path.join(html_folder, 'items')
+ os.makedirs(html_folder, exist_ok=True)
+ os.makedirs(items_folder, exist_ok=True)
+
+ with open(os.path.join(html_folder, 'index.html'), 'w') as f:
+ # header and title
+ f.write(f"""
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>{title}</title>
+ <style>
+ article {{
+ align-items: flex-start;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4em;
+ }}
+ html {{
+ box-sizing: border-box;
+ font-family: "Amazon Ember", "Source Sans", "Verdana", "Calibri", sans-serif;
+ padding: 2em;
+ }}
+ td {{
+ padding: 3px 7px;
+ text-align: center;
+ }}
+ td:first-child {{
+ text-align: end;
+ }}
+ th {{
+ background: #ff9900;
+ color: #000;
+ font-size: 1.2em;
+ padding: 7px 7px;
+ }}
+ </style>
+ </head>
+ </body>
+ <h1>{title}</h1>
+ <article>
+ """)
+
+ # top 20
+ add_audio_table(f, html_folder, results[:-21: -1], "Top 20", metric)
+
+ # 20 around median
+ N = len(results) // 2
+ add_audio_table(f, html_folder, results[N + 10 : N - 10: -1], "Median 20", metric)
+
+ # flop 20
+ add_audio_table(f, html_folder, results[:20], "Flop 20", metric)
+
+ # footer
+ f.write("""
+ </article>
+ </body>
+ </html>
+ """)
+
+metric_sorting_signs = {
+ 'pesq' : 1,
+ 'pitch_error' : -1,
+ 'voicing_error' : -1
+}
+
+def is_valid_result(data, metrics):
+ if not isinstance(data, dict):
+ return False
+
+ for metric in metrics:
+ if not metric in data:
+ return False
+
+ return True
+
+
+def evaluate_results(output_folder, results, metric):
+
+ results = sorted(results, key=lambda x : metric_sorting_signs[metric] * x[1])
+ with open(os.path.join(args.output_folder, f'scores_{metric}.txt'), 'w') as f:
+ for result in results:
+ f.write(f"{os.path.relpath(result[0], args.output_folder)} {result[1]}\n")
+
+
+ # some statistics
+ mean = sum([r[1] for r in results]) / len(results)
+ top_mean = sum([r[1] for r in results[-20:]]) / 20
+ bottom_mean = sum([r[1] for r in results[:20]]) / 20
+
+ with open(os.path.join(args.output_folder, f'stats_{metric}.txt'), 'w') as f:
+ f.write(f"mean score: {mean}\n")
+ f.write(f"bottom mean score: {bottom_mean}\n")
+ f.write(f"top mean score: {top_mean}\n")
+
+ print(f"\nmean score: {mean}")
+ print(f"bottom mean score: {bottom_mean}")
+ print(f"top mean score: {top_mean}\n")
+
+ # create output html
+ create_html(os.path.join(output_folder, 'html', metric), results, setup['test'], metric)
+
+if __name__ == "__main__":
+ args = parser.parse_args()
+
+ # check for sox
+ if not check_for_sox_in_path():
+ raise RuntimeError("script requires sox")
+
+
+ # prepare output folder
+ if os.path.exists(args.output_folder):
+ print("warning: output folder exists")
+
+ reply = input('continue? (y/n): ')
+ while reply not in {'y', 'n'}:
+ reply = input('continue? (y/n): ')
+
+ if reply == 'n':
+ os._exit()
+ else:
+ # start with a clean sleight
+ shutil.rmtree(args.output_folder)
+
+ os.makedirs(args.output_folder, exist_ok=True)
+
+ # extract metrics
+ metrics = args.metrics.split(",")
+ for metric in metrics:
+ if not metric in metric_sorting_signs:
+ print(f"unknown metric {metric}")
+ args.usage()
+
+ # read setup
+ print(f"loading {args.setup}...")
+ with open(args.setup, "r") as f:
+ setup = yaml.load(f.read(), yaml.FullLoader)
+
+ model_commands = setup['processing']
+
+ print("\nfound the following model commands:")
+ for command in model_commands:
+ print(command.format(INPUT='input.wav', OUTPUT='output.wav', PLCFILE='input_is_lost.txt'))
+
+ # store setup to output folder
+ setup['input'] = os.path.abspath(args.input_folder)
+ setup['output'] = os.path.abspath(args.output_folder)
+ setup['seed'] = args.seed
+ with open(os.path.join(args.output_folder, 'setup.yml'), 'w') as f:
+ yaml.dump(setup, f)
+
+ # get input
+ print(f"\nCollecting audio files from {args.input_folder}...")
+ file_list = get_wave_file_list(args.input_folder, check_for_features=False)
+ print(f"...{len(file_list)} files found\n")
+
+ # sample from file list
+ file_list = sorted(file_list)
+ random.seed(args.seed)
+ random.shuffle(file_list)
+ num_testitems = min(args.num_testitems, len(file_list))
+ file_list = file_list[:num_testitems]
+
+
+ print(f"\nlaunching test on {num_testitems} items...")
+ # helper function for parallel processing
+ def func(input_path):
+ output_path = get_output_path(args.input_folder, input_path, args.output_folder)
+
+ try:
+ rval = run_processing_chain(input_path, output_path, model_commands, args.fs, metrics=metrics, plc_suffix=args.plc_suffix, verbose=args.verbose)
+ except:
+ rval = (input_path, -1)
+
+ return rval
+
+ with multiprocessing.Pool(args.num_workers) as p:
+ results = p.map(func, file_list)
+
+ results_dict = dict()
+ for name, values in results:
+ if is_valid_result(values, metrics):
+ results_dict[name] = values
+
+ print(results_dict)
+
+ # evaluating results
+ num_failures = num_testitems - len(results_dict)
+ print(f"\nprocessing of {num_failures} items failed\n")
+
+ for metric in metrics:
+ print(metric)
+ evaluate_results(
+ args.output_folder,
+ [(name, value[metric]) for name, value in results_dict.items()],
+ metric
+ ) \ No newline at end of file
diff --git a/dnn/torch/testsuite/utils/__init__.py b/dnn/torch/testsuite/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/dnn/torch/testsuite/utils/__init__.py
diff --git a/dnn/torch/testsuite/utils/files.py b/dnn/torch/testsuite/utils/files.py
new file mode 100644
index 00000000..c3a15536
--- /dev/null
+++ b/dnn/torch/testsuite/utils/files.py
@@ -0,0 +1,54 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+
+
+def get_wave_file_list(parent_folder, extensions=[".wav", ".flac"], check_for_features=False):
+ """ traverses subfolders of parent_folder in search for files that match the given extension """
+
+ file_list = []
+
+ for root, dirs, files in os.walk(parent_folder, topdown=True):
+
+ for file in files:
+
+ stem, ext = os.path.splitext(file)
+
+ #check for extension
+ if not ext in extensions:
+ continue
+
+ # check if feature file exists
+ if check_for_features and not os.path.isfile(os.path.join(root, stem + "_features.f32")):
+ continue
+
+ file_list.append(os.path.join(root, file))
+
+ return file_list \ No newline at end of file
diff --git a/dnn/torch/testsuite/utils/pesq.py b/dnn/torch/testsuite/utils/pesq.py
new file mode 100644
index 00000000..5a27f518
--- /dev/null
+++ b/dnn/torch/testsuite/utils/pesq.py
@@ -0,0 +1,43 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import pesq
+import librosa
+
+def compute_PESQ(ref, test, fs=16000):
+
+ if not ref.endswith('.wav') or not test.endswith('.wav'):
+ raise ValueError('error: expecting .wav as file extension')
+
+ ref_item, _ = librosa.load(ref, sr=fs)
+ test_item, _ = librosa.load(test, sr=fs)
+
+ score = pesq.pesq(fs, ref_item, test_item)
+
+ return score \ No newline at end of file
diff --git a/dnn/torch/testsuite/utils/pitch.py b/dnn/torch/testsuite/utils/pitch.py
new file mode 100644
index 00000000..f9429651
--- /dev/null
+++ b/dnn/torch/testsuite/utils/pitch.py
@@ -0,0 +1,61 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import numpy as np
+from scipy.io import wavfile
+import amfm_decompy.pYAAPT as pYAAPT
+import amfm_decompy.basic_tools as basic
+
+def get_voicing_info(x, sr=16000):
+
+ signal = basic.SignalObj(x, sr)
+ pitch = pYAAPT.yaapt(signal, **{'frame_length' : 20.0, 'tda_frame_length' : 20.0})
+
+ pitch_values = pitch.samp_values
+ voiced_flags = pitch.vuv.astype('float')
+
+ return pitch_values, voiced_flags
+
+def compute_pitch_error(ref_path, test_path, fs=16000):
+ fs_orig, x_orig = wavfile.read(ref_path)
+ fs_test, x_test = wavfile.read(test_path)
+
+ min_length = min(len(x_orig), len(x_test))
+ x_orig = x_orig[:min_length]
+ x_test = x_test[:min_length]
+
+ assert fs_orig == fs_test == fs
+
+ pitch_contour_orig, voicing_orig = get_voicing_info(x_orig.astype(np.float32))
+ pitch_contour_test, voicing_test = get_voicing_info(x_test.astype(np.float32))
+
+ return {
+ 'pitch_error' : np.mean(np.abs(pitch_contour_orig - pitch_contour_test)).item(),
+ 'voicing_error' : np.sum(np.abs(voicing_orig - voicing_test)).item() / len(voicing_orig)
+ } \ No newline at end of file
diff --git a/dnn/torch/weight-exchange/README.md b/dnn/torch/weight-exchange/README.md
new file mode 100644
index 00000000..f4818b5b
--- /dev/null
+++ b/dnn/torch/weight-exchange/README.md
@@ -0,0 +1,21 @@
+# weight-exchange
+
+
+
+## Weight Exchange
+Repo wor exchanging weights betweeen torch an tensorflow.keras modules, using an intermediate numpy format.
+
+Routines for loading/dumping torch weights are located in exchange/torch and can be loaded with
+```
+import exchange.torch
+```
+and routines for loading/dumping tensorflow weights are located in exchange/tf and can be loaded with
+```
+import exchange.tf
+```
+
+Note that `exchange.torch` requires torch to be installed and `exchange.tf` requires tensorflow. To avoid the necessity of installing both torch and tensorflow in the working environment, none of these submodules is imported when calling `import exchange`. Similarly, the requirements listed in `requirements.txt` do include neither Tensorflow or Pytorch.
+
+
+## C export
+The module `exchange.c_export` contains routines to export weights to C files. On the long run it will be possible to call all `dump_...` functions with either a path string or a `CWriter` instance based on which the export format is chosen. This is currently only implemented for `torch.nn.GRU`, `torch.nn.Linear` and `torch.nn.Conv1d`. \ No newline at end of file
diff --git a/dnn/torch/weight-exchange/requirements.txt b/dnn/torch/weight-exchange/requirements.txt
new file mode 100644
index 00000000..296d6545
--- /dev/null
+++ b/dnn/torch/weight-exchange/requirements.txt
@@ -0,0 +1 @@
+numpy \ No newline at end of file
diff --git a/dnn/torch/weight-exchange/setup.py b/dnn/torch/weight-exchange/setup.py
new file mode 100644
index 00000000..2b20440b
--- /dev/null
+++ b/dnn/torch/weight-exchange/setup.py
@@ -0,0 +1,48 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+#!/usr/bin/env/python
+import os
+from setuptools import setup
+
+lib_folder = os.path.dirname(os.path.realpath(__file__))
+
+with open(os.path.join(lib_folder, 'requirements.txt'), 'r') as f:
+ install_requires = list(f.read().splitlines())
+
+print(install_requires)
+
+setup(name='wexchange',
+ version='1.6',
+ author='Jan Buethe',
+ author_email='jbuethe@amazon.de',
+ description='Weight-exchange library between Pytorch and Tensorflow',
+ packages=['wexchange', 'wexchange.tf', 'wexchange.torch', 'wexchange.c_export'],
+ install_requires=install_requires
+ )
diff --git a/dnn/torch/weight-exchange/wexchange/__init__.py b/dnn/torch/weight-exchange/wexchange/__init__.py
new file mode 100644
index 00000000..a0286142
--- /dev/null
+++ b/dnn/torch/weight-exchange/wexchange/__init__.py
@@ -0,0 +1,30 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+from . import c_export \ No newline at end of file
diff --git a/dnn/torch/weight-exchange/wexchange/c_export/__init__.py b/dnn/torch/weight-exchange/wexchange/c_export/__init__.py
new file mode 100644
index 00000000..2a580c80
--- /dev/null
+++ b/dnn/torch/weight-exchange/wexchange/c_export/__init__.py
@@ -0,0 +1,31 @@
+from .c_writer import CWriter
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+from .common import print_gru_layer, print_dense_layer, print_conv1d_layer, print_tconv1d_layer, print_conv2d_layer, print_vector \ No newline at end of file
diff --git a/dnn/torch/weight-exchange/wexchange/c_export/c_writer.py b/dnn/torch/weight-exchange/wexchange/c_export/c_writer.py
new file mode 100644
index 00000000..2745f337
--- /dev/null
+++ b/dnn/torch/weight-exchange/wexchange/c_export/c_writer.py
@@ -0,0 +1,182 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+from collections import OrderedDict
+
+class CWriter:
+ def __init__(self,
+ filename_without_extension,
+ message=None,
+ header_only=False,
+ create_state_struct=False,
+ enable_binary_blob=True,
+ model_struct_name="Model",
+ nnet_header="nnet.h",
+ add_typedef=False):
+ """
+ Writer class for creating souce and header files for weight exports to C
+
+ Parameters:
+ -----------
+
+ filename_without_extension: str
+ filename from which .c and .h files are created
+
+ message: str, optional
+ if given and not None, this message will be printed as comment in the header file
+
+ header_only: bool, optional
+ if True, only a header file is created; defaults to False
+
+ enable_binary_blob: bool, optional
+ if True, export is done in binary blob format and a model type is created; defaults to False
+
+ create_state_struct: bool, optional
+ if True, a state struct type is created in the header file; if False, state sizes are defined as macros; defaults to False
+
+ model_struct_name: str, optional
+ name used for the model struct type; only relevant when enable_binary_blob is True; defaults to "Model"
+
+ nnet_header: str, optional
+ name of header nnet header file; defaults to nnet.h
+
+ """
+
+
+ self.header_only = header_only
+ self.enable_binary_blob = enable_binary_blob
+ self.create_state_struct = create_state_struct
+ self.model_struct_name = model_struct_name
+ self.add_typedef = add_typedef
+
+ # for binary blob format, format is key=<layer name>, value=(<layer type>, <init call>)
+ self.layer_dict = OrderedDict()
+
+ # for binary blob format, format is key=<layer name>, value=<layer type>
+ self.weight_arrays = []
+
+ # form model struct, format is key=<layer name>, value=<number of elements>
+ self.state_dict = OrderedDict()
+
+ self.header = open(filename_without_extension + ".h", "w")
+ header_name = os.path.basename(filename_without_extension) + '.h'
+
+ if message is not None:
+ self.header.write(f"/* {message} */\n\n")
+
+ self.header_guard = os.path.basename(filename_without_extension).upper() + "_H"
+ self.header.write(
+f'''
+#ifndef {self.header_guard}
+#define {self.header_guard}
+
+#include "{nnet_header}"
+
+'''
+ )
+
+ if not self.header_only:
+ self.source = open(filename_without_extension + ".c", "w")
+ if message is not None:
+ self.source.write(f"/* {message} */\n\n")
+
+ self.source.write(
+f"""
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+""")
+ self.source.write(f'#include "{header_name}"\n\n')
+
+
+ def _finalize_header(self):
+
+ # create model type
+ if self.enable_binary_blob:
+ if self.add_typedef:
+ self.header.write(f"\ntypedef struct {{")
+ else:
+ self.header.write(f"\nstruct {self.model_struct_name} {{")
+ for name, data in self.layer_dict.items():
+ layer_type = data[0]
+ self.header.write(f"\n {layer_type} {name};")
+ if self.add_typedef:
+ self.header.write(f"\n}} {self.model_struct_name};\n")
+ else:
+ self.header.write(f"\n}};\n")
+
+ init_prototype = f"int init_{self.model_struct_name.lower()}({self.model_struct_name} *model, const WeightArray *arrays)"
+ self.header.write(f"\n{init_prototype};\n")
+
+ self.header.write(f"\n#endif /* {self.header_guard} */\n")
+
+ def _finalize_source(self):
+
+ if self.enable_binary_blob:
+ # create weight array
+ if len(set(self.weight_arrays)) != len(self.weight_arrays):
+ raise ValueError("error: detected duplicates in weight arrays")
+ self.source.write("\n#ifndef USE_WEIGHTS_FILE\n")
+ self.source.write(f"const WeightArray {self.model_struct_name.lower()}_arrays[] = {{\n")
+ for name in self.weight_arrays:
+ self.source.write(f"#ifdef WEIGHTS_{name}_DEFINED\n")
+ self.source.write(f' {{"{name}", WEIGHTS_{name}_TYPE, sizeof({name}), {name}}},\n')
+ self.source.write(f"#endif\n")
+ self.source.write(" {NULL, 0, 0, NULL}\n")
+ self.source.write("};\n")
+
+ self.source.write("#endif /* USE_WEIGHTS_FILE */\n")
+
+ # create init function definition
+ init_prototype = f"int init_{self.model_struct_name.lower()}({self.model_struct_name} *model, const WeightArray *arrays)"
+ self.source.write("\n#ifndef DUMP_BINARY_WEIGHTS\n")
+ self.source.write(f"{init_prototype} {{\n")
+ for name, data in self.layer_dict.items():
+ self.source.write(f" if ({data[1]}) return 1;\n")
+ self.source.write(" return 0;\n")
+ self.source.write("}\n")
+ self.source.write("#endif /* DUMP_BINARY_WEIGHTS */\n")
+
+
+ def close(self):
+
+ if not self.header_only:
+ self._finalize_source()
+ self.source.close()
+
+ self._finalize_header()
+ self.header.close()
+
+ def __del__(self):
+ try:
+ self.close()
+ except:
+ pass \ No newline at end of file
diff --git a/dnn/torch/weight-exchange/wexchange/c_export/common.py b/dnn/torch/weight-exchange/wexchange/c_export/common.py
new file mode 100644
index 00000000..039edd9b
--- /dev/null
+++ b/dnn/torch/weight-exchange/wexchange/c_export/common.py
@@ -0,0 +1,387 @@
+'''Copyright (c) 2017-2018 Mozilla
+ Copyright (c) 2022 Amazon
+
+ 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 FOUNDATION 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.
+'''
+
+import numpy as np
+
+from .c_writer import CWriter
+
+def print_vector(writer, vector, name, dtype='float', reshape_8x4=False, static=True, debug_float=False):
+
+ if isinstance(writer, CWriter):
+ f = writer.source
+ binary_blob = writer.enable_binary_blob
+ else:
+ f = writer
+ binary_blob = False
+
+ dtype_suffix = {
+ 'float' : 'float',
+ 'opus_int8' : 'int8',
+ 'opus_uint16' : 'uint16',
+ 'opus_int16' : 'int16',
+ 'int' : 'int',
+ 'qweight': 'qweight'
+ }
+
+
+ if binary_blob:
+ f.write(
+f'''
+#ifndef USE_WEIGHTS_FILE
+'''
+ )
+ writer.weight_arrays.append(name)
+
+ if reshape_8x4:
+ vector = vector.reshape((vector.shape[0]//4, 4, vector.shape[1]//8, 8))
+ vector = vector.transpose((2, 0, 3, 1))
+
+ v = np.reshape(vector, (-1))
+
+ if debug_float:
+ f.write('#ifndef DISABLE_DEBUG_FLOAT\n')
+ if binary_blob:
+ f.write(
+f'''
+#define WEIGHTS_{name}_DEFINED
+#define WEIGHTS_{name}_TYPE WEIGHT_TYPE_{dtype_suffix[dtype]}
+'''
+ )
+
+ if static:
+ f.write('static ')
+
+ f.write(f'const {dtype} {name}[{len(v)}] = {{\n ')
+
+ for i in range(0, len(v)):
+
+ f.write(f'{v[i]}')
+
+ if (i!=len(v)-1):
+ f.write(',')
+ else:
+ break
+
+ if (i%8==7):
+ f.write("\n ")
+ else:
+ f.write(" ")
+
+ f.write('\n};\n\n')
+ if debug_float: f.write('#endif /*DISABLE_DEBUG_FLOAT*/\n')
+
+ if binary_blob:
+ f.write(
+f'''
+#endif /* USE_WEIGHTS_FILE */
+'''
+ )
+
+ return vector
+
+
+
+def extract_diagonal(A):
+ """ input shape is (N, k*N) """
+
+ N, M = A.shape
+ B = A.copy()
+ assert M % N == 0
+ k = M // N
+
+ diags = []
+ for l in range(k):
+ diag = np.diag(B[:, l * N : (l+1) * N]).copy()
+ B[:, l * N : (l+1) * N] -= np.diag(diag)
+ diags.append(diag)
+
+ diag = np.concatenate(diags)
+
+ return diag, B
+
+def quantize_weight(weight, scale):
+ scale = scale + 1e-30
+ Aq = np.round(weight / scale).astype('int')
+ if Aq.max() > 127 or Aq.min() <= -128:
+ raise ValueError("value out of bounds in quantize_weight")
+ Aq = np.clip(np.round(weight / scale).astype('int'), -128, 127)
+ return Aq
+
+
+def print_sparse_weight(writer, A, name, scale=1/128, have_diag=True, quantize=False):
+ N = A.shape[0]
+ M = A.shape[1]
+ W = np.zeros((0,), dtype='int')
+ W0 = np.zeros((0,))
+
+ if have_diag:
+ diag, A = extract_diagonal(A)
+ print_vector(writer, diag, name + '_diag')
+
+ if quantize:
+ Aq = quantize_weight(A, scale)
+ else:
+ Aq = A
+
+ # extract blocks
+ idx = np.zeros((0,), dtype='int')
+ for i in range(M//8):
+ pos = idx.shape[0]
+ idx = np.append(idx, -1)
+ nb_nonzero = 0
+ for j in range(N//4):
+ block = A[j*4:(j+1)*4, i*8:(i+1)*8]
+ qblock = Aq[j*4:(j+1)*4, i*8:(i+1)*8]
+ if np.sum(np.abs(block)) > 1e-10:
+ nb_nonzero = nb_nonzero + 1
+ idx = np.append(idx, j*4)
+ vblock = qblock.transpose((1,0)).reshape((-1,))
+ W0 = np.concatenate([W0, block.reshape((-1,))])
+ W = np.concatenate([W, vblock])
+ idx[pos] = nb_nonzero
+
+ if quantize: print_vector(writer, W, name + '_int8', reshape_8x4=False, dtype='opus_int8')
+ print_vector(writer, W0, name + '_float', reshape_8x4=False, dtype='float', debug_float=quantize)
+ print_vector(writer, idx, name + '_idx', reshape_8x4=False, dtype='int')
+
+ return Aq
+
+
+
+def compute_scaling(weight):
+ """ computes optimal scaling vector for weight of shape (features_in, features_out) """
+
+ n_in, n_out = weight.shape
+ assert n_in % 4 == 0 and n_out % 8 == 0
+
+ weight_max_abs = np.max(np.abs(weight), axis=0)
+ weight_max_sum = np.max(np.abs(weight[: n_in : 2] + weight[1 : n_in : 2]), axis=0)
+ scale_max = weight_max_abs / 127
+ scale_sum = weight_max_sum / 129
+
+ scale = np.maximum(scale_max, scale_sum)
+
+ return scale
+
+def qn(string):
+ if string == "NULL": return string
+ else: return '"' + string + '"'
+
+def print_linear_layer(writer : CWriter,
+ name : str,
+ weight : np.ndarray,
+ bias : np.ndarray,
+ scale : np.ndarray = None,
+ sparse : bool = False,
+ diagonal : bool = False,
+ quantize : bool = True):
+
+ """ prints linear layer
+
+ Parameters:
+ -----------
+ name : str
+ layer name
+ weight: np.ndarray
+ ...
+ scale: np.ndarray or None
+ If None auto scaling will be applied. Otherwise, output channels will be multiplied by scale (the usual broadcasting rules apply).
+
+
+ """
+
+ if len(weight.shape) != 2:
+ raise ValueError('expecting 2-dim weight array in print_linear_layer')
+
+
+ bias_name = "NULL" if bias is None else name + "_bias"
+ subias_name = name + "_subias" if quantize else "NULL"
+ scale_name = name + "_scale" if quantize else "NULL"
+ idx_name = name + "_weights_idx" if sparse else "NULL"
+ float_weight_name = name + "_weights_float"
+ int_weight_name = name + "_weights_int8" if quantize else "NULL"
+ diag_name = name + "_weights_diag" if sparse and diagonal else "NULL"
+
+ nb_inputs, nb_outputs = weight.shape
+
+ if scale is None and quantize:
+ scale = compute_scaling(weight)
+
+
+ if sparse:
+ weight_q = print_sparse_weight(writer, weight, name + "_weights", scale=scale, have_diag=diagonal, quantize=quantize)
+ else:
+ if quantize:
+ weight_q = quantize_weight(weight, scale)
+ print_vector(writer, weight_q, name + "_weights_int8", dtype='opus_int8', reshape_8x4=True)
+
+ print_vector(writer, weight, name + "_weights_float", dtype='float', reshape_8x4=False, debug_float=quantize)
+
+ if quantize:
+ subias = (np.zeros(nb_outputs) if bias is None else bias) - np.sum(weight_q * scale, axis=0)
+ print_vector(writer, subias, name + "_subias")
+
+ final_scale = scale / 127 * np.ones(nb_outputs)
+ print_vector(writer, final_scale, name + "_scale")
+
+ if bias is not None:
+ print_vector(writer, bias, name + "_bias")
+
+
+ init_call = f'linear_init(&model->{name}, arrays, {qn(bias_name)}, {qn(subias_name)}, {qn(int_weight_name)},' \
+ + f'{qn(float_weight_name)}, {qn(idx_name)}, {qn(diag_name)}, {qn(scale_name)}, {nb_inputs}, {nb_outputs})'
+
+ writer.layer_dict[name] = ('LinearLayer', init_call)
+
+
+def print_dense_layer(writer : CWriter,
+ name : str,
+ weight : np.ndarray,
+ bias : np.ndarray,
+ scale=1/128,
+ format : str = 'torch',
+ sparse=False,
+ diagonal=False,
+ quantize=False):
+
+ if format == 'torch':
+ weight = weight.transpose()
+
+ print_linear_layer(writer, name, weight, bias, scale=scale, sparse=sparse, diagonal=diagonal, quantize=quantize)
+
+ writer.header.write(f"\n#define {name.upper()}_OUT_SIZE {weight.shape[1]}\n")
+
+
+def print_conv1d_layer(writer : CWriter,
+ name : str,
+ weight : np.ndarray,
+ bias : np.ndarray,
+ scale=1/128,
+ format : str = 'torch',
+ quantize=False,
+ sparse=False):
+
+
+ if format == "torch":
+ # convert to channels last
+ weight = np.transpose(weight, (2, 1, 0))
+
+ lin_weight = np.reshape(weight, (-1, weight.shape[-1]))
+ print_linear_layer(writer, name, lin_weight, bias, scale=scale, sparse=sparse, diagonal=False, quantize=quantize)
+
+
+ writer.header.write(f"\n#define {name.upper()}_OUT_SIZE {weight.shape[2]}\n")
+ writer.header.write(f"\n#define {name.upper()}_IN_SIZE {weight.shape[1]}\n")
+ writer.header.write(f"\n#define {name.upper()}_STATE_SIZE ({weight.shape[1]} * ({weight.shape[0] - 1}))\n")
+ writer.header.write(f"\n#define {name.upper()}_DELAY {(weight.shape[0] - 1) // 2}\n") # CAVE: delay is not a property of the conv layer
+
+ return weight.shape[0] * weight.shape[1]
+
+def print_conv2d_layer(writer : CWriter,
+ name : str,
+ weight : np.ndarray,
+ bias : np.ndarray,
+ scale : float=1/128,
+ quantize : bool=False):
+
+ if quantize:
+ print("[print_conv2d_layer] warning: quantize argument ignored")
+
+ bias_name = name + "_bias"
+ float_weight_name = name + "_weight_float"
+
+ print_vector(writer, weight, float_weight_name)
+ print_vector(writer, bias, bias_name)
+
+ # init function
+ out_channels, in_channels, ksize1, ksize2 = weight.shape
+ init_call = f'conv2d_init(&model->{name}, arrays, "{bias_name}", "{float_weight_name}", {in_channels}, {out_channels}, {ksize1}, {ksize2})'
+
+ writer.layer_dict[name] = ('Conv2dLayer', init_call)
+
+
+
+def print_gru_layer(writer : CWriter,
+ name : str,
+ weight : np.ndarray,
+ recurrent_weight : np.ndarray,
+ bias : np.ndarray,
+ recurrent_bias : np.ndarray,
+ format : str = 'torch',
+ quantize : bool = False,
+ input_sparse : bool = False,
+ recurrent_sparse : bool = False,
+ scale=1/128,
+ recurrent_scale=1/128
+ ):
+
+ if format == "torch":
+ # change gate ordering from rzn to zrn
+
+ N = weight.shape[0] // 3
+ for x in [weight, recurrent_weight, bias, recurrent_bias]:
+ if x is None: continue
+ tmp = x[0:N].copy()
+ x[0:N] = x[N:2*N]
+ x[N:2*N] = tmp
+
+ weight = weight.transpose()
+ recurrent_weight = recurrent_weight.transpose()
+ else:
+ N = weight.shape[1] // 3
+
+ print_linear_layer(writer, name + "_input", weight, bias, scale=scale, sparse=input_sparse, quantize=quantize)
+ print_linear_layer(writer, name + "_recurrent", recurrent_weight, recurrent_bias, scale=recurrent_scale, sparse=recurrent_sparse, diagonal=recurrent_sparse, quantize=quantize)
+
+ # wrapping it up
+ writer.header.write(f"\n#define {name.upper()}_OUT_SIZE {N}\n")
+ writer.header.write(f"\n#define {name.upper()}_STATE_SIZE {N}\n")
+
+ return N
+
+
+def print_tconv1d_layer(writer : CWriter,
+ name : str,
+ weight : np.ndarray,
+ bias : np.ndarray,
+ stride: int,
+ scale=1/128,
+ quantize=False,
+ sparse=False):
+
+ in_channels, out_channels, kernel_size = weight.shape
+
+
+ linear_weight = weight.transpose(2, 1, 0).reshape(kernel_size * out_channels, in_channels).transpose(1, 0)
+ linear_bias = np.repeat(bias[np.newaxis, :], kernel_size, 0).flatten()
+
+ print_linear_layer(writer, name, linear_weight, linear_bias, scale=scale, quantize=quantize, sparse=sparse)
+
+ writer.header.write(f"\n#define {name.upper()}_KERNEL_SIZE {kernel_size}\n")
+ writer.header.write(f"\n#define {name.upper()}_STRIDE {stride}\n")
+ writer.header.write(f"\n#define {name.upper()}_IN_CHANNELS {in_channels}\n")
+ writer.header.write(f"\n#define {name.upper()}_OUT_CHANNELS {out_channels}\n") \ No newline at end of file
diff --git a/dnn/torch/weight-exchange/wexchange/tf/__init__.py b/dnn/torch/weight-exchange/wexchange/tf/__init__.py
new file mode 100644
index 00000000..02678048
--- /dev/null
+++ b/dnn/torch/weight-exchange/wexchange/tf/__init__.py
@@ -0,0 +1,5 @@
+from .tf import dump_tf_conv1d_weights, load_tf_conv1d_weights
+from .tf import dump_tf_dense_weights, load_tf_dense_weights
+from .tf import dump_tf_embedding_weights, load_tf_embedding_weights
+from .tf import dump_tf_gru_weights, load_tf_gru_weights
+from .tf import dump_tf_weights, load_tf_weights \ No newline at end of file
diff --git a/dnn/torch/weight-exchange/wexchange/tf/tf.py b/dnn/torch/weight-exchange/wexchange/tf/tf.py
new file mode 100644
index 00000000..bebbb55a
--- /dev/null
+++ b/dnn/torch/weight-exchange/wexchange/tf/tf.py
@@ -0,0 +1,188 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+
+import tensorflow as tf
+import numpy as np
+
+from wexchange.c_export import CWriter, print_gru_layer, print_dense_layer, print_conv1d_layer
+
+def dump_tf_gru_weights(where, gru, name='gru', input_sparse=False, recurrent_sparse=False, quantize=False, scale=1/128, recurrent_scale=1/128):
+
+
+ assert gru.activation == tf.keras.activations.tanh
+ assert gru.recurrent_activation == tf.keras.activations.sigmoid
+ assert gru.reset_after == True
+
+ w_ih = gru.weights[0].numpy().transpose().copy()
+ w_hh = gru.weights[1].numpy().transpose().copy()
+ b_ih = gru.weights[2].numpy()[0].copy()
+ b_hh = gru.weights[2].numpy()[1].copy()
+
+ if isinstance(where, CWriter):
+ return print_gru_layer(where, name, w_ih, w_hh, b_ih, b_hh, format='tf', input_sparse=input_sparse, recurrent_sparse=recurrent_sparse, quantize=quantize, scale=scale, recurrent_scale=recurrent_scale)
+ else:
+ os.makedirs(where, exist_ok=True)
+
+ # zrn => rzn
+ N = w_ih.shape[0] // 3
+ for x in [w_ih, w_hh, b_ih, b_hh]:
+ tmp = x[0:N].copy()
+ x[0:N] = x[N:2*N]
+ x[N:2*N] = tmp
+
+ np.save(os.path.join(where, 'weight_ih_rzn.npy'), w_ih)
+ np.save(os.path.join(where, 'weight_hh_rzn.npy'), w_hh)
+ np.save(os.path.join(where, 'bias_ih_rzn.npy'), b_ih)
+ np.save(os.path.join(where, 'bias_hh_rzn.npy'), b_hh)
+
+
+def load_tf_gru_weights(path, gru):
+
+ assert gru.activation == tf.keras.activations.tanh
+ assert gru.recurrent_activation == tf.keras.activations.sigmoid
+ assert gru.reset_after == True
+
+ w_ih = np.load(os.path.join(path, 'weight_ih_rzn.npy'))
+ w_hh = np.load(os.path.join(path, 'weight_hh_rzn.npy'))
+ b_ih = np.load(os.path.join(path, 'bias_ih_rzn.npy'))
+ b_hh = np.load(os.path.join(path, 'bias_hh_rzn.npy'))
+
+ # rzn => zrn
+ N = w_ih.shape[0] // 3
+ for x in [w_ih, w_hh, b_ih, b_hh]:
+ tmp = x[0:N].copy()
+ x[0:N] = x[N:2*N]
+ x[N:2*N] = tmp
+
+ gru.weights[0].assign(tf.convert_to_tensor(w_ih.transpose()))
+ gru.weights[1].assign(tf.convert_to_tensor(w_hh.transpose()))
+ gru.weights[2].assign(tf.convert_to_tensor(np.vstack((b_ih, b_hh))))
+
+
+def dump_tf_dense_weights(where, dense, name='dense', scale=1/128, sparse=False, diagonal=False, quantize=False):
+
+ w = dense.weights[0].numpy()
+ if dense.bias is None:
+ b = np.zeros(dense.units, dtype=w.dtype)
+ else:
+ b = dense.bias.numpy()
+
+
+
+ if isinstance(where, CWriter):
+ return print_dense_layer(where, name, w, b, scale=scale, format='tf', sparse=sparse, diagonal=diagonal, quantize=quantize)
+
+ else:
+ os.makedirs(where, exist_ok=True)
+
+ np.save(os.path.join(where, 'weight.npy'), w.transpose())
+ np.save(os.path.join(where, 'bias.npy'), b)
+
+
+def load_tf_dense_weights(path, dense):
+
+ w = np.load(os.path.join(path, 'weight.npy')).transpose()
+ b = np.load(os.path.join(path, 'bias.npy'))
+
+ dense.weights[0].assign(tf.convert_to_tensor(w))
+ if dense.bias is not None:
+ dense.weights[1].assign(tf.convert_to_tensor(b))
+
+
+def dump_tf_conv1d_weights(where, conv, name='conv', scale=1/128, quantize=False):
+
+ assert conv.data_format == 'channels_last'
+
+ w = conv.weights[0].numpy().copy()
+ if conv.bias is None:
+ b = np.zeros(conv.filters, dtype=w.dtype)
+ else:
+ b = conv.bias.numpy()
+
+ if isinstance(where, CWriter):
+ return print_conv1d_layer(where, name, w, b, scale=scale, format='tf', quantize=quantize)
+ else:
+ os.makedirs(where, exist_ok=True)
+
+ w = np.transpose(w, (2, 1, 0))
+ np.save(os.path.join(where, 'weight_oik.npy'), w)
+ np.save(os.path.join(where, 'bias.npy'), b)
+
+
+def load_tf_conv1d_weights(path, conv):
+
+ w = np.load(os.path.join(path, 'weight_oik.npy'))
+ b = np.load(os.path.join(path, 'bias.npy'))
+
+ w = np.transpose(w, (2, 1, 0))
+
+ conv.weights[0].assign(tf.convert_to_tensor(w))
+ if conv.bias is not None:
+ conv.weights[1].assign(tf.convert_to_tensor(b))
+
+
+def dump_tf_embedding_weights(path, emb):
+ os.makedirs(path, exist_ok=True)
+
+ w = emb.weights[0].numpy()
+ np.save(os.path.join(path, 'weight.npy'), w)
+
+
+
+def load_tf_embedding_weights(path, emb):
+
+ w = np.load(os.path.join(path, 'weight.npy'))
+ emb.weights[0].assign(tf.convert_to_tensor(w))
+
+
+def dump_tf_weights(path, module):
+ if isinstance(module, tf.keras.layers.Dense):
+ dump_tf_dense_weights(path, module)
+ elif isinstance(module, tf.keras.layers.GRU):
+ dump_tf_gru_weights(path, module)
+ elif isinstance(module, tf.keras.layers.Conv1D):
+ dump_tf_conv1d_weights(path, module)
+ elif isinstance(module, tf.keras.layers.Embedding):
+ dump_tf_embedding_weights(path, module)
+ else:
+ raise ValueError(f'dump_tf_weights: layer of type {type(module)} not supported')
+
+def load_tf_weights(path, module):
+ if isinstance(module, tf.keras.layers.Dense):
+ load_tf_dense_weights(path, module)
+ elif isinstance(module, tf.keras.layers.GRU):
+ load_tf_gru_weights(path, module)
+ elif isinstance(module, tf.keras.layers.Conv1D):
+ load_tf_conv1d_weights(path, module)
+ elif isinstance(module, tf.keras.layers.Embedding):
+ load_tf_embedding_weights(path, module)
+ else:
+ raise ValueError(f'dump_tf_weights: layer of type {type(module)} not supported') \ No newline at end of file
diff --git a/dnn/torch/weight-exchange/wexchange/torch/__init__.py b/dnn/torch/weight-exchange/wexchange/torch/__init__.py
new file mode 100644
index 00000000..8245566d
--- /dev/null
+++ b/dnn/torch/weight-exchange/wexchange/torch/__init__.py
@@ -0,0 +1,37 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+from .torch import dump_torch_conv1d_weights, load_torch_conv1d_weights
+from .torch import dump_torch_conv2d_weights, load_torch_conv2d_weights
+from .torch import dump_torch_dense_weights, load_torch_dense_weights
+from .torch import dump_torch_gru_weights, load_torch_gru_weights
+from .torch import dump_torch_grucell_weights
+from .torch import dump_torch_embedding_weights, load_torch_embedding_weights
+from .torch import dump_torch_weights, load_torch_weights
+from .torch import dump_torch_adaptive_conv1d_weights \ No newline at end of file
diff --git a/dnn/torch/weight-exchange/wexchange/torch/torch.py b/dnn/torch/weight-exchange/wexchange/torch/torch.py
new file mode 100644
index 00000000..af5d3e59
--- /dev/null
+++ b/dnn/torch/weight-exchange/wexchange/torch/torch.py
@@ -0,0 +1,433 @@
+"""
+/* Copyright (c) 2023 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import os
+import sys
+
+import torch
+import numpy as np
+
+sys.path.append(sys.path.append(os.path.join(os.path.dirname(__file__), '../osce')))
+try:
+ import utils.layers as osce_layers
+ from utils.layers.limited_adaptive_conv1d import LimitedAdaptiveConv1d
+ from utils.layers.limited_adaptive_comb1d import LimitedAdaptiveComb1d
+ from utils.layers.td_shaper import TDShaper
+ has_osce=True
+except:
+ has_osce=False
+
+from wexchange.c_export import CWriter, print_gru_layer, print_dense_layer, print_conv1d_layer, print_tconv1d_layer, print_conv2d_layer
+
+def dump_torch_adaptive_conv1d_weights(where, adaconv, name='adaconv', scale=1/128, quantize=False):
+
+
+ w_kernel = adaconv.conv_kernel.weight.detach().cpu().numpy().copy()
+ b_kernel = adaconv.conv_kernel.bias.detach().cpu().numpy().copy()
+ w_gain = adaconv.filter_gain.weight.detach().cpu().numpy().copy()
+ b_gain = adaconv.filter_gain.bias.detach().cpu().numpy().copy()
+
+ if isinstance(where, CWriter):
+ # pad kernel for quantization
+ left_padding = adaconv.padding[0]
+ kernel_size = adaconv.kernel_size
+ in_channels = adaconv.in_channels
+ out_channels = adaconv.out_channels
+ feature_dim = adaconv.feature_dim
+
+ if quantize and kernel_size % 8:
+ kernel_padding = 8 - (kernel_size % 8)
+ w_kernel = np.concatenate(
+ (np.zeros((out_channels, in_channels, kernel_padding, feature_dim)), w_kernel.reshape(out_channels, in_channels, kernel_size, feature_dim)),
+ dtype=w_kernel.dtype,
+ axis=2).reshape(-1, feature_dim)
+ b_kernel = np.concatenate(
+ (np.zeros((out_channels, in_channels, kernel_padding)), b_kernel.reshape(out_channels, in_channels, kernel_size)),
+ dtype=b_kernel.dtype,
+ axis=2).reshape(-1)
+ left_padding += kernel_padding
+ kernel_size += kernel_padding
+
+ # write relevant scalar parameters to header file
+ where.header.write(f"""
+#define {name.upper()}_FILTER_GAIN_A {adaconv.filter_gain_a:f}f
+#define {name.upper()}_FILTER_GAIN_B {adaconv.filter_gain_b:f}f
+#define {name.upper()}_SHAPE_GAIN {adaconv.shape_gain:f}f
+#define {name.upper()}_KERNEL_SIZE {kernel_size}
+#define {name.upper()}_FRAME_SIZE {adaconv.frame_size}
+#define {name.upper()}_LEFT_PADDING {left_padding}
+#define {name.upper()}_OVERLAP_SIZE {adaconv.overlap_size}
+#define {name.upper()}_IN_CHANNELS {adaconv.in_channels}
+#define {name.upper()}_OUT_CHANNELS {adaconv.out_channels}
+#define {name.upper()}_NORM_P {adaconv.norm_p}
+#define {name.upper()}_FEATURE_DIM {adaconv.feature_dim}
+"""
+ )
+
+ print_dense_layer(where, name + "_kernel", w_kernel, b_kernel, scale=scale, format='torch', sparse=False, diagonal=False, quantize=quantize)
+ print_dense_layer(where, name + "_gain", w_gain, b_gain, format='torch', sparse=False, diagonal=False, quantize=False)
+
+
+ else:
+ np.save(where, 'weight_kernel.npy', w_kernel)
+ np.save(where, 'bias_kernel.npy', b_kernel)
+ np.save(where, 'weight_gain.npy', w_gain)
+ np.save(where, 'bias_gain.npy', b_gain)
+
+
+def dump_torch_adaptive_comb1d_weights(where, adaconv, name='adaconv', scale=1/128, quantize=False):
+
+
+ w_kernel = adaconv.conv_kernel.weight.detach().cpu().numpy().copy()
+ b_kernel = adaconv.conv_kernel.bias.detach().cpu().numpy().copy()
+ w_gain = adaconv.filter_gain.weight.detach().cpu().numpy().copy()
+ b_gain = adaconv.filter_gain.bias.detach().cpu().numpy().copy()
+ w_global_gain = adaconv.global_filter_gain.weight.detach().cpu().numpy().copy()
+ b_global_gain = adaconv.global_filter_gain.bias.detach().cpu().numpy().copy()
+
+
+ if isinstance(where, CWriter):
+ # pad kernel for quantization
+ left_padding = adaconv.padding[0]
+ kernel_size = adaconv.kernel_size
+
+ if quantize and w_kernel.shape[0] % 8:
+ kernel_padding = 8 - (w_kernel.shape[0] % 8)
+ w_kernel = np.concatenate((np.zeros((kernel_padding, w_kernel.shape[1])), w_kernel), dtype=w_kernel.dtype)
+ b_kernel = np.concatenate((np.zeros((kernel_padding)), b_kernel), dtype=b_kernel.dtype)
+ left_padding += kernel_padding
+ kernel_size += kernel_padding
+ # write relevant scalar parameters to header file
+ where.header.write(f"""
+#define {name.upper()}_FILTER_GAIN_A {adaconv.filter_gain_a:f}f
+#define {name.upper()}_FILTER_GAIN_B {adaconv.filter_gain_b:f}f
+#define {name.upper()}_LOG_GAIN_LIMIT {adaconv.log_gain_limit:f}f
+#define {name.upper()}_KERNEL_SIZE {kernel_size}
+#define {name.upper()}_LEFT_PADDING {left_padding}
+#define {name.upper()}_FRAME_SIZE {adaconv.frame_size}
+#define {name.upper()}_OVERLAP_SIZE {adaconv.overlap_size}
+#define {name.upper()}_IN_CHANNELS {adaconv.in_channels}
+#define {name.upper()}_OUT_CHANNELS {adaconv.out_channels}
+#define {name.upper()}_NORM_P {adaconv.norm_p}
+#define {name.upper()}_FEATURE_DIM {adaconv.feature_dim}
+#define {name.upper()}_MAX_LAG {adaconv.max_lag}
+"""
+ )
+
+ print_dense_layer(where, name + "_kernel", w_kernel, b_kernel, scale=scale, format='torch', sparse=False, diagonal=False, quantize=quantize)
+ print_dense_layer(where, name + "_gain", w_gain, b_gain, format='torch', sparse=False, diagonal=False, quantize=False)
+ print_dense_layer(where, name + "_global_gain", w_global_gain, b_global_gain, format='torch', sparse=False, diagonal=False, quantize=False)
+
+
+ else:
+ np.save(where, 'weight_kernel.npy', w_kernel)
+ np.save(where, 'bias_kernel.npy', b_kernel)
+ np.save(where, 'weight_gain.npy', w_gain)
+ np.save(where, 'bias_gain.npy', b_gain)
+ np.save(where, 'weight_global_gain.npy', w_global_gain)
+ np.save(where, 'bias_global_gain.npy', b_global_gain)
+
+def dump_torch_tdshaper(where, shaper, name='tdshaper', quantize=False, scale=1/128):
+
+ if isinstance(where, CWriter):
+ where.header.write(f"""
+#define {name.upper()}_FEATURE_DIM {shaper.feature_dim}
+#define {name.upper()}_FRAME_SIZE {shaper.frame_size}
+#define {name.upper()}_AVG_POOL_K {shaper.avg_pool_k}
+#define {name.upper()}_INNOVATE {1 if shaper.innovate else 0}
+#define {name.upper()}_POOL_AFTER {1 if shaper.pool_after else 0}
+"""
+ )
+
+ dump_torch_conv1d_weights(where, shaper.feature_alpha1_f, name + "_alpha1_f", quantize=quantize, scale=scale)
+ dump_torch_conv1d_weights(where, shaper.feature_alpha1_t, name + "_alpha1_t")
+ dump_torch_conv1d_weights(where, shaper.feature_alpha2, name + "_alpha2")
+
+ if shaper.innovate:
+ dump_torch_conv1d_weights(where, shaper.feature_alpha1b, name + "_alpha1b")
+ dump_torch_conv1d_weights(where, shaper.feature_alpha1c, name + "_alpha1c")
+ dump_torch_conv1d_weights(where, shaper.feature_alpha2b, name + "_alpha2b")
+ dump_torch_conv1d_weights(where, shaper.feature_alpha2c, name + "_alpha2c")
+
+
+
+def dump_torch_gru_weights(where, gru, name='gru', input_sparse=False, recurrent_sparse=False, quantize=False, scale=1/128, recurrent_scale=1/128):
+
+ assert gru.num_layers == 1
+ assert gru.bidirectional == False
+
+ w_ih = gru.weight_ih_l0.detach().cpu().numpy().copy()
+ w_hh = gru.weight_hh_l0.detach().cpu().numpy().copy()
+ if hasattr(gru, 'bias_ih_l0'):
+ b_ih = gru.bias_ih_l0.detach().cpu().numpy().copy()
+ else:
+ b_ih = None
+ if hasattr(gru, 'bias_hh_l0'):
+ b_hh = gru.bias_hh_l0.detach().cpu().numpy().copy()
+ else:
+ b_hh = None
+
+ if isinstance(where, CWriter):
+ return print_gru_layer(where, name, w_ih, w_hh, b_ih, b_hh, format='torch', input_sparse=input_sparse, recurrent_sparse=recurrent_sparse, quantize=quantize, scale=scale, recurrent_scale=recurrent_scale)
+ else:
+ os.makedirs(where, exist_ok=True)
+
+ np.save(os.path.join(where, 'weight_ih_rzn.npy'), w_ih)
+ np.save(os.path.join(where, 'weight_hh_rzn.npy'), w_hh)
+ np.save(os.path.join(where, 'bias_ih_rzn.npy'), b_ih)
+ np.save(os.path.join(where, 'bias_hh_rzn.npy'), b_hh)
+
+
+def dump_torch_grucell_weights(where, gru, name='gru', input_sparse=False, recurrent_sparse=False, quantize=False, scale=1/128, recurrent_scale=1/128):
+
+ w_ih = gru.weight_ih.detach().cpu().numpy().copy()
+ w_hh = gru.weight_hh.detach().cpu().numpy().copy()
+ if hasattr(gru, 'bias_ih') and gru.bias_ih is not None:
+ b_ih = gru.bias_ih.detach().cpu().numpy().copy()
+ else:
+ b_ih = None
+ if hasattr(gru, 'bias_hh') and gru.bias_hh is not None:
+ b_hh = gru.bias_hh.detach().cpu().numpy().copy()
+ else:
+ b_hh = None
+
+ if isinstance(where, CWriter):
+ return print_gru_layer(where, name, w_ih, w_hh, b_ih, b_hh, format='torch', input_sparse=input_sparse, recurrent_sparse=recurrent_sparse, quantize=quantize, scale=scale, recurrent_scale=recurrent_scale)
+ else:
+ os.makedirs(where, exist_ok=True)
+
+ np.save(os.path.join(where, 'weight_ih_rzn.npy'), w_ih)
+ np.save(os.path.join(where, 'weight_hh_rzn.npy'), w_hh)
+ np.save(os.path.join(where, 'bias_ih_rzn.npy'), b_ih)
+ np.save(os.path.join(where, 'bias_hh_rzn.npy'), b_hh)
+
+
+
+def load_torch_gru_weights(where, gru):
+
+ assert gru.num_layers == 1
+ assert gru.bidirectional == False
+
+ w_ih = np.load(os.path.join(where, 'weight_ih_rzn.npy'))
+ w_hh = np.load(os.path.join(where, 'weight_hh_rzn.npy'))
+ b_ih = np.load(os.path.join(where, 'bias_ih_rzn.npy'))
+ b_hh = np.load(os.path.join(where, 'bias_hh_rzn.npy'))
+
+ with torch.no_grad():
+ gru.weight_ih_l0.set_(torch.from_numpy(w_ih))
+ gru.weight_hh_l0.set_(torch.from_numpy(w_hh))
+ gru.bias_ih_l0.set_(torch.from_numpy(b_ih))
+ gru.bias_hh_l0.set_(torch.from_numpy(b_hh))
+
+
+def dump_torch_dense_weights(where, dense, name='dense', scale=1/128, sparse=False, diagonal=False, quantize=False):
+
+ w = dense.weight.detach().cpu().numpy().copy()
+ if dense.bias is None:
+ b = np.zeros(dense.out_features, dtype=w.dtype)
+ else:
+ b = dense.bias.detach().cpu().numpy().copy()
+
+ if isinstance(where, CWriter):
+ return print_dense_layer(where, name, w, b, scale=scale, format='torch', sparse=sparse, diagonal=diagonal, quantize=quantize)
+
+ else:
+ os.makedirs(where, exist_ok=True)
+
+ np.save(os.path.join(where, 'weight.npy'), w)
+ np.save(os.path.join(where, 'bias.npy'), b)
+
+
+def load_torch_dense_weights(where, dense):
+
+ w = np.load(os.path.join(where, 'weight.npy'))
+ b = np.load(os.path.join(where, 'bias.npy'))
+
+ with torch.no_grad():
+ dense.weight.set_(torch.from_numpy(w))
+ if dense.bias is not None:
+ dense.bias.set_(torch.from_numpy(b))
+
+
+def dump_torch_conv1d_weights(where, conv, name='conv', scale=1/128, quantize=False, sparse=False):
+
+ w = conv.weight.detach().cpu().numpy().copy()
+ if conv.bias is None:
+ b = np.zeros(conv.out_channels, dtype=w.dtype)
+ else:
+ b = conv.bias.detach().cpu().numpy().copy()
+
+ if isinstance(where, CWriter):
+
+ return print_conv1d_layer(where, name, w, b, scale=scale, format='torch', quantize=quantize, sparse=sparse)
+ else:
+ os.makedirs(where, exist_ok=True)
+
+ np.save(os.path.join(where, 'weight_oik.npy'), w)
+
+ np.save(os.path.join(where, 'bias.npy'), b)
+
+
+def load_torch_conv1d_weights(where, conv):
+
+ with torch.no_grad():
+ w = np.load(os.path.join(where, 'weight_oik.npy'))
+ conv.weight.set_(torch.from_numpy(w))
+ if type(conv.bias) != type(None):
+ b = np.load(os.path.join(where, 'bias.npy'))
+ if conv.bias is not None:
+ conv.bias.set_(torch.from_numpy(b))
+
+
+def dump_torch_tconv1d_weights(where, conv, name='conv', scale=1/128, quantize=False, sparse=False):
+
+ w = conv.weight.detach().cpu().numpy().copy()
+ if conv.bias is None:
+ b = np.zeros(conv.out_channels, dtype=w.dtype)
+ else:
+ b = conv.bias.detach().cpu().numpy().copy()
+
+ if isinstance(where, CWriter):
+
+ return print_tconv1d_layer(where, name, w, b, conv.stride[0], scale=scale, quantize=quantize, sparse=sparse)
+ else:
+ os.makedirs(where, exist_ok=True)
+
+ np.save(os.path.join(where, 'weight_oik.npy'), w)
+
+ np.save(os.path.join(where, 'bias.npy'), b)
+
+
+def load_torch_tconv1d_weights(where, conv):
+
+ with torch.no_grad():
+ w = np.load(os.path.join(where, 'weight_oik.npy'))
+ conv.weight.set_(torch.from_numpy(w))
+ if type(conv.bias) != type(None):
+ b = np.load(os.path.join(where, 'bias.npy'))
+ if conv.bias is not None:
+ conv.bias.set_(torch.from_numpy(b))
+
+
+def dump_torch_conv2d_weights(where, conv, name='conv', scale=1/128, quantize=False):
+ w = conv.weight.detach().cpu().permute(0, 1, 3, 2).numpy().copy()
+ if conv.bias is None:
+ b = np.zeros(conv.out_channels, dtype=w.dtype)
+ else:
+ b = conv.bias.detach().cpu().numpy().copy()
+
+ if isinstance(where, CWriter):
+ return print_conv2d_layer(where, name, w, b, scale=scale, quantize=quantize)
+
+ else:
+ os.makedirs(where, exist_ok=True)
+
+ np.save(os.path.join(where, 'weight_oiwh.npy'), w)
+
+ np.save(os.path.join(where, 'bias.npy'), b)
+
+def load_torch_conv2d_weights(where, conv):
+ with torch.no_grad():
+ w = np.load(os.path.join(where, 'weight_oiwh.npy'))
+ conv.weight.set_(torch.from_numpy(w).permute(0, 1, 3, 2))
+ if type(conv.bias) != type(None):
+ b = np.load(os.path.join(where, 'bias.npy'))
+ if conv.bias is not None:
+ conv.bias.set_(torch.from_numpy(b))
+
+
+def dump_torch_embedding_weights(where, embed, name='embed', scale=1/128, sparse=False, diagonal=False, quantize=False):
+
+ w = embed.weight.detach().cpu().numpy().copy().transpose()
+ b = np.zeros(w.shape[0], dtype=w.dtype)
+
+ if isinstance(where, CWriter):
+ return print_dense_layer(where, name, w, b, scale=scale, format='torch', sparse=sparse, diagonal=diagonal, quantize=quantize)
+
+ else:
+ os.makedirs(where, exist_ok=True)
+
+ np.save(os.path.join(where, 'weight.npy'), w)
+ np.save(os.path.join(where, 'bias.npy'), b)
+
+
+def load_torch_embedding_weights(where, emb):
+
+ w = np.load(os.path.join(where, 'weight.npy'))
+
+ with torch.no_grad():
+ emb.weight.set_(torch.from_numpy(w))
+
+def dump_torch_weights(where, module, name=None, verbose=False, **kwargs):
+ """ generic function for dumping weights of some torch.nn.Module """
+ if verbose and name is not None:
+ print(f"printing layer {name} of type {type(module)}...")
+ if isinstance(module, torch.nn.Linear):
+ return dump_torch_dense_weights(where, module, name, **kwargs)
+ elif isinstance(module, torch.nn.GRU):
+ return dump_torch_gru_weights(where, module, name, **kwargs)
+ elif isinstance(module, torch.nn.GRUCell):
+ return dump_torch_grucell_weights(where, module, name, **kwargs)
+ elif isinstance(module, torch.nn.Conv1d):
+ return dump_torch_conv1d_weights(where, module, name, **kwargs)
+ elif isinstance(module, torch.nn.Conv2d):
+ return dump_torch_conv2d_weights(where, module, name, **kwargs)
+ elif isinstance(module, torch.nn.Embedding):
+ return dump_torch_embedding_weights(where, module, name, **kwargs)
+ elif isinstance(module, torch.nn.ConvTranspose1d):
+ return dump_torch_tconv1d_weights(where, module, name, **kwargs)
+ else:
+ if has_osce:
+ if isinstance(module, LimitedAdaptiveConv1d):
+ dump_torch_adaptive_conv1d_weights(where, module, name, **kwargs)
+ elif isinstance(module, LimitedAdaptiveComb1d):
+ dump_torch_adaptive_comb1d_weights(where, module, name, **kwargs)
+ elif isinstance(module, TDShaper):
+ dump_torch_tdshaper(where, module, name, **kwargs)
+ else:
+ raise ValueError(f'dump_torch_weights: layer of type {type(module)} not supported')
+ else:
+ raise ValueError(f'dump_torch_weights: layer of type {type(module)} not supported')
+
+def load_torch_weights(where, module):
+ """ generic function for loading weights of some torch.nn.Module """
+ if isinstance(module, torch.nn.Linear):
+ load_torch_dense_weights(where, module)
+ elif isinstance(module, torch.nn.GRU):
+ load_torch_gru_weights(where, module)
+ elif isinstance(module, torch.nn.Conv1d):
+ load_torch_conv1d_weights(where, module)
+ elif isinstance(module, torch.nn.Conv2d):
+ load_torch_conv2d_weights(where, module)
+ elif isinstance(module, torch.nn.Embedding):
+ load_torch_embedding_weights(where, module)
+ elif isinstance(module, torch.nn.ConvTranspose1d):
+ return load_torch_tconv1d_weights(where, module)
+ else:
+ raise ValueError(f'load_torch_weights: layer of type {type(module)} not supported')
diff --git a/dnn/training_tf2/dataloader.py b/dnn/training_tf2/dataloader.py
new file mode 100644
index 00000000..ed441c1e
--- /dev/null
+++ b/dnn/training_tf2/dataloader.py
@@ -0,0 +1,49 @@
+import numpy as np
+from tensorflow.keras.utils import Sequence
+from ulaw import lin2ulaw
+
+def lpc2rc(lpc):
+ #print("shape is = ", lpc.shape)
+ order = lpc.shape[-1]
+ rc = 0*lpc
+ for i in range(order, 0, -1):
+ rc[:,:,i-1] = lpc[:,:,-1]
+ ki = rc[:,:,i-1:i].repeat(i-1, axis=2)
+ lpc = (lpc[:,:,:-1] - ki*lpc[:,:,-2::-1])/(1-ki*ki)
+ return rc
+
+class LPCNetLoader(Sequence):
+ def __init__(self, data, features, periods, batch_size, e2e=False, lookahead=2):
+ self.batch_size = batch_size
+ self.nb_batches = np.minimum(np.minimum(data.shape[0], features.shape[0]), periods.shape[0])//self.batch_size
+ self.data = data[:self.nb_batches*self.batch_size, :]
+ self.features = features[:self.nb_batches*self.batch_size, :]
+ self.periods = periods[:self.nb_batches*self.batch_size, :]
+ self.e2e = e2e
+ self.lookahead = lookahead
+ self.on_epoch_end()
+
+ def on_epoch_end(self):
+ self.indices = np.arange(self.nb_batches*self.batch_size)
+ np.random.shuffle(self.indices)
+
+ def __getitem__(self, index):
+ data = self.data[self.indices[index*self.batch_size:(index+1)*self.batch_size], :, :]
+ in_data = data[: , :, :1]
+ out_data = data[: , :, 1:]
+ features = self.features[self.indices[index*self.batch_size:(index+1)*self.batch_size], :, :-16]
+ periods = self.periods[self.indices[index*self.batch_size:(index+1)*self.batch_size], :, :]
+ outputs = [out_data]
+ inputs = [in_data, features, periods]
+ if self.lookahead > 0:
+ lpc = self.features[self.indices[index*self.batch_size:(index+1)*self.batch_size], 4-self.lookahead:-self.lookahead, -16:]
+ else:
+ lpc = self.features[self.indices[index*self.batch_size:(index+1)*self.batch_size], 4:, -16:]
+ if self.e2e:
+ outputs.append(lpc2rc(lpc))
+ else:
+ inputs.append(lpc)
+ return (inputs, outputs)
+
+ def __len__(self):
+ return self.nb_batches
diff --git a/dnn/training_tf2/decode_rdovae.py b/dnn/training_tf2/decode_rdovae.py
new file mode 100644
index 00000000..f9bf9bf6
--- /dev/null
+++ b/dnn/training_tf2/decode_rdovae.py
@@ -0,0 +1,111 @@
+#!/usr/bin/python3
+'''Copyright (c) 2021-2022 Amazon
+ Copyright (c) 2018-2019 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+# Train an LPCNet model
+
+import argparse
+#from plc_loader import PLCLoader
+
+parser = argparse.ArgumentParser(description='Train a PLC model')
+
+parser.add_argument('bits', metavar='<bits file>', help='binary features file (int16)')
+parser.add_argument('output', metavar='<output>', help='output features')
+parser.add_argument('--model', metavar='<model>', default='rdovae', help='PLC model python definition (without .py)')
+group1 = parser.add_mutually_exclusive_group()
+group1.add_argument('--weights', metavar='<input weights>', help='model weights')
+parser.add_argument('--cond-size', metavar='<units>', default=1024, type=int, help='number of units in conditioning network (default 1024)')
+parser.add_argument('--batch-size', metavar='<batch size>', default=1, type=int, help='batch size to use (default 128)')
+parser.add_argument('--seq-length', metavar='<sequence length>', default=1000, type=int, help='sequence length to use (default 1000)')
+
+
+args = parser.parse_args()
+
+import importlib
+rdovae = importlib.import_module(args.model)
+
+import sys
+import numpy as np
+from tensorflow.keras.optimizers import Adam
+from tensorflow.keras.callbacks import ModelCheckpoint, CSVLogger
+import tensorflow.keras.backend as K
+import h5py
+
+import tensorflow as tf
+from rdovae import pvq_quantize
+from rdovae import apply_dead_zone
+
+# Try reducing batch_size if you run out of memory on your GPU
+batch_size = args.batch_size
+
+model, encoder, decoder, qembedding = rdovae.new_rdovae_model(nb_used_features=20, nb_bits=80, batch_size=batch_size, cond_size=args.cond_size)
+model.load_weights(args.weights)
+
+lpc_order = 16
+nbits=80
+
+
+bits_file = args.bits
+sequence_size = args.seq_length
+
+# u for unquantised, load 16 bit PCM samples and convert to mu-law
+
+
+bits = np.memmap(bits_file + "-syms.f32", dtype='float32', mode='r')
+nb_sequences = len(bits)//(40*sequence_size)//batch_size*batch_size
+bits = bits[:nb_sequences*sequence_size*40]
+
+bits = np.reshape(bits, (nb_sequences, sequence_size//2, 20*4))
+print(bits.shape)
+
+lambda_val = 0.001 * np.ones((nb_sequences, sequence_size//2, 1))
+quant_id = np.round(3.8*np.log(lambda_val/.0002)).astype('int16')
+quant_id = quant_id[:,:,0]
+quant_embed = qembedding(quant_id)
+quant_scale = tf.math.softplus(quant_embed[:,:,:nbits])
+dead_zone = tf.math.softplus(quant_embed[:, :, nbits : 2 * nbits])
+
+bits = bits*quant_scale
+bits = np.round(apply_dead_zone([bits, dead_zone]).numpy())
+bits = bits/quant_scale
+
+
+state = np.memmap(bits_file + "-state.f32", dtype='float32', mode='r')
+
+state = np.reshape(state, (nb_sequences, sequence_size//2, 24))
+state = state[:,-1,:]
+state = pvq_quantize(state, 82)
+#state = state/(1e-15+tf.norm(state, axis=-1,keepdims=True))
+
+print("shapes are:")
+print(bits.shape)
+print(state.shape)
+
+bits = bits[:,1::2,:]
+features = decoder.predict([bits, state], batch_size=batch_size)
+
+features.astype('float32').tofile(args.output)
diff --git a/dnn/training_tf2/diffembed.py b/dnn/training_tf2/diffembed.py
new file mode 100644
index 00000000..e04ae154
--- /dev/null
+++ b/dnn/training_tf2/diffembed.py
@@ -0,0 +1,49 @@
+"""
+Modification of Tensorflow's Embedding Layer:
+ 1. Not restricted to be the first layer of a model
+ 2. Differentiable (allows non-integer lookups)
+ - For non integer lookup, this layer linearly interpolates between the adjacent embeddings in the following way to preserver gradient flow
+ - E = (1 - frac(x))*embed(floor(x)) + frac(x)*embed(ceil(x))
+"""
+
+import tensorflow as tf
+from tensorflow.keras.layers import Layer
+
+class diff_Embed(Layer):
+ """
+ Parameters:
+ - units: int
+ Dimension of the Embedding
+ - dict_size: int
+ Number of Embeddings to lookup
+ - pcm_init: boolean
+ Initialized for the embedding matrix
+ """
+ def __init__(self, units=128, dict_size = 256, pcm_init = True, initializer = None, **kwargs):
+ super(diff_Embed, self).__init__(**kwargs)
+ self.units = units
+ self.dict_size = dict_size
+ self.pcm_init = pcm_init
+ self.initializer = initializer
+
+ def build(self, input_shape):
+ w_init = tf.random_normal_initializer()
+ if self.pcm_init:
+ w_init = self.initializer
+ self.w = tf.Variable(initial_value=w_init(shape=(self.dict_size, self.units),dtype='float32'),trainable=True)
+
+ def call(self, inputs):
+ alpha = inputs - tf.math.floor(inputs)
+ alpha = tf.expand_dims(alpha,axis = -1)
+ alpha = tf.tile(alpha,[1,1,1,self.units])
+ inputs = tf.cast(inputs,'int32')
+ M = (1 - alpha)*tf.gather(self.w,inputs) + alpha*tf.gather(self.w,tf.clip_by_value(inputs + 1, 0, 255))
+ return M
+
+ def get_config(self):
+ config = super(diff_Embed, self).get_config()
+ config.update({"units": self.units})
+ config.update({"dict_size" : self.dict_size})
+ config.update({"pcm_init" : self.pcm_init})
+ config.update({"initializer" : self.initializer})
+ return config \ No newline at end of file
diff --git a/dnn/training_tf2/dump_lpcnet.py b/dnn/training_tf2/dump_lpcnet.py
new file mode 100755
index 00000000..97ce0ced
--- /dev/null
+++ b/dnn/training_tf2/dump_lpcnet.py
@@ -0,0 +1,388 @@
+#!/usr/bin/python3
+'''Copyright (c) 2017-2018 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+import os
+import io
+import lpcnet
+import sys
+import numpy as np
+from tensorflow.keras.optimizers import Adam
+from tensorflow.keras.layers import Layer, GRU, Dense, Conv1D, Embedding
+from ulaw import ulaw2lin, lin2ulaw
+from mdense import MDense
+from diffembed import diff_Embed
+from parameters import get_parameter
+import h5py
+import re
+import argparse
+
+
+# no cuda devices needed
+os.environ['CUDA_VISIBLE_DEVICES'] = ""
+
+# Flag for dumping e2e (differentiable lpc) network weights
+flag_e2e = False
+
+
+max_rnn_neurons = 1
+max_conv_inputs = 1
+max_mdense_tmp = 1
+
+def printVector(f, vector, name, dtype='float', dotp=False):
+ global array_list
+ if dotp:
+ vector = vector.reshape((vector.shape[0]//4, 4, vector.shape[1]//8, 8))
+ vector = vector.transpose((2, 0, 3, 1))
+ v = np.reshape(vector, (-1));
+ #print('static const float ', name, '[', len(v), '] = \n', file=f)
+ if name not in array_list:
+ array_list.append(name)
+ f.write('#ifndef USE_WEIGHTS_FILE\n')
+ f.write('#define WEIGHTS_{}_DEFINED\n'.format(name))
+ f.write('#define WEIGHTS_{}_TYPE WEIGHT_TYPE_{}\n'.format(name, dtype))
+ f.write('static const {} {}[{}] = {{\n '.format(dtype, name, len(v)))
+ for i in range(0, len(v)):
+ f.write('{}'.format(v[i]))
+ if (i!=len(v)-1):
+ f.write(',')
+ else:
+ break;
+ if (i%8==7):
+ f.write("\n ")
+ else:
+ f.write(" ")
+ #print(v, file=f)
+ f.write('\n};\n')
+ f.write('#endif\n\n')
+ return;
+
+def printSparseVector(f, A, name, have_diag=True):
+ N = A.shape[0]
+ M = A.shape[1]
+ W = np.zeros((0,), dtype='int')
+ W0 = np.zeros((0,))
+ if have_diag:
+ diag = np.concatenate([np.diag(A[:,:N]), np.diag(A[:,N:2*N]), np.diag(A[:,2*N:])])
+ A[:,:N] = A[:,:N] - np.diag(np.diag(A[:,:N]))
+ A[:,N:2*N] = A[:,N:2*N] - np.diag(np.diag(A[:,N:2*N]))
+ A[:,2*N:] = A[:,2*N:] - np.diag(np.diag(A[:,2*N:]))
+ printVector(f, diag, name + '_diag')
+ AQ = np.minimum(127, np.maximum(-128, np.round(A*128))).astype('int')
+ idx = np.zeros((0,), dtype='int')
+ for i in range(M//8):
+ pos = idx.shape[0]
+ idx = np.append(idx, -1)
+ nb_nonzero = 0
+ for j in range(N//4):
+ block = A[j*4:(j+1)*4, i*8:(i+1)*8]
+ qblock = AQ[j*4:(j+1)*4, i*8:(i+1)*8]
+ if np.sum(np.abs(block)) > 1e-10:
+ nb_nonzero = nb_nonzero + 1
+ idx = np.append(idx, j*4)
+ vblock = qblock.transpose((1,0)).reshape((-1,))
+ W0 = np.concatenate([W0, block.reshape((-1,))])
+ W = np.concatenate([W, vblock])
+ idx[pos] = nb_nonzero
+ f.write('#ifdef DOT_PROD\n')
+ printVector(f, W, name, dtype='qweight')
+ f.write('#else /*DOT_PROD*/\n')
+ printVector(f, W0, name, dtype='qweight')
+ f.write('#endif /*DOT_PROD*/\n')
+ #idx = np.tile(np.concatenate([np.array([N]), np.arange(N)]), 3*N//16)
+ printVector(f, idx, name + '_idx', dtype='int')
+ return AQ
+
+def dump_layer_ignore(self, f, hf):
+ print("ignoring layer " + self.name + " of type " + self.__class__.__name__)
+ return False
+Layer.dump_layer = dump_layer_ignore
+
+def dump_sparse_gru(self, f, hf):
+ global max_rnn_neurons
+ name = 'sparse_' + self.name
+ print("printing layer " + name + " of type sparse " + self.__class__.__name__)
+ weights = self.get_weights()
+ qweights = printSparseVector(f, weights[1], name + '_recurrent_weights')
+ printVector(f, weights[-1], name + '_bias')
+ subias = weights[-1].copy()
+ subias[1,:] = subias[1,:] - np.sum(qweights*(1./128),axis=0)
+ printVector(f, subias, name + '_subias')
+ if hasattr(self, 'activation'):
+ activation = self.activation.__name__.upper()
+ else:
+ activation = 'TANH'
+ if hasattr(self, 'reset_after') and not self.reset_after:
+ reset_after = 0
+ else:
+ reset_after = 1
+ neurons = weights[0].shape[1]//3
+ max_rnn_neurons = max(max_rnn_neurons, neurons)
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ hf.write('#define {}_STATE_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ model_struct.write(' SparseGRULayer {};\n'.format(name));
+ model_init.write(' if (sparse_gru_init(&model->{}, arrays, "{}_bias", "{}_subias", "{}_recurrent_weights_diag", "{}_recurrent_weights", "{}_recurrent_weights_idx", {}, ACTIVATION_{}, {})) return 1;\n'
+ .format(name, name, name, name, name, name, weights[0].shape[1]//3, activation, reset_after))
+ return True
+
+def dump_grub(self, f, hf, gru_a_size):
+ global max_rnn_neurons
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()
+ qweight = printSparseVector(f, weights[0][:gru_a_size, :], name + '_weights', have_diag=False)
+
+ f.write('#ifdef DOT_PROD\n')
+ qweight2 = np.clip(np.round(128.*weights[1]).astype('int'), -128, 127)
+ printVector(f, qweight2, name + '_recurrent_weights', dotp=True, dtype='qweight')
+ f.write('#else /*DOT_PROD*/\n')
+ printVector(f, weights[1], name + '_recurrent_weights')
+ f.write('#endif /*DOT_PROD*/\n')
+
+ printVector(f, weights[-1], name + '_bias')
+ subias = weights[-1].copy()
+ subias[0,:] = subias[0,:] - np.sum(qweight*(1./128.),axis=0)
+ subias[1,:] = subias[1,:] - np.sum(qweight2*(1./128.),axis=0)
+ printVector(f, subias, name + '_subias')
+ if hasattr(self, 'activation'):
+ activation = self.activation.__name__.upper()
+ else:
+ activation = 'TANH'
+ if hasattr(self, 'reset_after') and not self.reset_after:
+ reset_after = 0
+ else:
+ reset_after = 1
+ neurons = weights[0].shape[1]//3
+ max_rnn_neurons = max(max_rnn_neurons, neurons)
+ model_struct.write(' GRULayer {};\n'.format(name));
+ model_init.write(' if (gru_init(&model->{}, arrays, "{}_bias", "{}_subias", "{}_weights", "{}_weights_idx", "{}_recurrent_weights", {}, {}, ACTIVATION_{}, {})) return 1;\n'
+ .format(name, name, name, name, name, name, gru_a_size, weights[0].shape[1]//3, activation, reset_after))
+ return True
+
+def dump_gru_layer_dummy(self, f, hf):
+ name = self.name
+ weights = self.get_weights()
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ hf.write('#define {}_STATE_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ return True;
+
+GRU.dump_layer = dump_gru_layer_dummy
+
+def dump_dense_layer_impl(name, weights, bias, activation, f, hf):
+ printVector(f, weights, name + '_weights')
+ printVector(f, bias, name + '_bias')
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights.shape[1]))
+ model_struct.write(' DenseLayer {};\n'.format(name));
+ model_init.write(' if (dense_init(&model->{}, arrays, "{}_bias", "{}_weights", {}, {}, ACTIVATION_{})) return 1;\n'
+ .format(name, name, name, weights.shape[0], weights.shape[1], activation))
+
+def dump_dense_layer(self, f, hf):
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()
+ activation = self.activation.__name__.upper()
+ dump_dense_layer_impl(name, weights[0], weights[1], activation, f, hf)
+ return False
+
+Dense.dump_layer = dump_dense_layer
+
+def dump_mdense_layer(self, f, hf):
+ global max_mdense_tmp
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()
+ printVector(f, np.transpose(weights[0], (0, 2, 1)), name + '_weights')
+ printVector(f, np.transpose(weights[1], (1, 0)), name + '_bias')
+ printVector(f, np.transpose(weights[2], (1, 0)), name + '_factor')
+ activation = self.activation.__name__.upper()
+ max_mdense_tmp = max(max_mdense_tmp, weights[0].shape[0]*weights[0].shape[2])
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[0]))
+ model_struct.write(' MDenseLayer {};\n'.format(name));
+ model_init.write(' if (mdense_init(&model->{}, arrays, "{}_bias", "{}_weights", "{}_factor", {}, {}, {}, ACTIVATION_{})) return 1;\n'
+ .format(name, name, name, name, weights[0].shape[1], weights[0].shape[0], weights[0].shape[2], activation))
+ return False
+MDense.dump_layer = dump_mdense_layer
+
+def dump_conv1d_layer(self, f, hf):
+ global max_conv_inputs
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()
+ printVector(f, weights[0], name + '_weights')
+ printVector(f, weights[-1], name + '_bias')
+ activation = self.activation.__name__.upper()
+ max_conv_inputs = max(max_conv_inputs, weights[0].shape[1]*weights[0].shape[0])
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[2]))
+ hf.write('#define {}_STATE_SIZE ({}*{})\n'.format(name.upper(), weights[0].shape[1], (weights[0].shape[0]-1)))
+ hf.write('#define {}_DELAY {}\n'.format(name.upper(), (weights[0].shape[0]-1)//2))
+ model_struct.write(' Conv1DLayer {};\n'.format(name));
+ model_init.write(' if (conv1d_init(&model->{}, arrays, "{}_bias", "{}_weights", {}, {}, {}, ACTIVATION_{})) return 1;\n'
+ .format(name, name, name, weights[0].shape[1], weights[0].shape[0], weights[0].shape[2], activation))
+ return True
+Conv1D.dump_layer = dump_conv1d_layer
+
+
+def dump_embedding_layer_impl(name, weights, f, hf):
+ printVector(f, weights, name + '_weights')
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights.shape[1]))
+ model_struct.write(' EmbeddingLayer {};\n'.format(name));
+ model_init.write(' if (embedding_init(&model->{}, arrays, "{}_weights", {}, {})) return 1;\n'
+ .format(name, name, weights.shape[0], weights.shape[1]))
+
+def dump_embedding_layer(self, f, hf):
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()[0]
+ dump_embedding_layer_impl(name, weights, f, hf)
+ return False
+Embedding.dump_layer = dump_embedding_layer
+diff_Embed.dump_layer = dump_embedding_layer
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument('model_file', type=str, help='model weight h5 file')
+ parser.add_argument('--nnet-header', type=str, help='name of c header file for dumped model', default='nnet_data.h')
+ parser.add_argument('--nnet-source', type=str, help='name of c source file for dumped model', default='nnet_data.c')
+ parser.add_argument('--lpc-gamma', type=float, help='LPC weighting factor. If not specified I will attempt to read it from the model file with 1 as default', default=None)
+ parser.add_argument('--lookahead', type=float, help='Features lookahead. If not specified I will attempt to read it from the model file with 2 as default', default=None)
+
+ args = parser.parse_args()
+
+ filename = args.model_file
+ with h5py.File(filename, "r") as f:
+ units = min(f['model_weights']['gru_a']['gru_a']['recurrent_kernel:0'].shape)
+ units2 = min(f['model_weights']['gru_b']['gru_b']['recurrent_kernel:0'].shape)
+ cond_size = min(f['model_weights']['feature_dense1']['feature_dense1']['kernel:0'].shape)
+ e2e = 'rc2lpc' in f['model_weights']
+
+ model, _, _ = lpcnet.new_lpcnet_model(rnn_units1=units, rnn_units2=units2, flag_e2e = e2e, cond_size=cond_size)
+ model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['sparse_categorical_accuracy'])
+ #model.summary()
+
+ model.load_weights(filename, by_name=True)
+
+ cfile = args.nnet_source
+ hfile = args.nnet_header
+
+ f = open(cfile, 'w')
+ hf = open(hfile, 'w')
+ model_struct = io.StringIO()
+ model_init = io.StringIO()
+ model_struct.write('typedef struct {\n')
+ model_init.write('#ifndef DUMP_BINARY_WEIGHTS\n')
+ model_init.write('int init_lpcnet_model(LPCNetModel *model, const WeightArray *arrays) {\n')
+ array_list = []
+
+ f.write('/*This file is automatically generated from a Keras model*/\n')
+ f.write('/*based on model {}*/\n\n'.format(sys.argv[1]))
+ f.write('#ifdef HAVE_CONFIG_H\n#include "config.h"\n#endif\n\n#include "nnet.h"\n#include "{}"\n\n'.format(hfile))
+
+ hf.write('/*This file is automatically generated from a Keras model*/\n\n')
+ hf.write('#ifndef RNN_DATA_H\n#define RNN_DATA_H\n\n#include "nnet.h"\n\n')
+
+ if e2e:
+ hf.write('/* This is an end-to-end model */\n')
+ hf.write('#define END2END\n\n')
+ else:
+ hf.write('/* This is *not* an end-to-end model */\n')
+ hf.write('/* #define END2END */\n\n')
+
+ # LPC weighting factor
+ if type(args.lpc_gamma) == type(None):
+ lpc_gamma = get_parameter(model, 'lpc_gamma', 1)
+ else:
+ lpc_gamma = args.lpc_gamma
+
+ hf.write('/* LPC weighting factor */\n')
+ hf.write('#define LPC_GAMMA ' + str(lpc_gamma) +'f\n\n')
+
+ # look-ahead
+ if type(args.lookahead) == type(None):
+ lookahead = get_parameter(model, 'lookahead', 2)
+ else:
+ lookahead = args.lookahead
+
+ hf.write('/* Features look-ahead */\n')
+ hf.write('#define FEATURES_DELAY ' + str(lookahead) +'\n\n')
+
+ embed_size = lpcnet.embed_size
+
+ E = model.get_layer('embed_sig').get_weights()[0]
+ W = model.get_layer('gru_a').get_weights()[0][:embed_size,:]
+ dump_embedding_layer_impl('gru_a_embed_sig', np.dot(E, W), f, hf)
+ W = model.get_layer('gru_a').get_weights()[0][embed_size:2*embed_size,:]
+ dump_embedding_layer_impl('gru_a_embed_pred', np.dot(E, W), f, hf)
+ W = model.get_layer('gru_a').get_weights()[0][2*embed_size:3*embed_size,:]
+ dump_embedding_layer_impl('gru_a_embed_exc', np.dot(E, W), f, hf)
+ W = model.get_layer('gru_a').get_weights()[0][3*embed_size:,:]
+ #FIXME: dump only half the biases
+ b = model.get_layer('gru_a').get_weights()[2]
+ dump_dense_layer_impl('gru_a_dense_feature', W, b[:len(b)//2], 'LINEAR', f, hf)
+
+ W = model.get_layer('gru_b').get_weights()[0][model.rnn_units1:,:]
+ b = model.get_layer('gru_b').get_weights()[2]
+ # Set biases to zero because they'll be included in the GRU input part
+ # (we need regular and SU biases)
+ dump_dense_layer_impl('gru_b_dense_feature', W, 0*b[:len(b)//2], 'LINEAR', f, hf)
+ dump_grub(model.get_layer('gru_b'), f, hf, model.rnn_units1)
+
+ layer_list = []
+ for i, layer in enumerate(model.layers):
+ if layer.dump_layer(f, hf):
+ layer_list.append(layer.name)
+
+ dump_sparse_gru(model.get_layer('gru_a'), f, hf)
+
+ f.write('#ifndef USE_WEIGHTS_FILE\n')
+ f.write('const WeightArray lpcnet_arrays[] = {\n')
+ for name in array_list:
+ f.write('#ifdef WEIGHTS_{}_DEFINED\n'.format(name))
+ f.write(' {{"{}", WEIGHTS_{}_TYPE, sizeof({}), {}}},\n'.format(name, name, name, name))
+ f.write('#endif\n')
+ f.write(' {NULL, 0, 0, NULL}\n};\n')
+ f.write('#endif\n')
+
+ model_init.write(' return 0;\n}\n')
+ model_init.write('#endif\n')
+ f.write(model_init.getvalue())
+
+ hf.write('#define MAX_RNN_NEURONS {}\n\n'.format(max_rnn_neurons))
+ hf.write('#define MAX_CONV_INPUTS {}\n\n'.format(max_conv_inputs))
+ hf.write('#define MAX_MDENSE_TMP {}\n\n'.format(max_mdense_tmp))
+
+
+ hf.write('typedef struct {\n')
+ for i, name in enumerate(layer_list):
+ hf.write(' float {}_state[{}_STATE_SIZE];\n'.format(name, name.upper()))
+ hf.write('} NNetState;\n\n')
+
+ model_struct.write('} LPCNetModel;\n\n')
+ hf.write(model_struct.getvalue())
+ hf.write('int init_lpcnet_model(LPCNetModel *model, const WeightArray *arrays);\n\n')
+ hf.write('\n\n#endif\n')
+
+ f.close()
+ hf.close()
diff --git a/dnn/training_tf2/dump_plc.py b/dnn/training_tf2/dump_plc.py
new file mode 100755
index 00000000..a490ade1
--- /dev/null
+++ b/dnn/training_tf2/dump_plc.py
@@ -0,0 +1,296 @@
+#!/usr/bin/python3
+'''Copyright (c) 2021-2022 Amazon
+ Copyright (c) 2017-2018 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+import lpcnet_plc
+import io
+import sys
+import numpy as np
+from tensorflow.keras.optimizers import Adam
+from tensorflow.keras.layers import Layer, GRU, Dense, Conv1D, Embedding
+import h5py
+import re
+
+# Flag for dumping e2e (differentiable lpc) network weights
+flag_e2e = False
+
+max_rnn_neurons = 1
+max_conv_inputs = 1
+
+def printVector(f, vector, name, dtype='float', dotp=False):
+ global array_list
+ if dotp:
+ vector = vector.reshape((vector.shape[0]//4, 4, vector.shape[1]//8, 8))
+ vector = vector.transpose((2, 0, 3, 1))
+ v = np.reshape(vector, (-1));
+ #print('static const float ', name, '[', len(v), '] = \n', file=f)
+ if name not in array_list:
+ array_list.append(name)
+ f.write('#ifndef USE_WEIGHTS_FILE\n')
+ f.write('#define WEIGHTS_{}_DEFINED\n'.format(name))
+ f.write('#define WEIGHTS_{}_TYPE WEIGHT_TYPE_{}\n'.format(name, dtype))
+ f.write('static const {} {}[{}] = {{\n '.format(dtype, name, len(v)))
+ for i in range(0, len(v)):
+ f.write('{}'.format(v[i]))
+ if (i!=len(v)-1):
+ f.write(',')
+ else:
+ break;
+ if (i%8==7):
+ f.write("\n ")
+ else:
+ f.write(" ")
+ #print(v, file=f)
+ f.write('\n};\n')
+ f.write('#endif\n\n')
+ return;
+
+def printSparseVector(f, A, name, have_diag=True):
+ N = A.shape[0]
+ M = A.shape[1]
+ W = np.zeros((0,), dtype='int')
+ W0 = np.zeros((0,))
+ if have_diag:
+ diag = np.concatenate([np.diag(A[:,:N]), np.diag(A[:,N:2*N]), np.diag(A[:,2*N:])])
+ A[:,:N] = A[:,:N] - np.diag(np.diag(A[:,:N]))
+ A[:,N:2*N] = A[:,N:2*N] - np.diag(np.diag(A[:,N:2*N]))
+ A[:,2*N:] = A[:,2*N:] - np.diag(np.diag(A[:,2*N:]))
+ printVector(f, diag, name + '_diag')
+ AQ = np.minimum(127, np.maximum(-128, np.round(A*128))).astype('int')
+ idx = np.zeros((0,), dtype='int')
+ for i in range(M//8):
+ pos = idx.shape[0]
+ idx = np.append(idx, -1)
+ nb_nonzero = 0
+ for j in range(N//4):
+ block = A[j*4:(j+1)*4, i*8:(i+1)*8]
+ qblock = AQ[j*4:(j+1)*4, i*8:(i+1)*8]
+ if np.sum(np.abs(block)) > 1e-10:
+ nb_nonzero = nb_nonzero + 1
+ idx = np.append(idx, j*4)
+ vblock = qblock.transpose((1,0)).reshape((-1,))
+ W0 = np.concatenate([W0, block.reshape((-1,))])
+ W = np.concatenate([W, vblock])
+ idx[pos] = nb_nonzero
+ f.write('#ifdef DOT_PROD\n')
+ printVector(f, W, name, dtype='qweight')
+ f.write('#else /*DOT_PROD*/\n')
+ printVector(f, W0, name, dtype='qweight')
+ f.write('#endif /*DOT_PROD*/\n')
+ #idx = np.tile(np.concatenate([np.array([N]), np.arange(N)]), 3*N//16)
+ printVector(f, idx, name + '_idx', dtype='int')
+ return AQ
+
+def dump_layer_ignore(self, f, hf):
+ print("ignoring layer " + self.name + " of type " + self.__class__.__name__)
+ return False
+Layer.dump_layer = dump_layer_ignore
+
+def dump_sparse_gru(self, f, hf):
+ global max_rnn_neurons
+ name = 'sparse_' + self.name
+ print("printing layer " + name + " of type sparse " + self.__class__.__name__)
+ weights = self.get_weights()
+ qweights = printSparseVector(f, weights[1], name + '_recurrent_weights')
+ printVector(f, weights[-1], name + '_bias')
+ subias = weights[-1].copy()
+ subias[1,:] = subias[1,:] - np.sum(qweights*(1./128),axis=0)
+ printVector(f, subias, name + '_subias')
+ if hasattr(self, 'activation'):
+ activation = self.activation.__name__.upper()
+ else:
+ activation = 'TANH'
+ if hasattr(self, 'reset_after') and not self.reset_after:
+ reset_after = 0
+ else:
+ reset_after = 1
+ neurons = weights[0].shape[1]//3
+ max_rnn_neurons = max(max_rnn_neurons, neurons)
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ hf.write('#define {}_STATE_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ model_struct.write(' SparseGRULayer {};\n'.format(name));
+ model_init.write(' if (sparse_gru_init(&model->{}, arrays, "{}_bias", "{}_subias", "{}_recurrent_weights_diag", "{}_recurrent_weights", "{}_recurrent_weights_idx", {}, ACTIVATION_{}, {})) return 1;\n'
+ .format(name, name, name, name, name, name, weights[0].shape[1]//3, activation, reset_after))
+ return True
+
+def dump_gru_layer(self, f, hf):
+ global max_rnn_neurons
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()
+ qweight = printSparseVector(f, weights[0], name + '_weights', have_diag=False)
+
+ f.write('#ifdef DOT_PROD\n')
+ qweight2 = np.clip(np.round(128.*weights[1]).astype('int'), -128, 127)
+ printVector(f, qweight2, name + '_recurrent_weights', dotp=True, dtype='qweight')
+ f.write('#else /*DOT_PROD*/\n')
+ printVector(f, weights[1], name + '_recurrent_weights')
+ f.write('#endif /*DOT_PROD*/\n')
+
+ printVector(f, weights[-1], name + '_bias')
+ subias = weights[-1].copy()
+ subias[0,:] = subias[0,:] - np.sum(qweight*(1./128.),axis=0)
+ subias[1,:] = subias[1,:] - np.sum(qweight2*(1./128.),axis=0)
+ printVector(f, subias, name + '_subias')
+ if hasattr(self, 'activation'):
+ activation = self.activation.__name__.upper()
+ else:
+ activation = 'TANH'
+ if hasattr(self, 'reset_after') and not self.reset_after:
+ reset_after = 0
+ else:
+ reset_after = 1
+ neurons = weights[0].shape[1]//3
+ max_rnn_neurons = max(max_rnn_neurons, neurons)
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ hf.write('#define {}_STATE_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ model_struct.write(' GRULayer {};\n'.format(name));
+ model_init.write(' if (gru_init(&model->{}, arrays, "{}_bias", "{}_subias", "{}_weights", "{}_weights_idx", "{}_recurrent_weights", {}, {}, ACTIVATION_{}, {})) return 1;\n'
+ .format(name, name, name, name, name, name, weights[0].shape[0], weights[0].shape[1]//3, activation, reset_after))
+ return True
+GRU.dump_layer = dump_gru_layer
+
+def dump_gru_layer_dummy(self, f, hf):
+ name = self.name
+ weights = self.get_weights()
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ hf.write('#define {}_STATE_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ return True;
+
+#GRU.dump_layer = dump_gru_layer_dummy
+
+def dump_dense_layer_impl(name, weights, bias, activation, f, hf):
+ printVector(f, weights, name + '_weights')
+ printVector(f, bias, name + '_bias')
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights.shape[1]))
+ model_struct.write(' DenseLayer {};\n'.format(name));
+ model_init.write(' if (dense_init(&model->{}, arrays, "{}_bias", "{}_weights", {}, {}, ACTIVATION_{})) return 1;\n'
+ .format(name, name, name, weights.shape[0], weights.shape[1], activation))
+
+def dump_dense_layer(self, f, hf):
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()
+ activation = self.activation.__name__.upper()
+ dump_dense_layer_impl(name, weights[0], weights[1], activation, f, hf)
+ return False
+
+Dense.dump_layer = dump_dense_layer
+
+def dump_conv1d_layer(self, f, hf):
+ global max_conv_inputs
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()
+ printVector(f, weights[0], name + '_weights')
+ printVector(f, weights[-1], name + '_bias')
+ activation = self.activation.__name__.upper()
+ max_conv_inputs = max(max_conv_inputs, weights[0].shape[1]*weights[0].shape[0])
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[2]))
+ hf.write('#define {}_STATE_SIZE ({}*{})\n'.format(name.upper(), weights[0].shape[1], (weights[0].shape[0]-1)))
+ hf.write('#define {}_DELAY {}\n'.format(name.upper(), (weights[0].shape[0]-1)//2))
+ model_struct.write(' Conv1DLayer {};\n'.format(name));
+ model_init.write(' if (conv1d_init(&model->{}, arrays, "{}_bias", "{}_weights", {}, {}, {}, ACTIVATION_{})) return 1;\n'
+ .format(name, name, name, weights[0].shape[1], weights[0].shape[0], weights[0].shape[2], activation))
+ return True
+Conv1D.dump_layer = dump_conv1d_layer
+
+
+
+filename = sys.argv[1]
+with h5py.File(filename, "r") as f:
+ units = min(f['model_weights']['plc_gru1']['plc_gru1']['recurrent_kernel:0'].shape)
+ units2 = min(f['model_weights']['plc_gru2']['plc_gru2']['recurrent_kernel:0'].shape)
+ cond_size = f['model_weights']['plc_dense1']['plc_dense1']['kernel:0'].shape[1]
+
+model = lpcnet_plc.new_lpcnet_plc_model(rnn_units=units, cond_size=cond_size)
+model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['sparse_categorical_accuracy'])
+#model.summary()
+
+model.load_weights(filename, by_name=True)
+
+if len(sys.argv) > 2:
+ cfile = sys.argv[2];
+ hfile = sys.argv[3];
+else:
+ cfile = 'plc_data.c'
+ hfile = 'plc_data.h'
+
+
+f = open(cfile, 'w')
+hf = open(hfile, 'w')
+model_struct = io.StringIO()
+model_init = io.StringIO()
+model_struct.write('typedef struct {\n')
+model_init.write('#ifndef DUMP_BINARY_WEIGHTS\n')
+model_init.write('int init_plc_model(PLCModel *model, const WeightArray *arrays) {\n')
+array_list = []
+
+
+f.write('/*This file is automatically generated from a Keras model*/\n')
+f.write('/*based on model {}*/\n\n'.format(sys.argv[1]))
+f.write('#ifdef HAVE_CONFIG_H\n#include "config.h"\n#endif\n\n#include "nnet.h"\n#include "{}"\n\n'.format(hfile))
+
+hf.write('/*This file is automatically generated from a Keras model*/\n\n')
+hf.write('#ifndef PLC_DATA_H\n#define PLC_DATA_H\n\n#include "nnet.h"\n\n')
+
+layer_list = []
+for i, layer in enumerate(model.layers):
+ if layer.dump_layer(f, hf):
+ layer_list.append(layer.name)
+
+#dump_sparse_gru(model.get_layer('gru_a'), f, hf)
+f.write('#ifndef USE_WEIGHTS_FILE\n')
+f.write('const WeightArray lpcnet_plc_arrays[] = {\n')
+for name in array_list:
+ f.write('#ifdef WEIGHTS_{}_DEFINED\n'.format(name))
+ f.write(' {{"{}", WEIGHTS_{}_TYPE, sizeof({}), {}}},\n'.format(name, name, name, name))
+ f.write('#endif\n')
+f.write(' {NULL, 0, 0, NULL}\n};\n')
+f.write('#endif\n')
+
+model_init.write(' return 0;\n}\n')
+model_init.write('#endif\n')
+f.write(model_init.getvalue())
+
+
+hf.write('#define PLC_MAX_RNN_NEURONS {}\n\n'.format(max_rnn_neurons))
+#hf.write('#define PLC_MAX_CONV_INPUTS {}\n\n'.format(max_conv_inputs))
+
+hf.write('typedef struct {\n')
+for i, name in enumerate(layer_list):
+ hf.write(' float {}_state[{}_STATE_SIZE];\n'.format(name, name.upper()))
+hf.write('} PLCNetState;\n\n')
+
+model_struct.write('} PLCModel;\n\n')
+hf.write(model_struct.getvalue())
+hf.write('int init_plc_model(PLCModel *model, const WeightArray *arrays);\n\n')
+
+hf.write('\n\n#endif\n')
+
+f.close()
+hf.close()
diff --git a/dnn/training_tf2/dump_rdovae.py b/dnn/training_tf2/dump_rdovae.py
new file mode 100644
index 00000000..1858c8a4
--- /dev/null
+++ b/dnn/training_tf2/dump_rdovae.py
@@ -0,0 +1,306 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+
+import argparse
+from ftplib import parse150
+import os
+
+os.environ['CUDA_VISIBLE_DEVICES'] = ""
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('weights', metavar="<weight file>", type=str, help='model weight file in hdf5 format')
+parser.add_argument('--cond-size', type=int, help="conditioning size (default: 256)", default=256)
+parser.add_argument('--latent-dim', type=int, help="dimension of latent space (default: 80)", default=80)
+parser.add_argument('--quant-levels', type=int, help="number of quantization steps (default: 16)", default=16)
+
+args = parser.parse_args()
+
+# now import the heavy stuff
+import tensorflow as tf
+import numpy as np
+from keraslayerdump import dump_conv1d_layer, dump_dense_layer, dump_gru_layer, printVector
+from rdovae import new_rdovae_model
+
+def start_header(header_fid, header_name):
+ header_guard = os.path.basename(header_name)[:-2].upper() + "_H"
+ header_fid.write(
+f"""
+#ifndef {header_guard}
+#define {header_guard}
+
+"""
+ )
+
+def finish_header(header_fid):
+ header_fid.write(
+"""
+#endif
+
+"""
+ )
+
+def start_source(source_fid, header_name, weight_file):
+ source_fid.write(
+f"""
+/* this source file was automatically generated from weight file {weight_file} */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "{header_name}"
+
+"""
+ )
+
+def finish_source(source_fid):
+ pass
+
+
+def dump_statistical_model(qembedding, f, fh):
+ w = qembedding.weights[0].numpy()
+ levels, dim = w.shape
+ N = dim // 6
+
+ print("dumping statistical model")
+ quant_scales = tf.math.softplus(w[:, : N]).numpy()
+ dead_zone = 0.05 * tf.math.softplus(w[:, N : 2 * N]).numpy()
+ r = tf.math.sigmoid(w[:, 5 * N : 6 * N]).numpy()
+ p0 = tf.math.sigmoid(w[:, 4 * N : 5 * N]).numpy()
+ p0 = 1 - r ** (0.5 + 0.5 * p0)
+
+ quant_scales_q8 = np.round(quant_scales * 2**8).astype(np.uint16)
+ dead_zone_q10 = np.round(dead_zone * 2**10).astype(np.uint16)
+ r_q15 = np.round(r * 2**15).astype(np.uint16)
+ p0_q15 = np.round(p0 * 2**15).astype(np.uint16)
+
+ printVector(f, quant_scales_q8, 'dred_quant_scales_q8', dtype='opus_uint16', static=False)
+ printVector(f, dead_zone_q10, 'dred_dead_zone_q10', dtype='opus_uint16', static=False)
+ printVector(f, r_q15, 'dred_r_q15', dtype='opus_uint16', static=False)
+ printVector(f, p0_q15, 'dred_p0_q15', dtype='opus_uint16', static=False)
+
+ fh.write(
+f"""
+extern const opus_uint16 dred_quant_scales_q8[{levels * N}];
+extern const opus_uint16 dred_dead_zone_q10[{levels * N}];
+extern const opus_uint16 dred_r_q15[{levels * N}];
+extern const opus_uint16 dred_p0_q15[{levels * N}];
+
+"""
+ )
+
+if __name__ == "__main__":
+
+ model, encoder, decoder, qembedding = new_rdovae_model(20, args.latent_dim, cond_size=args.cond_size, nb_quant=args.quant_levels)
+ model.load_weights(args.weights)
+
+
+
+
+ # encoder
+ encoder_dense_names = [
+ 'enc_dense1',
+ 'enc_dense3',
+ 'enc_dense5',
+ 'enc_dense7',
+ 'enc_dense8',
+ 'gdense1',
+ 'gdense2'
+ ]
+
+ encoder_gru_names = [
+ 'enc_dense2',
+ 'enc_dense4',
+ 'enc_dense6'
+ ]
+
+ encoder_conv1d_names = [
+ 'bits_dense'
+ ]
+
+ source_fid = open("dred_rdovae_enc_data.c", 'w')
+ header_fid = open("dred_rdovae_enc_data.h", 'w')
+
+ start_header(header_fid, "dred_rdovae_enc_data.h")
+ start_source(source_fid, "dred_rdovae_enc_data.h", os.path.basename(args.weights))
+
+ header_fid.write(
+f"""
+#include "dred_rdovae_constants.h"
+
+#include "nnet.h"
+"""
+ )
+
+ # dump GRUs
+ max_rnn_neurons_enc = max(
+ [
+ dump_gru_layer(encoder.get_layer(name), source_fid, header_fid, dotp=True, sparse=True)
+ for name in encoder_gru_names
+ ]
+ )
+
+ # dump conv layers
+ max_conv_inputs = max(
+ [
+ dump_conv1d_layer(encoder.get_layer(name), source_fid, header_fid)
+ for name in encoder_conv1d_names
+ ]
+ )
+
+ # dump Dense layers
+ for name in encoder_dense_names:
+ layer = encoder.get_layer(name)
+ dump_dense_layer(layer, source_fid, header_fid)
+
+ # some global constants
+ header_fid.write(
+f"""
+
+#define DRED_ENC_MAX_RNN_NEURONS {max_rnn_neurons_enc}
+
+#define DRED_ENC_MAX_CONV_INPUTS {max_conv_inputs}
+
+"""
+ )
+
+ finish_header(header_fid)
+ finish_source(source_fid)
+
+ header_fid.close()
+ source_fid.close()
+
+ # statistical model
+ source_fid = open("dred_rdovae_stats_data.c", 'w')
+ header_fid = open("dred_rdovae_stats_data.h", 'w')
+
+ start_header(header_fid, "dred_rdovae_stats_data.h")
+ start_source(source_fid, "dred_rdovae_stats_data.h", os.path.basename(args.weights))
+
+ header_fid.write(
+"""
+
+#include "opus_types.h"
+
+"""
+ )
+
+ dump_statistical_model(qembedding, source_fid, header_fid)
+
+ finish_header(header_fid)
+ finish_source(source_fid)
+
+ header_fid.close()
+ source_fid.close()
+
+ # decoder
+ decoder_dense_names = [
+ 'state1',
+ 'state2',
+ 'state3',
+ 'dec_dense1',
+ 'dec_dense3',
+ 'dec_dense5',
+ 'dec_dense7',
+ 'dec_dense8',
+ 'dec_final'
+ ]
+
+ decoder_gru_names = [
+ 'dec_dense2',
+ 'dec_dense4',
+ 'dec_dense6'
+ ]
+
+ source_fid = open("dred_rdovae_dec_data.c", 'w')
+ header_fid = open("dred_rdovae_dec_data.h", 'w')
+
+ start_header(header_fid, "dred_rdovae_dec_data.h")
+ start_source(source_fid, "dred_rdovae_dec_data.h", os.path.basename(args.weights))
+
+ header_fid.write(
+f"""
+#include "dred_rdovae_constants.h"
+
+#include "nnet.h"
+"""
+ )
+
+
+ # dump GRUs
+ max_rnn_neurons_dec = max(
+ [
+ dump_gru_layer(decoder.get_layer(name), source_fid, header_fid, dotp=True, sparse=True)
+ for name in decoder_gru_names
+ ]
+ )
+
+ # dump Dense layers
+ for name in decoder_dense_names:
+ layer = decoder.get_layer(name)
+ dump_dense_layer(layer, source_fid, header_fid)
+
+ # some global constants
+ header_fid.write(
+f"""
+
+#define DRED_DEC_MAX_RNN_NEURONS {max_rnn_neurons_dec}
+
+"""
+ )
+
+ finish_header(header_fid)
+ finish_source(source_fid)
+
+ header_fid.close()
+ source_fid.close()
+
+ # common constants
+ header_fid = open("dred_rdovae_constants.h", 'w')
+ start_header(header_fid, "dred_rdovae_constants.h")
+
+ header_fid.write(
+f"""
+#define DRED_NUM_FEATURES 20
+
+#define DRED_LATENT_DIM {args.latent_dim}
+
+#define DRED_STATE_DIM {24}
+
+#define DRED_NUM_QUANTIZATION_LEVELS {qembedding.weights[0].shape[0]}
+
+#define DRED_MAX_RNN_NEURONS {max(max_rnn_neurons_enc, max_rnn_neurons_dec)}
+
+#define DRED_MAX_CONV_INPUTS {max_conv_inputs}
+"""
+ )
+
+ finish_header(header_fid) \ No newline at end of file
diff --git a/dnn/training_tf2/encode_rdovae.py b/dnn/training_tf2/encode_rdovae.py
new file mode 100644
index 00000000..f144fc48
--- /dev/null
+++ b/dnn/training_tf2/encode_rdovae.py
@@ -0,0 +1,125 @@
+#!/usr/bin/python3
+'''Copyright (c) 2021-2022 Amazon
+ Copyright (c) 2018-2019 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+# Train an LPCNet model
+
+import argparse
+#from plc_loader import PLCLoader
+
+parser = argparse.ArgumentParser(description='Train a PLC model')
+
+parser.add_argument('features', metavar='<features file>', help='binary features file (float32)')
+parser.add_argument('output', metavar='<output>', help='trained model file (.h5)')
+parser.add_argument('--model', metavar='<model>', default='rdovae', help='PLC model python definition (without .py)')
+group1 = parser.add_mutually_exclusive_group()
+group1.add_argument('--weights', metavar='<input weights>', help='model weights')
+parser.add_argument('--cond-size', metavar='<units>', default=1024, type=int, help='number of units in conditioning network (default 1024)')
+parser.add_argument('--batch-size', metavar='<batch size>', default=1, type=int, help='batch size to use (default 128)')
+parser.add_argument('--seq-length', metavar='<sequence length>', default=1000, type=int, help='sequence length to use (default 1000)')
+
+
+args = parser.parse_args()
+
+import importlib
+rdovae = importlib.import_module(args.model)
+
+from rdovae import apply_dead_zone
+
+import sys
+import numpy as np
+from tensorflow.keras.optimizers import Adam
+from tensorflow.keras.callbacks import ModelCheckpoint, CSVLogger
+import tensorflow.keras.backend as K
+import h5py
+
+import tensorflow as tf
+from rdovae import pvq_quantize
+
+# Try reducing batch_size if you run out of memory on your GPU
+batch_size = args.batch_size
+
+model, encoder, decoder, qembedding = rdovae.new_rdovae_model(nb_used_features=20, nb_bits=80, batch_size=batch_size, cond_size=args.cond_size)
+model.load_weights(args.weights)
+
+lpc_order = 16
+
+feature_file = args.features
+nb_features = model.nb_used_features + lpc_order
+nb_used_features = model.nb_used_features
+sequence_size = args.seq_length
+
+# u for unquantised, load 16 bit PCM samples and convert to mu-law
+
+
+features = np.memmap(feature_file, dtype='float32', mode='r')
+nb_sequences = len(features)//(nb_features*sequence_size)//batch_size*batch_size
+features = features[:nb_sequences*sequence_size*nb_features]
+
+features = np.reshape(features, (nb_sequences, sequence_size, nb_features))
+print(features.shape)
+features = features[:, :, :nb_used_features]
+#features = np.random.randn(73600, 1000, 17)
+
+
+bits, gru_state_dec = encoder.predict([features], batch_size=batch_size)
+(gru_state_dec).astype('float32').tofile(args.output + "-state.f32")
+
+
+#dist = rdovae.feat_dist_loss(features, quant_out)
+#rate = rdovae.sq1_rate_loss(features, model_bits)
+#rate2 = rdovae.sq_rate_metric(features, model_bits)
+#print(dist, rate, rate2)
+
+print("shapes are:")
+print(bits.shape)
+print(gru_state_dec.shape)
+
+features.astype('float32').tofile(args.output + "-input.f32")
+#quant_out.astype('float32').tofile(args.output + "-enc_dec.f32")
+nbits=80
+bits.astype('float32').tofile(args.output + "-syms.f32")
+
+lambda_val = 0.0002 * np.ones((nb_sequences, sequence_size//2, 1))
+quant_id = np.round(3.8*np.log(lambda_val/.0002)).astype('int16')
+quant_id = quant_id[:,:,0]
+quant_embed = qembedding(quant_id)
+quant_scale = tf.math.softplus(quant_embed[:,:,:nbits])
+dead_zone = tf.math.softplus(quant_embed[:, :, nbits : 2 * nbits])
+
+bits = bits*quant_scale
+bits = np.round(apply_dead_zone([bits, dead_zone]).numpy())
+bits = bits/quant_scale
+
+gru_state_dec = pvq_quantize(gru_state_dec, 82)
+#gru_state_dec = gru_state_dec/(1e-15+tf.norm(gru_state_dec, axis=-1,keepdims=True))
+gru_state_dec = gru_state_dec[:,-1,:]
+dec_out = decoder([bits[:,1::2,:], gru_state_dec])
+
+print(dec_out.shape)
+
+dec_out.numpy().astype('float32').tofile(args.output + "-quant_out.f32")
diff --git a/dnn/training_tf2/fec_encoder.py b/dnn/training_tf2/fec_encoder.py
new file mode 100644
index 00000000..15ef12b2
--- /dev/null
+++ b/dnn/training_tf2/fec_encoder.py
@@ -0,0 +1,256 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe and Jean-Marc Valin */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+import os
+import subprocess
+import argparse
+
+
+import numpy as np
+from scipy.io import wavfile
+import tensorflow as tf
+
+from rdovae import new_rdovae_model, pvq_quantize, apply_dead_zone, sq_rate_metric
+from fec_packets import write_fec_packets, read_fec_packets
+
+
+debug = False
+
+if debug:
+ args = type('dummy', (object,),
+ {
+ 'input' : 'item1.wav',
+ 'weights' : 'testout/rdovae_alignment_fix_1024_120.h5',
+ 'enc_lambda' : 0.0007,
+ 'output' : "test_0007.fec",
+ 'cond_size' : 1024,
+ 'num_redundancy_frames' : 64,
+ 'extra_delay' : 0,
+ 'dump_data' : './dump_data'
+ })()
+ os.environ['CUDA_VISIBLE_DEVICES']=""
+else:
+ parser = argparse.ArgumentParser(description='Encode redundancy for Opus neural FEC. Designed for use with voip application and 20ms frames')
+
+ parser.add_argument('input', metavar='<input signal>', help='audio input (.wav or .raw or .pcm as int16)')
+ parser.add_argument('weights', metavar='<weights>', help='trained model file (.h5)')
+# parser.add_argument('enc_lambda', metavar='<lambda>', type=float, help='lambda for controlling encoder rate')
+ parser.add_argument('output', type=str, help='output file (will be extended with .fec)')
+
+ parser.add_argument('--dump-data', type=str, default='./dump_data', help='path to dump data executable (default ./dump_data)')
+ parser.add_argument('--cond-size', metavar='<units>', default=1024, type=int, help='number of units in conditioning network (default 1024)')
+ parser.add_argument('--quant-levels', type=int, help="number of quantization steps (default: 40)", default=40)
+ parser.add_argument('--num-redundancy-frames', default=64, type=int, help='number of redundancy frames (20ms) per packet (default 64)')
+ parser.add_argument('--extra-delay', default=0, type=int, help="last features in packet are calculated with the decoder aligned samples, use this option to add extra delay (in samples at 16kHz)")
+ parser.add_argument('--lossfile', type=str, help='file containing loss trace (0 for frame received, 1 for lost)')
+
+ parser.add_argument('--debug-output', action='store_true', help='if set, differently assembled features are written to disk')
+
+ args = parser.parse_args()
+
+model, encoder, decoder, qembedding = new_rdovae_model(nb_used_features=20, nb_bits=80, batch_size=1, nb_quant=args.quant_levels, cond_size=args.cond_size)
+model.load_weights(args.weights)
+
+lpc_order = 16
+
+## prepare input signal
+# SILK frame size is 20ms and LPCNet subframes are 10ms
+subframe_size = 160
+frame_size = 2 * subframe_size
+
+# 91 samples delay to align with SILK decoded frames
+silk_delay = 91
+
+# prepend zeros to have enough history to produce the first package
+zero_history = (args.num_redundancy_frames - 1) * frame_size
+
+# dump data has a (feature) delay of 10ms
+dump_data_delay = 160
+
+total_delay = silk_delay + zero_history + args.extra_delay - dump_data_delay
+
+# load signal
+if args.input.endswith('.raw') or args.input.endswith('.pcm') or args.input.endswith('.sw'):
+ signal = np.fromfile(args.input, dtype='int16')
+
+elif args.input.endswith('.wav'):
+ fs, signal = wavfile.read(args.input)
+else:
+ raise ValueError(f'unknown input signal format: {args.input}')
+
+# fill up last frame with zeros
+padded_signal_length = len(signal) + total_delay
+tail = padded_signal_length % frame_size
+right_padding = (frame_size - tail) % frame_size
+
+signal = np.concatenate((np.zeros(total_delay, dtype=np.int16), signal, np.zeros(right_padding, dtype=np.int16)))
+
+padded_signal_file = os.path.splitext(args.input)[0] + '_padded.raw'
+signal.tofile(padded_signal_file)
+
+# write signal and call dump_data to create features
+
+feature_file = os.path.splitext(args.input)[0] + '_features.f32'
+command = f"{args.dump_data} -test {padded_signal_file} {feature_file}"
+r = subprocess.run(command, shell=True)
+if r.returncode != 0:
+ raise RuntimeError(f"command '{command}' failed with exit code {r.returncode}")
+
+# load features
+nb_features = model.nb_used_features + lpc_order
+nb_used_features = model.nb_used_features
+
+# load features
+features = np.fromfile(feature_file, dtype='float32')
+num_subframes = len(features) // nb_features
+num_subframes = 2 * (num_subframes // 2)
+num_frames = num_subframes // 2
+
+features = np.reshape(features, (1, -1, nb_features))
+features = features[:, :, :nb_used_features]
+features = features[:, :num_subframes, :]
+
+#variable quantizer depending on the delay
+q0 = 3
+q1 = 15
+quant_id = np.round(q1 + (q0-q1)*np.arange(args.num_redundancy_frames//2)/args.num_redundancy_frames).astype('int16')
+#print(quant_id)
+
+quant_embed = qembedding(quant_id)
+
+# run encoder
+print("running fec encoder...")
+symbols, gru_state_dec = encoder.predict(features)
+
+# apply quantization
+nsymbols = 80
+quant_scale = tf.math.softplus(quant_embed[:, :nsymbols]).numpy()
+dead_zone = tf.math.softplus(quant_embed[:, nsymbols : 2 * nsymbols]).numpy()
+#symbols = apply_dead_zone([symbols, dead_zone]).numpy()
+#qsymbols = np.round(symbols)
+quant_gru_state_dec = pvq_quantize(gru_state_dec, 82)
+
+# rate estimate
+hard_distr_embed = tf.math.sigmoid(quant_embed[:, 4 * nsymbols : ]).numpy()
+#rate_input = np.concatenate((qsymbols, hard_distr_embed, enc_lambda), axis=-1)
+#rates = sq_rate_metric(None, rate_input, reduce=False).numpy()
+
+# run decoder
+input_length = args.num_redundancy_frames // 2
+offset = args.num_redundancy_frames - 1
+
+packets = []
+packet_sizes = []
+
+sym_batch = np.zeros((num_frames-offset, args.num_redundancy_frames//2, nsymbols), dtype='float32')
+quant_state = quant_gru_state_dec[0, offset:num_frames, :]
+#pack symbols for batch processing
+for i in range(offset, num_frames):
+ sym_batch[i-offset, :, :] = symbols[0, i - 2 * input_length + 2 : i + 1 : 2, :]
+
+#quantize symbols
+sym_batch = sym_batch * quant_scale
+sym_batch = apply_dead_zone([sym_batch, dead_zone]).numpy()
+sym_batch = np.round(sym_batch)
+
+hard_distr_embed = np.broadcast_to(hard_distr_embed, (sym_batch.shape[0], sym_batch.shape[1], 2*sym_batch.shape[2]))
+fake_lambda = np.ones((sym_batch.shape[0], sym_batch.shape[1], 1), dtype='float32')
+rate_input = np.concatenate((sym_batch, hard_distr_embed, fake_lambda), axis=-1)
+rates = sq_rate_metric(None, rate_input, reduce=False).numpy()
+#print(rates.shape)
+print("average rate = ", np.mean(rates[args.num_redundancy_frames:,:]))
+
+#sym_batch.tofile('qsyms.f32')
+
+sym_batch = sym_batch / quant_scale
+#print(sym_batch.shape, quant_state.shape)
+#features = decoder.predict([sym_batch, quant_state])
+features = decoder([sym_batch, quant_state])
+
+#for i in range(offset, num_frames):
+# print(f"processing frame {i - offset}...")
+# features = decoder.predict([qsymbols[:, i - 2 * input_length + 2 : i + 1 : 2, :], quant_embed_dec[:, i - 2 * input_length + 2 : i + 1 : 2, :], quant_gru_state_dec[:, i, :]])
+# packets.append(features)
+# packet_size = 8 * int((np.sum(rates[:, i - 2 * input_length + 2 : i + 1 : 2]) + 7) / 8) + 64
+# packet_sizes.append(packet_size)
+
+
+# write packets
+packet_file = args.output + '.fec' if not args.output.endswith('.fec') else args.output
+#write_fec_packets(packet_file, packets, packet_sizes)
+
+
+#print(f"average redundancy rate: {int(round(sum(packet_sizes) / len(packet_sizes) * 50 / 1000))} kbps")
+
+if args.lossfile != None:
+ loss = np.loadtxt(args.lossfile, dtype='int16')
+ fec_out = np.zeros((features.shape[0]*2, features.shape[-1]), dtype='float32')
+ foffset = -2
+ ptr = 0;
+ count = 2;
+ for i in range(features.shape[0]):
+ if (loss[i] == 0) or (i == features.shape[0]-1):
+ fec_out[ptr:ptr+count,:] = features[i, foffset:, :]
+ #print("filled ", count)
+ foffset = -2
+ ptr = ptr+count
+ count = 2
+ else:
+ count = count + 2
+ foffset = foffset - 2
+
+ fec_out_full = np.zeros((fec_out.shape[0], nb_features), dtype=np.float32)
+ fec_out_full[:, :nb_used_features] = fec_out
+
+ fec_out_full.tofile(packet_file[:-4] + f'_fec.f32')
+
+
+#create packets array like in the original version for debugging purposes
+for i in range(offset, num_frames):
+ packets.append(features[i-offset:i-offset+1, :, :])
+
+if args.debug_output:
+ import itertools
+
+ #batches = [2, 4]
+ batches = [4]
+ #offsets = [0, 4, 20]
+ offsets = [0, (args.num_redundancy_frames - 2)*2]
+ # sanity checks
+ # 1. concatenate features at offset 0
+ for batch, offset in itertools.product(batches, offsets):
+
+ stop = packets[0].shape[1] - offset
+ print(batch, offset, stop)
+ test_features = np.concatenate([packet[:,stop - batch: stop, :] for packet in packets[::batch//2]], axis=1)
+
+ test_features_full = np.zeros((test_features.shape[1], nb_features), dtype=np.float32)
+ test_features_full[:, :nb_used_features] = test_features[0, :, :]
+
+ print(f"writing debug output {packet_file[:-4] + f'_tf_batch{batch}_offset{offset}.f32'}")
+ test_features_full.tofile(packet_file[:-4] + f'_tf_batch{batch}_offset{offset}.f32')
diff --git a/dnn/training_tf2/fec_packets.c b/dnn/training_tf2/fec_packets.c
new file mode 100644
index 00000000..ee08ba95
--- /dev/null
+++ b/dnn/training_tf2/fec_packets.c
@@ -0,0 +1,142 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include "fec_packets.h"
+
+int get_fec_frame(const char * const filename, float *features, int packet_index, int subframe_index)
+{
+
+ int16_t version;
+ int16_t header_size;
+ int16_t num_packets;
+ int16_t packet_size;
+ int16_t subframe_size;
+ int16_t subframes_per_packet;
+ int16_t num_features;
+ long offset;
+
+ FILE *fid = fopen(filename, "rb");
+
+ /* read header */
+ if (fread(&version, sizeof(version), 1, fid) != 1) goto error;
+ if (fread(&header_size, sizeof(header_size), 1, fid) != 1) goto error;
+ if (fread(&num_packets, sizeof(num_packets), 1, fid) != 1) goto error;
+ if (fread(&packet_size, sizeof(packet_size), 1, fid) != 1) goto error;
+ if (fread(&subframe_size, sizeof(subframe_size), 1, fid) != 1) goto error;
+ if (fread(&subframes_per_packet, sizeof(subframes_per_packet), 1, fid) != 1) goto error;
+ if (fread(&num_features, sizeof(num_features), 1, fid) != 1) goto error;
+
+ /* check if indices are valid */
+ if (packet_index >= num_packets || subframe_index >= subframes_per_packet)
+ {
+ fprintf(stderr, "get_fec_frame: index out of bounds\n");
+ goto error;
+ }
+
+ /* calculate offset in file (+ 2 is for rate) */
+ offset = header_size + packet_index * packet_size + 2 + subframe_index * subframe_size;
+ fseek(fid, offset, SEEK_SET);
+
+ /* read features */
+ if (fread(features, sizeof(*features), num_features, fid) != num_features) goto error;
+
+ fclose(fid);
+ return 0;
+
+error:
+ fclose(fid);
+ return 1;
+}
+
+int get_fec_rate(const char * const filename, int packet_index)
+{
+ int16_t version;
+ int16_t header_size;
+ int16_t num_packets;
+ int16_t packet_size;
+ int16_t subframe_size;
+ int16_t subframes_per_packet;
+ int16_t num_features;
+ long offset;
+ int16_t rate;
+
+ FILE *fid = fopen(filename, "rb");
+
+ /* read header */
+ if (fread(&version, sizeof(version), 1, fid) != 1) goto error;
+ if (fread(&header_size, sizeof(header_size), 1, fid) != 1) goto error;
+ if (fread(&num_packets, sizeof(num_packets), 1, fid) != 1) goto error;
+ if (fread(&packet_size, sizeof(packet_size), 1, fid) != 1) goto error;
+ if (fread(&subframe_size, sizeof(subframe_size), 1, fid) != 1) goto error;
+ if (fread(&subframes_per_packet, sizeof(subframes_per_packet), 1, fid) != 1) goto error;
+ if (fread(&num_features, sizeof(num_features), 1, fid) != 1) goto error;
+
+ /* check if indices are valid */
+ if (packet_index >= num_packets)
+ {
+ fprintf(stderr, "get_fec_rate: index out of bounds\n");
+ goto error;
+ }
+
+ /* calculate offset in file (+ 2 is for rate) */
+ offset = header_size + packet_index * packet_size;
+ fseek(fid, offset, SEEK_SET);
+
+ /* read rate */
+ if (fread(&rate, sizeof(rate), 1, fid) != 1) goto error;
+
+ fclose(fid);
+ return (int) rate;
+
+error:
+ fclose(fid);
+ return -1;
+}
+
+#if 0
+int main()
+{
+ float features[20];
+ int i;
+
+ if (get_fec_frame("../test.fec", &features[0], 0, 127))
+ {
+ return 1;
+ }
+
+ for (i = 0; i < 20; i ++)
+ {
+ printf("%d %f\n", i, features[i]);
+ }
+
+ printf("rate: %d\n", get_fec_rate("../test.fec", 0));
+
+}
+#endif \ No newline at end of file
diff --git a/dnn/training_tf2/fec_packets.h b/dnn/training_tf2/fec_packets.h
new file mode 100644
index 00000000..01b128b1
--- /dev/null
+++ b/dnn/training_tf2/fec_packets.h
@@ -0,0 +1,34 @@
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifndef FEC_PACKETS_H
+#define FEC_PACKETS_H
+
+int get_fec_frame(const char * const filename, float *features, int packet_index, int subframe_index);
+int get_fec_rate(const char * const filename, int packet_index);
+
+#endif
diff --git a/dnn/training_tf2/fec_packets.py b/dnn/training_tf2/fec_packets.py
new file mode 100644
index 00000000..6acbe9d2
--- /dev/null
+++ b/dnn/training_tf2/fec_packets.py
@@ -0,0 +1,108 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+import numpy as np
+
+
+
+def write_fec_packets(filename, packets, rates=None):
+ """ writes packets in binary format """
+
+ assert np.dtype(np.float32).itemsize == 4
+ assert np.dtype(np.int16).itemsize == 2
+
+ # derive some sizes
+ num_packets = len(packets)
+ subframes_per_packet = packets[0].shape[-2]
+ num_features = packets[0].shape[-1]
+
+ # size of float is 4
+ subframe_size = num_features * 4
+ packet_size = subframe_size * subframes_per_packet + 2 # two bytes for rate
+
+ version = 1
+ # header size (version, header_size, num_packets, packet_size, subframe_size, subrames_per_packet, num_features)
+ header_size = 14
+
+ with open(filename, 'wb') as f:
+
+ # header
+ f.write(np.int16(version).tobytes())
+ f.write(np.int16(header_size).tobytes())
+ f.write(np.int16(num_packets).tobytes())
+ f.write(np.int16(packet_size).tobytes())
+ f.write(np.int16(subframe_size).tobytes())
+ f.write(np.int16(subframes_per_packet).tobytes())
+ f.write(np.int16(num_features).tobytes())
+
+ # packets
+ for i, packet in enumerate(packets):
+ if type(rates) == type(None):
+ rate = 0
+ else:
+ rate = rates[i]
+
+ f.write(np.int16(rate).tobytes())
+
+ features = np.flip(packet, axis=-2)
+ f.write(features.astype(np.float32).tobytes())
+
+
+def read_fec_packets(filename):
+ """ reads packets from binary format """
+
+ assert np.dtype(np.float32).itemsize == 4
+ assert np.dtype(np.int16).itemsize == 2
+
+ with open(filename, 'rb') as f:
+
+ # header
+ version = np.frombuffer(f.read(2), dtype=np.int16).item()
+ header_size = np.frombuffer(f.read(2), dtype=np.int16).item()
+ num_packets = np.frombuffer(f.read(2), dtype=np.int16).item()
+ packet_size = np.frombuffer(f.read(2), dtype=np.int16).item()
+ subframe_size = np.frombuffer(f.read(2), dtype=np.int16).item()
+ subframes_per_packet = np.frombuffer(f.read(2), dtype=np.int16).item()
+ num_features = np.frombuffer(f.read(2), dtype=np.int16).item()
+
+ dummy_features = np.zeros((1, subframes_per_packet, num_features), dtype=np.float32)
+
+ # packets
+ rates = []
+ packets = []
+ for i in range(num_packets):
+
+ rate = np.frombuffer(f.read(2), dtype=np.int16).item
+ rates.append(rate)
+
+ features = np.reshape(np.frombuffer(f.read(subframe_size * subframes_per_packet), dtype=np.float32), dummy_features.shape)
+ packet = np.flip(features, axis=-2)
+ packets.append(packet)
+
+ return packets \ No newline at end of file
diff --git a/dnn/training_tf2/keraslayerdump.py b/dnn/training_tf2/keraslayerdump.py
new file mode 100644
index 00000000..5abc1488
--- /dev/null
+++ b/dnn/training_tf2/keraslayerdump.py
@@ -0,0 +1,189 @@
+'''Copyright (c) 2017-2018 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+""" helper functions for dumping some Keras layers to C files """
+
+import numpy as np
+
+
+def printVector(f, vector, name, dtype='float', dotp=False, static=True):
+ """ prints vector as one-dimensional C array """
+ if dotp:
+ vector = vector.reshape((vector.shape[0]//4, 4, vector.shape[1]//8, 8))
+ vector = vector.transpose((2, 0, 3, 1))
+ v = np.reshape(vector, (-1))
+ if static:
+ f.write('static const {} {}[{}] = {{\n '.format(dtype, name, len(v)))
+ else:
+ f.write('const {} {}[{}] = {{\n '.format(dtype, name, len(v)))
+ for i in range(0, len(v)):
+ f.write('{}'.format(v[i]))
+ if (i!=len(v)-1):
+ f.write(',')
+ else:
+ break;
+ if (i%8==7):
+ f.write("\n ")
+ else:
+ f.write(" ")
+ f.write('\n};\n\n')
+ return vector
+
+def printSparseVector(f, A, name, have_diag=True):
+ N = A.shape[0]
+ M = A.shape[1]
+ W = np.zeros((0,), dtype='int')
+ W0 = np.zeros((0,))
+ if have_diag:
+ diag = np.concatenate([np.diag(A[:,:N]), np.diag(A[:,N:2*N]), np.diag(A[:,2*N:])])
+ A[:,:N] = A[:,:N] - np.diag(np.diag(A[:,:N]))
+ A[:,N:2*N] = A[:,N:2*N] - np.diag(np.diag(A[:,N:2*N]))
+ A[:,2*N:] = A[:,2*N:] - np.diag(np.diag(A[:,2*N:]))
+ printVector(f, diag, name + '_diag')
+ AQ = np.minimum(127, np.maximum(-128, np.round(A*128))).astype('int')
+ idx = np.zeros((0,), dtype='int')
+ for i in range(M//8):
+ pos = idx.shape[0]
+ idx = np.append(idx, -1)
+ nb_nonzero = 0
+ for j in range(N//4):
+ block = A[j*4:(j+1)*4, i*8:(i+1)*8]
+ qblock = AQ[j*4:(j+1)*4, i*8:(i+1)*8]
+ if np.sum(np.abs(block)) > 1e-10:
+ nb_nonzero = nb_nonzero + 1
+ idx = np.append(idx, j*4)
+ vblock = qblock.transpose((1,0)).reshape((-1,))
+ W0 = np.concatenate([W0, block.reshape((-1,))])
+ W = np.concatenate([W, vblock])
+ idx[pos] = nb_nonzero
+ f.write('#ifdef DOT_PROD\n')
+ printVector(f, W, name, dtype='qweight')
+ f.write('#else /*DOT_PROD*/\n')
+ printVector(f, W0, name, dtype='qweight')
+ f.write('#endif /*DOT_PROD*/\n')
+ printVector(f, idx, name + '_idx', dtype='int')
+ return AQ
+
+def dump_sparse_gru(self, f, hf):
+ name = 'sparse_' + self.name
+ print("printing layer " + name + " of type sparse " + self.__class__.__name__)
+ weights = self.get_weights()
+ qweights = printSparseVector(f, weights[1], name + '_recurrent_weights')
+ printVector(f, weights[-1], name + '_bias')
+ subias = weights[-1].copy()
+ subias[1,:] = subias[1,:] - np.sum(qweights*(1./128),axis=0)
+ printVector(f, subias, name + '_subias')
+ if hasattr(self, 'activation'):
+ activation = self.activation.__name__.upper()
+ else:
+ activation = 'TANH'
+ if hasattr(self, 'reset_after') and not self.reset_after:
+ reset_after = 0
+ else:
+ reset_after = 1
+ neurons = weights[0].shape[1]//3
+ max_rnn_neurons = neurons
+ f.write('const SparseGRULayer {} = {{\n {}_bias,\n {}_subias,\n {}_recurrent_weights_diag,\n {}_recurrent_weights,\n {}_recurrent_weights_idx,\n {}, ACTIVATION_{}, {}\n}};\n\n'
+ .format(name, name, name, name, name, name, weights[0].shape[1]//3, activation, reset_after))
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ hf.write('#define {}_STATE_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ hf.write('extern const SparseGRULayer {};\n\n'.format(name));
+ return max_rnn_neurons
+
+def dump_gru_layer(self, f, hf, dotp=False, sparse=False):
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()
+ if sparse:
+ qweight = printSparseVector(f, weights[0], name + '_weights', have_diag=False)
+ else:
+ qweight = printVector(f, weights[0], name + '_weights')
+
+ if dotp:
+ f.write('#ifdef DOT_PROD\n')
+ qweight2 = np.clip(np.round(128.*weights[1]).astype('int'), -128, 127)
+ printVector(f, qweight2, name + '_recurrent_weights', dotp=True, dtype='qweight')
+ f.write('#else /*DOT_PROD*/\n')
+ else:
+ qweight2 = weights[1]
+
+ printVector(f, weights[1], name + '_recurrent_weights')
+ if dotp:
+ f.write('#endif /*DOT_PROD*/\n')
+
+ printVector(f, weights[-1], name + '_bias')
+ subias = weights[-1].copy()
+ subias[0,:] = subias[0,:] - np.sum(qweight*(1./128.),axis=0)
+ subias[1,:] = subias[1,:] - np.sum(qweight2*(1./128.),axis=0)
+ printVector(f, subias, name + '_subias')
+ if hasattr(self, 'activation'):
+ activation = self.activation.__name__.upper()
+ else:
+ activation = 'TANH'
+ if hasattr(self, 'reset_after') and not self.reset_after:
+ reset_after = 0
+ else:
+ reset_after = 1
+ neurons = weights[0].shape[1]//3
+ max_rnn_neurons = neurons
+ f.write('const GRULayer {} = {{\n {}_bias,\n {}_subias,\n {}_weights,\n {},\n {}_recurrent_weights,\n {}, {}, ACTIVATION_{}, {}\n}};\n\n'
+ .format(name, name, name, name, name + "_weights_idx" if sparse else "NULL", name, weights[0].shape[0], weights[0].shape[1]//3, activation, reset_after))
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ hf.write('#define {}_STATE_SIZE {}\n'.format(name.upper(), weights[0].shape[1]//3))
+ hf.write('extern const GRULayer {};\n\n'.format(name));
+ return max_rnn_neurons
+
+def dump_dense_layer_impl(name, weights, bias, activation, f, hf):
+ printVector(f, weights, name + '_weights')
+ printVector(f, bias, name + '_bias')
+ f.write('const DenseLayer {} = {{\n {}_bias,\n {}_weights,\n {}, {}, ACTIVATION_{}\n}};\n\n'
+ .format(name, name, name, weights.shape[0], weights.shape[1], activation))
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights.shape[1]))
+ hf.write('extern const DenseLayer {};\n\n'.format(name));
+
+def dump_dense_layer(self, f, hf):
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()
+ activation = self.activation.__name__.upper()
+ dump_dense_layer_impl(name, weights[0], weights[1], activation, f, hf)
+ return False
+
+def dump_conv1d_layer(self, f, hf):
+ name = self.name
+ print("printing layer " + name + " of type " + self.__class__.__name__)
+ weights = self.get_weights()
+ printVector(f, weights[0], name + '_weights')
+ printVector(f, weights[-1], name + '_bias')
+ activation = self.activation.__name__.upper()
+ max_conv_inputs = weights[0].shape[1]*weights[0].shape[0]
+ f.write('const Conv1DLayer {} = {{\n {}_bias,\n {}_weights,\n {}, {}, {}, ACTIVATION_{}\n}};\n\n'
+ .format(name, name, name, weights[0].shape[1], weights[0].shape[0], weights[0].shape[2], activation))
+ hf.write('#define {}_OUT_SIZE {}\n'.format(name.upper(), weights[0].shape[2]))
+ hf.write('#define {}_STATE_SIZE ({}*{})\n'.format(name.upper(), weights[0].shape[1], (weights[0].shape[0]-1)))
+ hf.write('#define {}_DELAY {}\n'.format(name.upper(), (weights[0].shape[0]-1)//2))
+ hf.write('extern const Conv1DLayer {};\n\n'.format(name));
+ return max_conv_inputs
diff --git a/dnn/training_tf2/lossfuncs.py b/dnn/training_tf2/lossfuncs.py
new file mode 100644
index 00000000..78be1fd6
--- /dev/null
+++ b/dnn/training_tf2/lossfuncs.py
@@ -0,0 +1,99 @@
+"""
+Custom Loss functions and metrics for training/analysis
+"""
+
+from tf_funcs import *
+import tensorflow as tf
+
+# The following loss functions all expect the lpcnet model to output the lpc prediction
+
+# Computing the excitation by subtracting the lpc prediction from the target, followed by minimizing the cross entropy
+def res_from_sigloss():
+ def loss(y_true,y_pred):
+ p = y_pred[:,:,0:1]
+ model_out = y_pred[:,:,2:]
+ e_gt = tf_l2u(y_true - p)
+ e_gt = tf.round(e_gt)
+ e_gt = tf.cast(e_gt,'int32')
+ sparse_cel = tf.keras.losses.SparseCategoricalCrossentropy(reduction=tf.keras.losses.Reduction.NONE)(e_gt,model_out)
+ return sparse_cel
+ return loss
+
+# Interpolated and Compensated Loss (In case of end to end lpcnet)
+# Interpolates between adjacent embeddings based on the fractional value of the excitation computed (similar to the embedding interpolation)
+# Also adds a probability compensation (to account for matching cross entropy in the linear domain), weighted by gamma
+def interp_mulaw(gamma = 1):
+ def loss(y_true,y_pred):
+ y_true = tf.cast(y_true, 'float32')
+ p = y_pred[:,:,0:1]
+ real_p = y_pred[:,:,1:2]
+ model_out = y_pred[:,:,2:]
+ e_gt = tf_l2u(y_true - p)
+ exc_gt = tf_l2u(y_true - real_p)
+ prob_compensation = tf.squeeze((K.abs(e_gt - 128)/128.0)*K.log(256.0))
+ regularization = tf.squeeze((K.abs(exc_gt - 128)/128.0)*K.log(256.0))
+ alpha = e_gt - tf.math.floor(e_gt)
+ alpha = tf.tile(alpha,[1,1,256])
+ e_gt = tf.cast(e_gt,'int32')
+ e_gt = tf.clip_by_value(e_gt,0,254)
+ interp_probab = (1 - alpha)*model_out + alpha*tf.roll(model_out,shift = -1,axis = -1)
+ sparse_cel = tf.keras.losses.SparseCategoricalCrossentropy(reduction=tf.keras.losses.Reduction.NONE)(e_gt,interp_probab)
+ loss_mod = sparse_cel + prob_compensation + gamma*regularization
+ return loss_mod
+ return loss
+
+# Same as above, except a metric
+def metric_oginterploss(y_true,y_pred):
+ p = y_pred[:,:,0:1]
+ model_out = y_pred[:,:,2:]
+ e_gt = tf_l2u(y_true - p)
+ prob_compensation = tf.squeeze((K.abs(e_gt - 128)/128.0)*K.log(256.0))
+ alpha = e_gt - tf.math.floor(e_gt)
+ alpha = tf.tile(alpha,[1,1,256])
+ e_gt = tf.cast(e_gt,'int32')
+ e_gt = tf.clip_by_value(e_gt,0,254)
+ interp_probab = (1 - alpha)*model_out + alpha*tf.roll(model_out,shift = -1,axis = -1)
+ sparse_cel = tf.keras.losses.SparseCategoricalCrossentropy(reduction=tf.keras.losses.Reduction.NONE)(e_gt,interp_probab)
+ loss_mod = sparse_cel + prob_compensation
+ return loss_mod
+
+# Interpolated cross entropy loss metric
+def metric_icel(y_true, y_pred):
+ p = y_pred[:,:,0:1]
+ model_out = y_pred[:,:,2:]
+ e_gt = tf_l2u(y_true - p)
+ alpha = e_gt - tf.math.floor(e_gt)
+ alpha = tf.tile(alpha,[1,1,256])
+ e_gt = tf.cast(e_gt,'int32')
+ e_gt = tf.clip_by_value(e_gt,0,254) #Check direction
+ interp_probab = (1 - alpha)*model_out + alpha*tf.roll(model_out,shift = -1,axis = -1)
+ sparse_cel = tf.keras.losses.SparseCategoricalCrossentropy(reduction=tf.keras.losses.Reduction.NONE)(e_gt,interp_probab)
+ return sparse_cel
+
+# Non-interpolated (rounded) cross entropy loss metric
+def metric_cel(y_true, y_pred):
+ y_true = tf.cast(y_true, 'float32')
+ p = y_pred[:,:,0:1]
+ model_out = y_pred[:,:,2:]
+ e_gt = tf_l2u(y_true - p)
+ e_gt = tf.round(e_gt)
+ e_gt = tf.cast(e_gt,'int32')
+ e_gt = tf.clip_by_value(e_gt,0,255)
+ sparse_cel = tf.keras.losses.SparseCategoricalCrossentropy(reduction=tf.keras.losses.Reduction.NONE)(e_gt,model_out)
+ return sparse_cel
+
+# Variance metric of the output excitation
+def metric_exc_sd(y_true,y_pred):
+ p = y_pred[:,:,0:1]
+ e_gt = tf_l2u(y_true - p)
+ sd_egt = tf.keras.losses.MeanSquaredError(reduction=tf.keras.losses.Reduction.NONE)(e_gt,128)
+ return sd_egt
+
+def loss_matchlar():
+ def loss(y_true,y_pred):
+ model_rc = y_pred[:,:,:16]
+ #y_true = lpc2rc(y_true)
+ loss_lar_diff = K.log((1.01 + model_rc)/(1.01 - model_rc)) - K.log((1.01 + y_true)/(1.01 - y_true))
+ loss_lar_diff = tf.square(loss_lar_diff)
+ return tf.reduce_mean(loss_lar_diff, axis=-1)
+ return loss
diff --git a/dnn/training_tf2/lpcnet.py b/dnn/training_tf2/lpcnet.py
new file mode 100644
index 00000000..497f7572
--- /dev/null
+++ b/dnn/training_tf2/lpcnet.py
@@ -0,0 +1,339 @@
+#!/usr/bin/python3
+'''Copyright (c) 2018 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+import math
+import tensorflow as tf
+from tensorflow.keras.models import Model
+from tensorflow.keras.layers import Input, GRU, Dense, Embedding, Reshape, Concatenate, Lambda, Conv1D, Multiply, Add, Bidirectional, MaxPooling1D, Activation, GaussianNoise
+from tensorflow.compat.v1.keras.layers import CuDNNGRU
+from tensorflow.keras import backend as K
+from tensorflow.keras.constraints import Constraint
+from tensorflow.keras.initializers import Initializer
+from tensorflow.keras.callbacks import Callback
+from mdense import MDense
+import numpy as np
+import h5py
+import sys
+from tf_funcs import *
+from diffembed import diff_Embed
+from parameters import set_parameter
+
+frame_size = 160
+pcm_bits = 8
+embed_size = 128
+pcm_levels = 2**pcm_bits
+
+def interleave(p, samples):
+ p2=tf.expand_dims(p, 3)
+ nb_repeats = pcm_levels//(2*p.shape[2])
+ p3 = tf.reshape(tf.repeat(tf.concat([1-p2, p2], 3), nb_repeats), (-1, samples, pcm_levels))
+ return p3
+
+def tree_to_pdf(p, samples):
+ return interleave(p[:,:,1:2], samples) * interleave(p[:,:,2:4], samples) * interleave(p[:,:,4:8], samples) * interleave(p[:,:,8:16], samples) \
+ * interleave(p[:,:,16:32], samples) * interleave(p[:,:,32:64], samples) * interleave(p[:,:,64:128], samples) * interleave(p[:,:,128:256], samples)
+
+def tree_to_pdf_train(p):
+ #FIXME: try not to hardcode the 2400 samples (15 frames * 160 samples/frame)
+ return tree_to_pdf(p, 2400)
+
+def tree_to_pdf_infer(p):
+ return tree_to_pdf(p, 1)
+
+def quant_regularizer(x):
+ Q = 128
+ Q_1 = 1./Q
+ #return .01 * tf.reduce_mean(1 - tf.math.cos(2*3.1415926535897931*(Q*x-tf.round(Q*x))))
+ return .01 * tf.reduce_mean(K.sqrt(K.sqrt(1.0001 - tf.math.cos(2*3.1415926535897931*(Q*x-tf.round(Q*x))))))
+
+class Sparsify(Callback):
+ def __init__(self, t_start, t_end, interval, density, quantize=False):
+ super(Sparsify, self).__init__()
+ self.batch = 0
+ self.t_start = t_start
+ self.t_end = t_end
+ self.interval = interval
+ self.final_density = density
+ self.quantize = quantize
+
+ def on_batch_end(self, batch, logs=None):
+ #print("batch number", self.batch)
+ self.batch += 1
+ if self.quantize or (self.batch > self.t_start and (self.batch-self.t_start) % self.interval == 0) or self.batch >= self.t_end:
+ #print("constrain");
+ layer = self.model.get_layer('gru_a')
+ w = layer.get_weights()
+ p = w[1]
+ nb = p.shape[1]//p.shape[0]
+ N = p.shape[0]
+ #print("nb = ", nb, ", N = ", N);
+ #print(p.shape)
+ #print ("density = ", density)
+ for k in range(nb):
+ density = self.final_density[k]
+ if self.batch < self.t_end and not self.quantize:
+ r = 1 - (self.batch-self.t_start)/(self.t_end - self.t_start)
+ density = 1 - (1-self.final_density[k])*(1 - r*r*r)
+ A = p[:, k*N:(k+1)*N]
+ A = A - np.diag(np.diag(A))
+ #This is needed because of the CuDNNGRU strange weight ordering
+ A = np.transpose(A, (1, 0))
+ L=np.reshape(A, (N//4, 4, N//8, 8))
+ S=np.sum(L*L, axis=-1)
+ S=np.sum(S, axis=1)
+ SS=np.sort(np.reshape(S, (-1,)))
+ thresh = SS[round(N*N//32*(1-density))]
+ mask = (S>=thresh).astype('float32')
+ mask = np.repeat(mask, 4, axis=0)
+ mask = np.repeat(mask, 8, axis=1)
+ mask = np.minimum(1, mask + np.diag(np.ones((N,))))
+ #This is needed because of the CuDNNGRU strange weight ordering
+ mask = np.transpose(mask, (1, 0))
+ p[:, k*N:(k+1)*N] = p[:, k*N:(k+1)*N]*mask
+ #print(thresh, np.mean(mask))
+ if self.quantize and ((self.batch > self.t_start and (self.batch-self.t_start) % self.interval == 0) or self.batch >= self.t_end):
+ if self.batch < self.t_end:
+ threshold = .5*(self.batch - self.t_start)/(self.t_end - self.t_start)
+ else:
+ threshold = .5
+ quant = np.round(p*128.)
+ res = p*128.-quant
+ mask = (np.abs(res) <= threshold).astype('float32')
+ p = mask/128.*quant + (1-mask)*p
+
+ w[1] = p
+ layer.set_weights(w)
+
+class SparsifyGRUB(Callback):
+ def __init__(self, t_start, t_end, interval, grua_units, density, quantize=False):
+ super(SparsifyGRUB, self).__init__()
+ self.batch = 0
+ self.t_start = t_start
+ self.t_end = t_end
+ self.interval = interval
+ self.final_density = density
+ self.grua_units = grua_units
+ self.quantize = quantize
+
+ def on_batch_end(self, batch, logs=None):
+ #print("batch number", self.batch)
+ self.batch += 1
+ if self.quantize or (self.batch > self.t_start and (self.batch-self.t_start) % self.interval == 0) or self.batch >= self.t_end:
+ #print("constrain");
+ layer = self.model.get_layer('gru_b')
+ w = layer.get_weights()
+ p = w[0]
+ N = p.shape[0]
+ M = p.shape[1]//3
+ for k in range(3):
+ density = self.final_density[k]
+ if self.batch < self.t_end and not self.quantize:
+ r = 1 - (self.batch-self.t_start)/(self.t_end - self.t_start)
+ density = 1 - (1-self.final_density[k])*(1 - r*r*r)
+ A = p[:, k*M:(k+1)*M]
+ #This is needed because of the CuDNNGRU strange weight ordering
+ A = np.reshape(A, (M, N))
+ A = np.transpose(A, (1, 0))
+ N2 = self.grua_units
+ A2 = A[:N2, :]
+ L=np.reshape(A2, (N2//4, 4, M//8, 8))
+ S=np.sum(L*L, axis=-1)
+ S=np.sum(S, axis=1)
+ SS=np.sort(np.reshape(S, (-1,)))
+ thresh = SS[round(M*N2//32*(1-density))]
+ mask = (S>=thresh).astype('float32')
+ mask = np.repeat(mask, 4, axis=0)
+ mask = np.repeat(mask, 8, axis=1)
+ A = np.concatenate([A2*mask, A[N2:,:]], axis=0)
+ #This is needed because of the CuDNNGRU strange weight ordering
+ A = np.transpose(A, (1, 0))
+ A = np.reshape(A, (N, M))
+ p[:, k*M:(k+1)*M] = A
+ #print(thresh, np.mean(mask))
+ if self.quantize and ((self.batch > self.t_start and (self.batch-self.t_start) % self.interval == 0) or self.batch >= self.t_end):
+ if self.batch < self.t_end:
+ threshold = .5*(self.batch - self.t_start)/(self.t_end - self.t_start)
+ else:
+ threshold = .5
+ quant = np.round(p*128.)
+ res = p*128.-quant
+ mask = (np.abs(res) <= threshold).astype('float32')
+ p = mask/128.*quant + (1-mask)*p
+
+ w[0] = p
+ layer.set_weights(w)
+
+
+class PCMInit(Initializer):
+ def __init__(self, gain=.1, seed=None):
+ self.gain = gain
+ self.seed = seed
+
+ def __call__(self, shape, dtype=None):
+ num_rows = 1
+ for dim in shape[:-1]:
+ num_rows *= dim
+ num_cols = shape[-1]
+ flat_shape = (num_rows, num_cols)
+ if self.seed is not None:
+ np.random.seed(self.seed)
+ a = np.random.uniform(-1.7321, 1.7321, flat_shape)
+ #a[:,0] = math.sqrt(12)*np.arange(-.5*num_rows+.5,.5*num_rows-.4)/num_rows
+ #a[:,1] = .5*a[:,0]*a[:,0]*a[:,0]
+ a = a + np.reshape(math.sqrt(12)*np.arange(-.5*num_rows+.5,.5*num_rows-.4)/num_rows, (num_rows, 1))
+ return self.gain * a.astype("float32")
+
+ def get_config(self):
+ return {
+ 'gain': self.gain,
+ 'seed': self.seed
+ }
+
+class WeightClip(Constraint):
+ '''Clips the weights incident to each hidden unit to be inside a range
+ '''
+ def __init__(self, c=2):
+ self.c = c
+
+ def __call__(self, p):
+ # Ensure that abs of adjacent weights don't sum to more than 127. Otherwise there's a risk of
+ # saturation when implementing dot products with SSSE3 or AVX2.
+ return self.c*p/tf.maximum(self.c, tf.repeat(tf.abs(p[:, 1::2])+tf.abs(p[:, 0::2]), 2, axis=1))
+ #return K.clip(p, -self.c, self.c)
+
+ def get_config(self):
+ return {'name': self.__class__.__name__,
+ 'c': self.c}
+
+constraint = WeightClip(0.992)
+
+def new_lpcnet_model(rnn_units1=384, rnn_units2=16, nb_used_features=20, batch_size=128, training=False, adaptation=False, quantize=False, flag_e2e = False, cond_size=128, lpc_order=16, lpc_gamma=1., lookahead=2):
+ pcm = Input(shape=(None, 1), batch_size=batch_size)
+ dpcm = Input(shape=(None, 3), batch_size=batch_size)
+ feat = Input(shape=(None, nb_used_features), batch_size=batch_size)
+ pitch = Input(shape=(None, 1), batch_size=batch_size)
+ dec_feat = Input(shape=(None, cond_size))
+ dec_state1 = Input(shape=(rnn_units1,))
+ dec_state2 = Input(shape=(rnn_units2,))
+
+ padding = 'valid' if training else 'same'
+ fconv1 = Conv1D(cond_size, 3, padding=padding, activation='tanh', name='feature_conv1')
+ fconv2 = Conv1D(cond_size, 3, padding=padding, activation='tanh', name='feature_conv2')
+ pembed = Embedding(256, 64, name='embed_pitch')
+ cat_feat = Concatenate()([feat, Reshape((-1, 64))(pembed(pitch))])
+
+ cfeat = fconv2(fconv1(cat_feat))
+
+ fdense1 = Dense(cond_size, activation='tanh', name='feature_dense1')
+ fdense2 = Dense(cond_size, activation='tanh', name='feature_dense2')
+
+ if flag_e2e and quantize:
+ fconv1.trainable = False
+ fconv2.trainable = False
+ fdense1.trainable = False
+ fdense2.trainable = False
+
+ cfeat = fdense2(fdense1(cfeat))
+
+ error_calc = Lambda(lambda x: tf_l2u(x[0] - tf.roll(x[1],1,axis = 1)))
+ if flag_e2e:
+ lpcoeffs = diff_rc2lpc(name = "rc2lpc")(cfeat)
+ else:
+ lpcoeffs = Input(shape=(None, lpc_order), batch_size=batch_size)
+
+ real_preds = diff_pred(name = "real_lpc2preds")([pcm,lpcoeffs])
+ weighting = lpc_gamma ** np.arange(1, 17).astype('float32')
+ weighted_lpcoeffs = Lambda(lambda x: x[0]*x[1])([lpcoeffs, weighting])
+ tensor_preds = diff_pred(name = "lpc2preds")([pcm,weighted_lpcoeffs])
+ past_errors = error_calc([pcm,tensor_preds])
+
+ embed = diff_Embed(name='embed_sig',initializer = PCMInit())
+ cpcm = Concatenate()([tf_l2u(pcm),tf_l2u(tensor_preds),past_errors])
+ cpcm = GaussianNoise(.3)(cpcm)
+ cpcm = Reshape((-1, embed_size*3))(embed(cpcm))
+ cpcm_decoder = Reshape((-1, embed_size*3))(embed(dpcm))
+
+
+ rep = Lambda(lambda x: K.repeat_elements(x, frame_size, 1))
+
+ quant = quant_regularizer if quantize else None
+
+ if training:
+ rnn = CuDNNGRU(rnn_units1, return_sequences=True, return_state=True, name='gru_a', stateful=True,
+ recurrent_constraint = constraint, recurrent_regularizer=quant)
+ rnn2 = CuDNNGRU(rnn_units2, return_sequences=True, return_state=True, name='gru_b', stateful=True,
+ kernel_constraint=constraint, recurrent_constraint = constraint, kernel_regularizer=quant, recurrent_regularizer=quant)
+ else:
+ rnn = GRU(rnn_units1, return_sequences=True, return_state=True, recurrent_activation="sigmoid", reset_after='true', name='gru_a', stateful=True,
+ recurrent_constraint = constraint, recurrent_regularizer=quant)
+ rnn2 = GRU(rnn_units2, return_sequences=True, return_state=True, recurrent_activation="sigmoid", reset_after='true', name='gru_b', stateful=True,
+ kernel_constraint=constraint, recurrent_constraint = constraint, kernel_regularizer=quant, recurrent_regularizer=quant)
+
+ rnn_in = Concatenate()([cpcm, rep(cfeat)])
+ md = MDense(pcm_levels, activation='sigmoid', name='dual_fc')
+ gru_out1, _ = rnn(rnn_in)
+ gru_out1 = GaussianNoise(.005)(gru_out1)
+ gru_out2, _ = rnn2(Concatenate()([gru_out1, rep(cfeat)]))
+ ulaw_prob = Lambda(tree_to_pdf_train)(md(gru_out2))
+
+ if adaptation:
+ rnn.trainable=False
+ rnn2.trainable=False
+ md.trainable=False
+ embed.Trainable=False
+
+ m_out = Concatenate(name='pdf')([tensor_preds,real_preds,ulaw_prob])
+ if not flag_e2e:
+ model = Model([pcm, feat, pitch, lpcoeffs], m_out)
+ else:
+ model = Model([pcm, feat, pitch], [m_out, cfeat])
+ model.rnn_units1 = rnn_units1
+ model.rnn_units2 = rnn_units2
+ model.nb_used_features = nb_used_features
+ model.frame_size = frame_size
+
+ if not flag_e2e:
+ encoder = Model([feat, pitch], cfeat)
+ dec_rnn_in = Concatenate()([cpcm_decoder, dec_feat])
+ else:
+ encoder = Model([feat, pitch], [cfeat,lpcoeffs])
+ dec_rnn_in = Concatenate()([cpcm_decoder, dec_feat])
+ dec_gru_out1, state1 = rnn(dec_rnn_in, initial_state=dec_state1)
+ dec_gru_out2, state2 = rnn2(Concatenate()([dec_gru_out1, dec_feat]), initial_state=dec_state2)
+ dec_ulaw_prob = Lambda(tree_to_pdf_infer)(md(dec_gru_out2))
+
+ if flag_e2e:
+ decoder = Model([dpcm, dec_feat, dec_state1, dec_state2], [dec_ulaw_prob, state1, state2])
+ else:
+ decoder = Model([dpcm, dec_feat, dec_state1, dec_state2], [dec_ulaw_prob, state1, state2])
+
+ # add parameters to model
+ set_parameter(model, 'lpc_gamma', lpc_gamma, dtype='float64')
+ set_parameter(model, 'flag_e2e', flag_e2e, dtype='bool')
+ set_parameter(model, 'lookahead', lookahead, dtype='int32')
+
+ return model, encoder, decoder
diff --git a/dnn/training_tf2/lpcnet_plc.py b/dnn/training_tf2/lpcnet_plc.py
new file mode 100644
index 00000000..618e0084
--- /dev/null
+++ b/dnn/training_tf2/lpcnet_plc.py
@@ -0,0 +1,101 @@
+#!/usr/bin/python3
+'''Copyright (c) 2021-2022 Amazon
+ Copyright (c) 2018-2019 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+import math
+import tensorflow as tf
+from tensorflow.keras.models import Model
+from tensorflow.keras.layers import Input, GRU, Dense, Embedding, Reshape, Concatenate, Lambda, Conv1D, Multiply, Add, Bidirectional, MaxPooling1D, Activation, GaussianNoise
+from tensorflow.compat.v1.keras.layers import CuDNNGRU
+from tensorflow.keras import backend as K
+from tensorflow.keras.constraints import Constraint
+from tensorflow.keras.initializers import Initializer
+from tensorflow.keras.callbacks import Callback
+import numpy as np
+
+def quant_regularizer(x):
+ Q = 128
+ Q_1 = 1./Q
+ #return .01 * tf.reduce_mean(1 - tf.math.cos(2*3.1415926535897931*(Q*x-tf.round(Q*x))))
+ return .01 * tf.reduce_mean(K.sqrt(K.sqrt(1.0001 - tf.math.cos(2*3.1415926535897931*(Q*x-tf.round(Q*x))))))
+
+
+class WeightClip(Constraint):
+ '''Clips the weights incident to each hidden unit to be inside a range
+ '''
+ def __init__(self, c=2):
+ self.c = c
+
+ def __call__(self, p):
+ # Ensure that abs of adjacent weights don't sum to more than 127. Otherwise there's a risk of
+ # saturation when implementing dot products with SSSE3 or AVX2.
+ return self.c*p/tf.maximum(self.c, tf.repeat(tf.abs(p[:, 1::2])+tf.abs(p[:, 0::2]), 2, axis=1))
+ #return K.clip(p, -self.c, self.c)
+
+ def get_config(self):
+ return {'name': self.__class__.__name__,
+ 'c': self.c}
+
+constraint = WeightClip(0.992)
+
+def new_lpcnet_plc_model(rnn_units=256, nb_used_features=20, nb_burg_features=36, batch_size=128, training=False, adaptation=False, quantize=False, cond_size=128):
+ feat = Input(shape=(None, nb_used_features+nb_burg_features), batch_size=batch_size)
+ lost = Input(shape=(None, 1), batch_size=batch_size)
+
+ fdense1 = Dense(cond_size, activation='tanh', name='plc_dense1')
+
+ cfeat = Concatenate()([feat, lost])
+ cfeat = fdense1(cfeat)
+ #cfeat = Conv1D(cond_size, 3, padding='causal', activation='tanh', name='plc_conv1')(cfeat)
+
+ quant = quant_regularizer if quantize else None
+
+ if training:
+ rnn = CuDNNGRU(rnn_units, return_sequences=True, return_state=True, name='plc_gru1', stateful=True,
+ kernel_constraint=constraint, recurrent_constraint = constraint, kernel_regularizer=quant, recurrent_regularizer=quant)
+ rnn2 = CuDNNGRU(rnn_units, return_sequences=True, return_state=True, name='plc_gru2', stateful=True,
+ kernel_constraint=constraint, recurrent_constraint = constraint, kernel_regularizer=quant, recurrent_regularizer=quant)
+ else:
+ rnn = GRU(rnn_units, return_sequences=True, return_state=True, recurrent_activation="sigmoid", reset_after='true', name='plc_gru1', stateful=True,
+ kernel_constraint=constraint, recurrent_constraint = constraint, kernel_regularizer=quant, recurrent_regularizer=quant)
+ rnn2 = GRU(rnn_units, return_sequences=True, return_state=True, recurrent_activation="sigmoid", reset_after='true', name='plc_gru2', stateful=True,
+ kernel_constraint=constraint, recurrent_constraint = constraint, kernel_regularizer=quant, recurrent_regularizer=quant)
+
+ gru_out1, _ = rnn(cfeat)
+ gru_out1 = GaussianNoise(.005)(gru_out1)
+ gru_out2, _ = rnn2(gru_out1)
+
+ out_dense = Dense(nb_used_features, activation='linear', name='plc_out')
+ plc_out = out_dense(gru_out2)
+
+ model = Model([feat, lost], plc_out)
+ model.rnn_units = rnn_units
+ model.cond_size = cond_size
+ model.nb_used_features = nb_used_features
+ model.nb_burg_features = nb_burg_features
+
+ return model
diff --git a/dnn/training_tf2/mdense.py b/dnn/training_tf2/mdense.py
new file mode 100644
index 00000000..844ae23e
--- /dev/null
+++ b/dnn/training_tf2/mdense.py
@@ -0,0 +1,95 @@
+from tensorflow.keras import backend as K
+from tensorflow.keras.layers import Layer, InputSpec
+from tensorflow.keras import activations
+from tensorflow.keras import initializers, regularizers, constraints
+import numpy as np
+import math
+
+class MDense(Layer):
+
+ def __init__(self, outputs,
+ channels=2,
+ activation=None,
+ use_bias=True,
+ kernel_initializer='glorot_uniform',
+ bias_initializer='zeros',
+ kernel_regularizer=None,
+ bias_regularizer=None,
+ activity_regularizer=None,
+ kernel_constraint=None,
+ bias_constraint=None,
+ **kwargs):
+ if 'input_shape' not in kwargs and 'input_dim' in kwargs:
+ kwargs['input_shape'] = (kwargs.pop('input_dim'),)
+ super(MDense, self).__init__(**kwargs)
+ self.units = outputs
+ self.channels = channels
+ self.activation = activations.get(activation)
+ self.use_bias = use_bias
+ self.kernel_initializer = initializers.get(kernel_initializer)
+ self.bias_initializer = initializers.get(bias_initializer)
+ self.kernel_regularizer = regularizers.get(kernel_regularizer)
+ self.bias_regularizer = regularizers.get(bias_regularizer)
+ self.activity_regularizer = regularizers.get(activity_regularizer)
+ self.kernel_constraint = constraints.get(kernel_constraint)
+ self.bias_constraint = constraints.get(bias_constraint)
+ self.input_spec = InputSpec(min_ndim=2)
+ self.supports_masking = True
+
+ def build(self, input_shape):
+ assert len(input_shape) >= 2
+ input_dim = input_shape[-1]
+
+ self.kernel = self.add_weight(shape=(self.units, input_dim, self.channels),
+ initializer=self.kernel_initializer,
+ name='kernel',
+ regularizer=self.kernel_regularizer,
+ constraint=self.kernel_constraint)
+ if self.use_bias:
+ self.bias = self.add_weight(shape=(self.units, self.channels),
+ initializer=self.bias_initializer,
+ name='bias',
+ regularizer=self.bias_regularizer,
+ constraint=self.bias_constraint)
+ else:
+ self.bias = None
+ self.factor = self.add_weight(shape=(self.units, self.channels),
+ initializer='ones',
+ name='factor',
+ regularizer=self.bias_regularizer,
+ constraint=self.bias_constraint)
+ self.input_spec = InputSpec(min_ndim=2, axes={-1: input_dim})
+ self.built = True
+
+ def call(self, inputs):
+ output = K.dot(inputs, self.kernel)
+ if self.use_bias:
+ output = output + self.bias
+ output = K.tanh(output) * self.factor
+ output = K.sum(output, axis=-1)
+ if self.activation is not None:
+ output = self.activation(output)
+ return output
+
+ def compute_output_shape(self, input_shape):
+ assert input_shape and len(input_shape) >= 2
+ assert input_shape[-1]
+ output_shape = list(input_shape)
+ output_shape[-1] = self.units
+ return tuple(output_shape)
+
+ def get_config(self):
+ config = {
+ 'units': self.units,
+ 'activation': activations.serialize(self.activation),
+ 'use_bias': self.use_bias,
+ 'kernel_initializer': initializers.serialize(self.kernel_initializer),
+ 'bias_initializer': initializers.serialize(self.bias_initializer),
+ 'kernel_regularizer': regularizers.serialize(self.kernel_regularizer),
+ 'bias_regularizer': regularizers.serialize(self.bias_regularizer),
+ 'activity_regularizer': regularizers.serialize(self.activity_regularizer),
+ 'kernel_constraint': constraints.serialize(self.kernel_constraint),
+ 'bias_constraint': constraints.serialize(self.bias_constraint)
+ }
+ base_config = super(MDense, self).get_config()
+ return dict(list(base_config.items()) + list(config.items()))
diff --git a/dnn/training_tf2/pade.py b/dnn/training_tf2/pade.py
new file mode 100644
index 00000000..f88f425c
--- /dev/null
+++ b/dnn/training_tf2/pade.py
@@ -0,0 +1,70 @@
+# Optimizing a rational function to optimize a tanh() approximation
+
+import numpy as np
+import tensorflow as tf
+from tensorflow.keras.models import Model
+from tensorflow.keras.layers import Input, GRU, Dense, Embedding, Reshape, Concatenate, Lambda, Conv1D, Multiply, Add, Bidirectional, MaxPooling1D, Activation
+import tensorflow.keras.backend as K
+from tensorflow.keras.optimizers import Adam, SGD
+
+def my_loss1(y_true, y_pred):
+ return 1*K.mean(K.square(y_true-y_pred)) + 1*K.max(K.square(y_true-y_pred), axis=1)
+
+def my_loss2(y_true, y_pred):
+ return .1*K.mean(K.square(y_true-y_pred)) + 1*K.max(K.square(y_true-y_pred), axis=1)
+
+def my_loss3(y_true, y_pred):
+ return .01*K.mean(K.square(y_true-y_pred)) + 1*K.max(K.square(y_true-y_pred), axis=1)
+
+# Using these initializers to seed the approximation
+# with a reasonable starting point
+def num_init(shape, dtype=None):
+ rr = tf.constant([[945], [105], [1]], dtype=dtype)
+ #rr = tf.constant([[946.56757], [98.01368], [0.66841]], dtype=dtype)
+ print(rr)
+ return rr
+
+def den_init(shape, dtype=None):
+ rr = tf.constant([[945], [420], [15]], dtype=dtype)
+ #rr = tf.constant([[946.604], [413.342], [12.465]], dtype=dtype)
+ print(rr)
+ return rr
+
+
+x = np.arange(-10, 10, .01)
+N = len(x)
+x = np.reshape(x, (1, -1, 1))
+x2 = x*x
+
+x2in = np.concatenate([x2*0 + 1, x2, x2*x2], axis=2)
+yout = np.tanh(x)
+
+
+model_x = Input(shape=(None, 1,))
+model_x2 = Input(shape=(None, 3,))
+
+num = Dense(1, name='num', use_bias=False, kernel_initializer=num_init)
+den = Dense(1, name='den', use_bias=False, kernel_initializer=den_init)
+
+def ratio(x):
+ return tf.minimum(1., tf.maximum(-1., x[0]*x[1]/x[2]))
+
+out_layer = Lambda(ratio)
+output = out_layer([model_x, num(model_x2), den(model_x2)])
+
+model = Model([model_x, model_x2], output)
+model.summary()
+
+model.compile(Adam(0.05, beta_1=0.9, beta_2=0.9, decay=2e-5), loss='mean_squared_error')
+model.fit([x, x2in], yout, batch_size=1, epochs=500000, validation_split=0.0)
+
+model.compile(Adam(0.001, beta_2=0.9, decay=1e-4), loss=my_loss1)
+model.fit([x, x2in], yout, batch_size=1, epochs=50000, validation_split=0.0)
+
+model.compile(Adam(0.0001, beta_2=0.9, decay=1e-4), loss=my_loss2)
+model.fit([x, x2in], yout, batch_size=1, epochs=50000, validation_split=0.0)
+
+model.compile(Adam(0.00001, beta_2=0.9, decay=1e-4), loss=my_loss3)
+model.fit([x, x2in], yout, batch_size=1, epochs=50000, validation_split=0.0)
+
+model.save_weights('tanh.h5')
diff --git a/dnn/training_tf2/parameters.py b/dnn/training_tf2/parameters.py
new file mode 100644
index 00000000..3621a4e4
--- /dev/null
+++ b/dnn/training_tf2/parameters.py
@@ -0,0 +1,29 @@
+""" module for handling extra model parameters for tf.keras models """
+
+import tensorflow as tf
+
+
+def set_parameter(model, parameter_name, parameter_value, dtype='float32'):
+ """ stores parameter_value as non-trainable weight with name parameter_name:0 """
+
+ weights = [weight for weight in model.weights if weight.name == (parameter_name + ":0")]
+
+ if len(weights) == 0:
+ model.add_weight(parameter_name, trainable=False, initializer=tf.keras.initializers.Constant(parameter_value), dtype=dtype)
+ elif len(weights) == 1:
+ weights[0].assign(parameter_value)
+ else:
+ raise ValueError(f"more than one weight starting with {parameter_name}:0 in model")
+
+
+def get_parameter(model, parameter_name, default=None):
+ """ returns parameter value if parameter is present in model and otherwise default """
+
+ weights = [weight for weight in model.weights if weight.name == (parameter_name + ":0")]
+
+ if len(weights) == 0:
+ return default
+ elif len(weights) > 1:
+ raise ValueError(f"more than one weight starting with {parameter_name}:0 in model")
+ else:
+ return weights[0].numpy().item()
diff --git a/dnn/training_tf2/plc_loader.py b/dnn/training_tf2/plc_loader.py
new file mode 100644
index 00000000..a9bd41d8
--- /dev/null
+++ b/dnn/training_tf2/plc_loader.py
@@ -0,0 +1,73 @@
+#!/usr/bin/python3
+'''Copyright (c) 2021-2022 Amazon
+
+ 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 FOUNDATION 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.
+'''
+
+import numpy as np
+from tensorflow.keras.utils import Sequence
+
+class PLCLoader(Sequence):
+ def __init__(self, features, lost, nb_burg_features, batch_size):
+ self.batch_size = batch_size
+ self.nb_batches = features.shape[0]//self.batch_size
+ self.features = features[:self.nb_batches*self.batch_size, :, :]
+ self.lost = lost.astype('float')
+ self.lost = self.lost[:(len(self.lost)//features.shape[1]-1)*features.shape[1]]
+ self.nb_burg_features = nb_burg_features
+ self.on_epoch_end()
+
+ def on_epoch_end(self):
+ self.indices = np.arange(self.nb_batches*self.batch_size)
+ np.random.shuffle(self.indices)
+ offset = np.random.randint(0, high=self.features.shape[1])
+ self.lost_offset = np.reshape(self.lost[offset:-self.features.shape[1]+offset], (-1, self.features.shape[1]))
+ self.lost_indices = np.random.randint(0, high=self.lost_offset.shape[0], size=self.nb_batches*self.batch_size)
+
+ def __getitem__(self, index):
+ features = self.features[self.indices[index*self.batch_size:(index+1)*self.batch_size], :, :]
+ burg_lost = (np.random.rand(features.shape[0], features.shape[1]) > .1).astype('float')
+ burg_lost = np.reshape(burg_lost, (features.shape[0], features.shape[1], 1))
+ burg_mask = np.tile(burg_lost, (1,1,self.nb_burg_features))
+
+ lost = self.lost_offset[self.lost_indices[index*self.batch_size:(index+1)*self.batch_size], :]
+ lost = np.reshape(lost, (features.shape[0], features.shape[1], 1))
+ lost_mask = np.tile(lost, (1,1,features.shape[2]))
+ in_features = features*lost_mask
+ in_features[:,:,:self.nb_burg_features] = in_features[:,:,:self.nb_burg_features]*burg_mask
+
+ #For the first frame after a loss, we don't have valid features, but the Burg estimate is valid.
+ #in_features[:,1:,self.nb_burg_features:] = in_features[:,1:,self.nb_burg_features:]*lost_mask[:,:-1,self.nb_burg_features:]
+ out_lost = np.copy(lost)
+ #out_lost[:,1:,:] = out_lost[:,1:,:]*out_lost[:,:-1,:]
+
+ out_features = np.concatenate([features[:,:,self.nb_burg_features:], 1.-out_lost], axis=-1)
+ burg_sign = 2*burg_lost - 1
+ # last dim is 1 for received packet, 0 for lost packet, and -1 when just the Burg info is missing
+ inputs = [in_features*lost_mask, lost*burg_sign]
+ outputs = [out_features]
+ return (inputs, outputs)
+
+ def __len__(self):
+ return self.nb_batches
diff --git a/dnn/training_tf2/rdovae.py b/dnn/training_tf2/rdovae.py
new file mode 100644
index 00000000..6240120d
--- /dev/null
+++ b/dnn/training_tf2/rdovae.py
@@ -0,0 +1,372 @@
+#!/usr/bin/python3
+'''Copyright (c) 2022 Amazon
+
+ 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 FOUNDATION 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.
+'''
+
+import math
+import tensorflow as tf
+from tensorflow.keras.models import Model
+from tensorflow.keras.layers import Input, GRU, Dense, Embedding, Reshape, Concatenate, Lambda, Conv1D, Multiply, Add, Bidirectional, MaxPooling1D, Activation, GaussianNoise, AveragePooling1D, RepeatVector
+from tensorflow.compat.v1.keras.layers import CuDNNGRU
+from tensorflow.keras import backend as K
+from tensorflow.keras.constraints import Constraint
+from tensorflow.keras.initializers import Initializer
+from tensorflow.keras.callbacks import Callback
+from tensorflow.keras.regularizers import l1
+import numpy as np
+import h5py
+from uniform_noise import UniformNoise
+
+class WeightClip(Constraint):
+ '''Clips the weights incident to each hidden unit to be inside a range
+ '''
+ def __init__(self, c=2):
+ self.c = c
+
+ def __call__(self, p):
+ # Ensure that abs of adjacent weights don't sum to more than 127. Otherwise there's a risk of
+ # saturation when implementing dot products with SSSE3 or AVX2.
+ return self.c*p/tf.maximum(self.c, tf.repeat(tf.abs(p[:, 1::2])+tf.abs(p[:, 0::2]), 2, axis=1))
+ #return K.clip(p, -self.c, self.c)
+
+ def get_config(self):
+ return {'name': self.__class__.__name__,
+ 'c': self.c}
+
+constraint = WeightClip(0.496)
+
+def soft_quantize(x):
+ #x = 4*x
+ #x = x - (.25/np.math.pi)*tf.math.sin(2*np.math.pi*x)
+ #x = x - (.25/np.math.pi)*tf.math.sin(2*np.math.pi*x)
+ #x = x - (.25/np.math.pi)*tf.math.sin(2*np.math.pi*x)
+ return x
+
+def noise_quantize(x):
+ return soft_quantize(x + (K.random_uniform((128, 16, 80))-.5) )
+
+def hard_quantize(x):
+ x = soft_quantize(x)
+ quantized = tf.round(x)
+ return x + tf.stop_gradient(quantized - x)
+
+def apply_dead_zone(x):
+ d = x[1]*.05
+ x = x[0]
+ y = x - d*tf.math.tanh(x/(.1+d))
+ return y
+
+def rate_loss(y_true,y_pred):
+ log2_e = 1.4427
+ n = y_pred.shape[-1]
+ C = n - log2_e*np.math.log(np.math.gamma(n))
+ k = K.sum(K.abs(y_pred), axis=-1)
+ p = 1.5
+ #rate = C + (n-1)*log2_e*tf.math.log((k**p + (n/5)**p)**(1/p))
+ rate = C + (n-1)*log2_e*tf.math.log(k + .112*n**2/(n/1.8+k) )
+ return K.mean(rate)
+
+eps=1e-6
+def safelog2(x):
+ log2_e = 1.4427
+ return log2_e*tf.math.log(eps+x)
+
+def feat_dist_loss(y_true,y_pred):
+ lambda_1 = 1./K.sqrt(y_pred[:,:,:,-1])
+ y_pred = y_pred[:,:,:,:-1]
+ ceps = y_pred[:,:,:,:18] - y_true[:,:,:18]
+ pitch = 2*(y_pred[:,:,:,18:19] - y_true[:,:,18:19])/(y_true[:,:,18:19] + 2)
+ corr = y_pred[:,:,:,19:] - y_true[:,:,19:]
+ pitch_weight = K.square(K.maximum(0., y_true[:,:,19:]+.5))
+ return K.mean(lambda_1*K.mean(K.square(ceps) + 10*(1/18.)*K.abs(pitch)*pitch_weight + (1/18.)*K.square(corr), axis=-1))
+
+def sq1_rate_loss(y_true,y_pred):
+ lambda_val = K.sqrt(y_pred[:,:,-1])
+ y_pred = y_pred[:,:,:-1]
+ log2_e = 1.4427
+ n = y_pred.shape[-1]//3
+ r = (y_pred[:,:,2*n:])
+ p0 = (y_pred[:,:,n:2*n])
+ p0 = 1-r**(.5+.5*p0)
+ y_pred = y_pred[:,:,:n]
+ y_pred = soft_quantize(y_pred)
+
+ y0 = K.maximum(0., 1. - K.abs(y_pred))**2
+ rate = -y0*safelog2(p0*r**K.abs(y_pred)) - (1-y0)*safelog2(.5*(1-p0)*(1-r)*r**(K.abs(y_pred)-1))
+ rate = -safelog2(-.5*tf.math.log(r)*r**K.abs(y_pred))
+ rate = -safelog2((1-r)/(1+r)*r**K.abs(y_pred))
+ #rate = -safelog2(- tf.math.sinh(.5*tf.math.log(r))* r**K.abs(y_pred) - tf.math.cosh(K.maximum(0., .5 - K.abs(y_pred))*tf.math.log(r)) + 1)
+ rate = lambda_val*K.sum(rate, axis=-1)
+ return K.mean(rate)
+
+def sq2_rate_loss(y_true,y_pred):
+ lambda_val = K.sqrt(y_pred[:,:,-1])
+ y_pred = y_pred[:,:,:-1]
+ log2_e = 1.4427
+ n = y_pred.shape[-1]//3
+ r = y_pred[:,:,2*n:]
+ p0 = y_pred[:,:,n:2*n]
+ p0 = 1-r**(.5+.5*p0)
+ #theta = K.minimum(1., .5 + 0*p0 - 0.04*tf.math.log(r))
+ #p0 = 1-r**theta
+ y_pred = tf.round(y_pred[:,:,:n])
+ y0 = K.maximum(0., 1. - K.abs(y_pred))**2
+ rate = -y0*safelog2(p0*r**K.abs(y_pred)) - (1-y0)*safelog2(.5*(1-p0)*(1-r)*r**(K.abs(y_pred)-1))
+ rate = lambda_val*K.sum(rate, axis=-1)
+ return K.mean(rate)
+
+def sq_rate_metric(y_true,y_pred, reduce=True):
+ y_pred = y_pred[:,:,:-1]
+ log2_e = 1.4427
+ n = y_pred.shape[-1]//3
+ r = y_pred[:,:,2*n:]
+ p0 = y_pred[:,:,n:2*n]
+ p0 = 1-r**(.5+.5*p0)
+ #theta = K.minimum(1., .5 + 0*p0 - 0.04*tf.math.log(r))
+ #p0 = 1-r**theta
+ y_pred = tf.round(y_pred[:,:,:n])
+ y0 = K.maximum(0., 1. - K.abs(y_pred))**2
+ rate = -y0*safelog2(p0*r**K.abs(y_pred)) - (1-y0)*safelog2(.5*(1-p0)*(1-r)*r**(K.abs(y_pred)-1))
+ rate = K.sum(rate, axis=-1)
+ if reduce:
+ rate = K.mean(rate)
+ return rate
+
+def pvq_quant_search(x, k):
+ x = x/tf.reduce_sum(tf.abs(x), axis=-1, keepdims=True)
+ kx = k*x
+ y = tf.round(kx)
+ newk = k
+
+ for j in range(10):
+ #print("y = ", y)
+ #print("iteration ", j)
+ abs_y = tf.abs(y)
+ abs_kx = tf.abs(kx)
+ kk=tf.reduce_sum(abs_y, axis=-1)
+ #print("sums = ", kk)
+ plus = 1.000001*tf.reduce_min((abs_y+.5)/(abs_kx+1e-15), axis=-1)
+ minus = .999999*tf.reduce_max((abs_y-.5)/(abs_kx+1e-15), axis=-1)
+ #print("plus = ", plus)
+ #print("minus = ", minus)
+ factor = tf.where(kk>k, minus, plus)
+ factor = tf.where(kk==k, tf.ones_like(factor), factor)
+ #print("scale = ", factor)
+ factor = tf.expand_dims(factor, axis=-1)
+ #newk = newk * (k/kk)**.2
+ newk = newk*factor
+ kx = newk*x
+ #print("newk = ", newk)
+ #print("unquantized = ", newk*x)
+ y = tf.round(kx)
+
+ #print(y)
+ #print(K.mean(K.sum(K.abs(y), axis=-1)))
+ return y
+
+def pvq_quantize(x, k):
+ x = x/(1e-15+tf.norm(x, axis=-1,keepdims=True))
+ quantized = pvq_quant_search(x, k)
+ quantized = quantized/(1e-15+tf.norm(quantized, axis=-1,keepdims=True))
+ return x + tf.stop_gradient(quantized - x)
+
+
+def var_repeat(x):
+ return tf.repeat(tf.expand_dims(x[0], 1), K.shape(x[1])[1], axis=1)
+
+nb_state_dim = 24
+
+def new_rdovae_encoder(nb_used_features=20, nb_bits=17, bunch=4, nb_quant=40, batch_size=128, cond_size=128, cond_size2=256, training=False):
+ feat = Input(shape=(None, nb_used_features), batch_size=batch_size)
+
+ gru = CuDNNGRU if training else GRU
+ enc_dense1 = Dense(cond_size2, activation='tanh', kernel_constraint=constraint, name='enc_dense1')
+ enc_dense2 = gru(cond_size, return_sequences=True, kernel_constraint=constraint, recurrent_constraint=constraint, name='enc_dense2')
+ enc_dense3 = Dense(cond_size2, activation='tanh', kernel_constraint=constraint, name='enc_dense3')
+ enc_dense4 = gru(cond_size, return_sequences=True, kernel_constraint=constraint, recurrent_constraint=constraint, name='enc_dense4')
+ enc_dense5 = Dense(cond_size2, activation='tanh', kernel_constraint=constraint, name='enc_dense5')
+ enc_dense6 = gru(cond_size, return_sequences=True, kernel_constraint=constraint, recurrent_constraint=constraint, name='enc_dense6')
+ enc_dense7 = Dense(cond_size, activation='tanh', kernel_constraint=constraint, name='enc_dense7')
+ enc_dense8 = Dense(cond_size, activation='tanh', kernel_constraint=constraint, name='enc_dense8')
+
+ #bits_dense = Dense(nb_bits, activation='linear', name='bits_dense')
+ bits_dense = Conv1D(nb_bits, 4, padding='causal', activation='linear', name='bits_dense')
+
+ zero_out = Lambda(lambda x: 0*x)
+ inputs = Reshape((-1, 2*nb_used_features))(feat)
+ d1 = enc_dense1(inputs)
+ d2 = enc_dense2(d1)
+ d3 = enc_dense3(d2)
+ d4 = enc_dense4(d3)
+ d5 = enc_dense5(d4)
+ d6 = enc_dense6(d5)
+ d7 = enc_dense7(d6)
+ d8 = enc_dense8(d7)
+ pre_out = Concatenate()([d1, d2, d3, d4, d5, d6, d7, d8])
+ enc_out = bits_dense(pre_out)
+ global_dense1 = Dense(128, activation='tanh', name='gdense1')
+ global_dense2 = Dense(nb_state_dim, activation='tanh', name='gdense2')
+ global_bits = global_dense2(global_dense1(pre_out))
+
+ encoder = Model([feat], [enc_out, global_bits], name='encoder')
+ return encoder
+
+def new_rdovae_decoder(nb_used_features=20, nb_bits=17, bunch=4, nb_quant=40, batch_size=128, cond_size=128, cond_size2=256, training=False):
+ bits_input = Input(shape=(None, nb_bits), batch_size=batch_size, name="dec_bits")
+ gru_state_input = Input(shape=(nb_state_dim,), batch_size=batch_size, name="dec_state")
+
+
+ gru = CuDNNGRU if training else GRU
+ dec_dense1 = Dense(cond_size2, activation='tanh', kernel_constraint=constraint, name='dec_dense1')
+ dec_dense2 = gru(cond_size, return_sequences=True, kernel_constraint=constraint, recurrent_constraint=constraint, name='dec_dense2')
+ dec_dense3 = Dense(cond_size2, activation='tanh', kernel_constraint=constraint, name='dec_dense3')
+ dec_dense4 = gru(cond_size, return_sequences=True, kernel_constraint=constraint, recurrent_constraint=constraint, name='dec_dense4')
+ dec_dense5 = Dense(cond_size2, activation='tanh', kernel_constraint=constraint, name='dec_dense5')
+ dec_dense6 = gru(cond_size, return_sequences=True, kernel_constraint=constraint, recurrent_constraint=constraint, name='dec_dense6')
+ dec_dense7 = Dense(cond_size, activation='tanh', kernel_constraint=constraint, name='dec_dense7')
+ dec_dense8 = Dense(cond_size, activation='tanh', kernel_constraint=constraint, name='dec_dense8')
+
+ dec_final = Dense(bunch*nb_used_features, activation='linear', name='dec_final')
+
+ time_reverse = Lambda(lambda x: K.reverse(x, 1))
+ #time_reverse = Lambda(lambda x: x)
+ #gru_state_rep = RepeatVector(64//bunch)(gru_state_input)
+
+ #gru_state_rep = Lambda(var_repeat, output_shape=(None, nb_state_dim)) ([gru_state_input, bits_input])
+ gru_state1 = Dense(cond_size, name="state1", activation='tanh')(gru_state_input)
+ gru_state2 = Dense(cond_size, name="state2", activation='tanh')(gru_state_input)
+ gru_state3 = Dense(cond_size, name="state3", activation='tanh')(gru_state_input)
+
+ dec1 = dec_dense1(time_reverse(bits_input))
+ dec2 = dec_dense2(dec1, initial_state=gru_state1)
+ dec3 = dec_dense3(dec2)
+ dec4 = dec_dense4(dec3, initial_state=gru_state2)
+ dec5 = dec_dense5(dec4)
+ dec6 = dec_dense6(dec5, initial_state=gru_state3)
+ dec7 = dec_dense7(dec6)
+ dec8 = dec_dense8(dec7)
+ output = Reshape((-1, nb_used_features))(dec_final(Concatenate()([dec1, dec2, dec3, dec4, dec5, dec6, dec7, dec8])))
+ decoder = Model([bits_input, gru_state_input], time_reverse(output), name='decoder')
+ decoder.nb_bits = nb_bits
+ decoder.bunch = bunch
+ return decoder
+
+def new_split_decoder(decoder):
+ nb_bits = decoder.nb_bits
+ bunch = decoder.bunch
+ bits_input = Input(shape=(None, nb_bits), name="split_bits")
+ gru_state_input = Input(shape=(None,nb_state_dim), name="split_state")
+
+ range_select = Lambda(lambda x: x[0][:,x[1]:x[2],:])
+ elem_select = Lambda(lambda x: x[0][:,x[1],:])
+ points = [0, 100, 200, 300, 400]
+ outputs = []
+ for i in range(len(points)-1):
+ begin = points[i]//bunch
+ end = points[i+1]//bunch
+ state = elem_select([gru_state_input, end-1])
+ bits = range_select([bits_input, begin, end])
+ outputs.append(decoder([bits, state]))
+ output = Concatenate(axis=1)(outputs)
+ split = Model([bits_input, gru_state_input], output, name="split")
+ return split
+
+def tensor_concat(x):
+ #n = x[1]//2
+ #x = x[0]
+ n=2
+ y = []
+ for i in range(n-1):
+ offset = 2 * (n-1-i)
+ tmp = K.concatenate([x[i][:, offset:, :], x[-1][:, -offset:, :]], axis=-2)
+ y.append(tf.expand_dims(tmp, axis=0))
+ y.append(tf.expand_dims(x[-1], axis=0))
+ return Concatenate(axis=0)(y)
+
+
+def new_rdovae_model(nb_used_features=20, nb_bits=17, bunch=4, nb_quant=40, batch_size=128, cond_size=128, cond_size2=256, training=False):
+
+ feat = Input(shape=(None, nb_used_features), batch_size=batch_size)
+ quant_id = Input(shape=(None,), batch_size=batch_size)
+ lambda_val = Input(shape=(None, 1), batch_size=batch_size)
+ lambda_bunched = AveragePooling1D(pool_size=bunch//2, strides=bunch//2, padding="valid")(lambda_val)
+ lambda_up = Lambda(lambda x: K.repeat_elements(x, 2, axis=-2))(lambda_val)
+
+ qembedding = Embedding(nb_quant, 6*nb_bits, name='quant_embed', embeddings_initializer='zeros')
+ quant_embed_dec = qembedding(quant_id)
+ quant_scale = Activation('softplus')(Lambda(lambda x: x[:,:,:nb_bits], name='quant_scale_embed')(quant_embed_dec))
+
+ encoder = new_rdovae_encoder(nb_used_features, nb_bits, bunch, nb_quant, batch_size, cond_size, cond_size2, training=training)
+ ze, gru_state_dec = encoder([feat])
+ ze = Multiply()([ze, quant_scale])
+
+ decoder = new_rdovae_decoder(nb_used_features, nb_bits, bunch, nb_quant, batch_size, cond_size, cond_size2, training=training)
+ split_decoder = new_split_decoder(decoder)
+
+ dead_zone = Activation('softplus')(Lambda(lambda x: x[:,:,nb_bits:2*nb_bits], name='dead_zone_embed')(quant_embed_dec))
+ soft_distr_embed = Activation('sigmoid')(Lambda(lambda x: x[:,:,2*nb_bits:4*nb_bits], name='soft_distr_embed')(quant_embed_dec))
+ hard_distr_embed = Activation('sigmoid')(Lambda(lambda x: x[:,:,4*nb_bits:], name='hard_distr_embed')(quant_embed_dec))
+
+ noisequant = UniformNoise()
+ hardquant = Lambda(hard_quantize)
+ dzone = Lambda(apply_dead_zone)
+ dze = dzone([ze,dead_zone])
+ ndze = noisequant(dze)
+ dze_quant = hardquant(dze)
+
+ div = Lambda(lambda x: x[0]/x[1])
+ dze_quant = div([dze_quant,quant_scale])
+ ndze_unquant = div([ndze,quant_scale])
+
+ mod_select = Lambda(lambda x: x[0][:,x[1]::bunch//2,:])
+ gru_state_dec = Lambda(lambda x: pvq_quantize(x, 82))(gru_state_dec)
+ combined_output = []
+ unquantized_output = []
+ cat = Concatenate(name="out_cat")
+ for i in range(bunch//2):
+ dze_select = mod_select([dze_quant, i])
+ ndze_select = mod_select([ndze_unquant, i])
+ state_select = mod_select([gru_state_dec, i])
+
+ tmp = split_decoder([dze_select, state_select])
+ tmp = cat([tmp, lambda_up])
+ combined_output.append(tmp)
+
+ tmp = split_decoder([ndze_select, state_select])
+ tmp = cat([tmp, lambda_up])
+ unquantized_output.append(tmp)
+
+ concat = Lambda(tensor_concat, name="output")
+ combined_output = concat(combined_output)
+ unquantized_output = concat(unquantized_output)
+
+ e2 = Concatenate(name="hard_bits")([dze, hard_distr_embed, lambda_val])
+ e = Concatenate(name="soft_bits")([dze, soft_distr_embed, lambda_val])
+
+
+ model = Model([feat, quant_id, lambda_val], [combined_output, unquantized_output, e, e2], name="end2end")
+ model.nb_used_features = nb_used_features
+
+ return model, encoder, decoder, qembedding
diff --git a/dnn/training_tf2/rdovae_exchange.py b/dnn/training_tf2/rdovae_exchange.py
new file mode 100644
index 00000000..3249677d
--- /dev/null
+++ b/dnn/training_tf2/rdovae_exchange.py
@@ -0,0 +1,138 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+
+import argparse
+import os
+import sys
+
+os.environ['CUDA_VISIBLE_DEVICES'] = ""
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('weights', metavar="<weight file>", type=str, help='model weight file in hdf5 format')
+parser.add_argument('output', metavar="<output folder>", type=str, help='output exchange folder')
+parser.add_argument('--cond-size', type=int, help="conditioning size (default: 256)", default=256)
+parser.add_argument('--latent-dim', type=int, help="dimension of latent space (default: 80)", default=80)
+parser.add_argument('--quant-levels', type=int, help="number of quantization steps (default: 16)", default=16)
+
+args = parser.parse_args()
+
+# now import the heavy stuff
+from rdovae import new_rdovae_model
+from wexchange.tf import dump_tf_weights, load_tf_weights
+
+
+exchange_name = {
+ 'enc_dense1' : 'encoder_stack_layer1_dense',
+ 'enc_dense3' : 'encoder_stack_layer3_dense',
+ 'enc_dense5' : 'encoder_stack_layer5_dense',
+ 'enc_dense7' : 'encoder_stack_layer7_dense',
+ 'enc_dense8' : 'encoder_stack_layer8_dense',
+ 'gdense1' : 'encoder_state_layer1_dense',
+ 'gdense2' : 'encoder_state_layer2_dense',
+ 'enc_dense2' : 'encoder_stack_layer2_gru',
+ 'enc_dense4' : 'encoder_stack_layer4_gru',
+ 'enc_dense6' : 'encoder_stack_layer6_gru',
+ 'bits_dense' : 'encoder_stack_layer9_conv',
+ 'qembedding' : 'statistical_model_embedding',
+ 'state1' : 'decoder_state1_dense',
+ 'state2' : 'decoder_state2_dense',
+ 'state3' : 'decoder_state3_dense',
+ 'dec_dense1' : 'decoder_stack_layer1_dense',
+ 'dec_dense3' : 'decoder_stack_layer3_dense',
+ 'dec_dense5' : 'decoder_stack_layer5_dense',
+ 'dec_dense7' : 'decoder_stack_layer7_dense',
+ 'dec_dense8' : 'decoder_stack_layer8_dense',
+ 'dec_final' : 'decoder_stack_layer9_dense',
+ 'dec_dense2' : 'decoder_stack_layer2_gru',
+ 'dec_dense4' : 'decoder_stack_layer4_gru',
+ 'dec_dense6' : 'decoder_stack_layer6_gru'
+}
+
+
+if __name__ == "__main__":
+
+ model, encoder, decoder, qembedding = new_rdovae_model(20, args.latent_dim, cond_size=args.cond_size, nb_quant=args.quant_levels)
+ model.load_weights(args.weights)
+
+ os.makedirs(args.output, exist_ok=True)
+
+ # encoder
+ encoder_dense_names = [
+ 'enc_dense1',
+ 'enc_dense3',
+ 'enc_dense5',
+ 'enc_dense7',
+ 'enc_dense8',
+ 'gdense1',
+ 'gdense2'
+ ]
+
+ encoder_gru_names = [
+ 'enc_dense2',
+ 'enc_dense4',
+ 'enc_dense6'
+ ]
+
+ encoder_conv1d_names = [
+ 'bits_dense'
+ ]
+
+
+ for name in encoder_dense_names + encoder_gru_names + encoder_conv1d_names:
+ print(f"writing layer {exchange_name[name]}...")
+ dump_tf_weights(os.path.join(args.output, exchange_name[name]), encoder.get_layer(name))
+
+ # qembedding
+ print(f"writing layer {exchange_name['qembedding']}...")
+ dump_tf_weights(os.path.join(args.output, exchange_name['qembedding']), qembedding)
+
+ # decoder
+ decoder_dense_names = [
+ 'state1',
+ 'state2',
+ 'state3',
+ 'dec_dense1',
+ 'dec_dense3',
+ 'dec_dense5',
+ 'dec_dense7',
+ 'dec_dense8',
+ 'dec_final'
+ ]
+
+ decoder_gru_names = [
+ 'dec_dense2',
+ 'dec_dense4',
+ 'dec_dense6'
+ ]
+
+ for name in decoder_dense_names + decoder_gru_names:
+ print(f"writing layer {exchange_name[name]}...")
+ dump_tf_weights(os.path.join(args.output, exchange_name[name]), decoder.get_layer(name))
diff --git a/dnn/training_tf2/rdovae_import.py b/dnn/training_tf2/rdovae_import.py
new file mode 100644
index 00000000..bc8b460d
--- /dev/null
+++ b/dnn/training_tf2/rdovae_import.py
@@ -0,0 +1,123 @@
+"""
+/* Copyright (c) 2022 Amazon
+ Written by Jan Buethe */
+/*
+ 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 OWNER
+ 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.
+*/
+"""
+
+
+import argparse
+import os
+import sys
+
+os.environ['CUDA_VISIBLE_DEVICES'] = ""
+
+parser = argparse.ArgumentParser()
+
+parser.add_argument('input', metavar="<input folder>", type=str, help='input exchange folder')
+parser.add_argument('weights', metavar="<weight file>", type=str, help='model weight file in hdf5 format')
+parser.add_argument('--cond-size', type=int, help="conditioning size (default: 256)", default=256)
+parser.add_argument('--latent-dim', type=int, help="dimension of latent space (default: 80)", default=80)
+parser.add_argument('--quant-levels', type=int, help="number of quantization steps (default: 16)", default=16)
+
+args = parser.parse_args()
+
+# now import the heavy stuff
+from rdovae import new_rdovae_model
+from wexchange.tf import load_tf_weights
+
+
+exchange_name = {
+ 'enc_dense1' : 'encoder_stack_layer1_dense',
+ 'enc_dense3' : 'encoder_stack_layer3_dense',
+ 'enc_dense5' : 'encoder_stack_layer5_dense',
+ 'enc_dense7' : 'encoder_stack_layer7_dense',
+ 'enc_dense8' : 'encoder_stack_layer8_dense',
+ 'gdense1' : 'encoder_state_layer1_dense',
+ 'gdense2' : 'encoder_state_layer2_dense',
+ 'enc_dense2' : 'encoder_stack_layer2_gru',
+ 'enc_dense4' : 'encoder_stack_layer4_gru',
+ 'enc_dense6' : 'encoder_stack_layer6_gru',
+ 'bits_dense' : 'encoder_stack_layer9_conv',
+ 'qembedding' : 'statistical_model_embedding',
+ 'state1' : 'decoder_state1_dense',
+ 'state2' : 'decoder_state2_dense',
+ 'state3' : 'decoder_state3_dense',
+ 'dec_dense1' : 'decoder_stack_layer1_dense',
+ 'dec_dense3' : 'decoder_stack_layer3_dense',
+ 'dec_dense5' : 'decoder_stack_layer5_dense',
+ 'dec_dense7' : 'decoder_stack_layer7_dense',
+ 'dec_dense8' : 'decoder_stack_layer8_dense',
+ 'dec_final' : 'decoder_stack_layer9_dense',
+ 'dec_dense2' : 'decoder_stack_layer2_gru',
+ 'dec_dense4' : 'decoder_stack_layer4_gru',
+ 'dec_dense6' : 'decoder_stack_layer6_gru'
+}
+
+if __name__ == "__main__":
+
+ model, encoder, decoder, qembedding = new_rdovae_model(20, args.latent_dim, cond_size=args.cond_size, nb_quant=args.quant_levels)
+
+ encoder_layers = [
+ 'enc_dense1',
+ 'enc_dense3',
+ 'enc_dense5',
+ 'enc_dense7',
+ 'enc_dense8',
+ 'gdense1',
+ 'gdense2',
+ 'enc_dense2',
+ 'enc_dense4',
+ 'enc_dense6',
+ 'bits_dense'
+ ]
+
+ decoder_layers = [
+ 'state1',
+ 'state2',
+ 'state3',
+ 'dec_dense1',
+ 'dec_dense3',
+ 'dec_dense5',
+ 'dec_dense7',
+ 'dec_dense8',
+ 'dec_final',
+ 'dec_dense2',
+ 'dec_dense4',
+ 'dec_dense6'
+ ]
+
+ for name in encoder_layers:
+ print(f"loading weight for layer {name}...")
+ load_tf_weights(os.path.join(args.input, exchange_name[name]), encoder.get_layer(name))
+
+ print(f"loading weight for layer qembedding...")
+ load_tf_weights(os.path.join(args.input, exchange_name['qembedding']), qembedding)
+
+ for name in decoder_layers:
+ print(f"loading weight for layer {name}...")
+ load_tf_weights(os.path.join(args.input, exchange_name[name]), decoder.get_layer(name))
+
+ model.save(args.weights)
diff --git a/dnn/training_tf2/test_lpcnet.py b/dnn/training_tf2/test_lpcnet.py
new file mode 100755
index 00000000..ca551e63
--- /dev/null
+++ b/dnn/training_tf2/test_lpcnet.py
@@ -0,0 +1,120 @@
+#!/usr/bin/python3
+'''Copyright (c) 2018 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+import argparse
+import sys
+
+import h5py
+import numpy as np
+
+import lpcnet
+from ulaw import ulaw2lin, lin2ulaw
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('model-file', type=str, help='model weight h5 file')
+parser.add_argument('--lpc-gamma', type=float, help='LPC weighting factor. WARNING: giving an inconsistent value here will severely degrade performance', default=1)
+
+args = parser.parse_args()
+
+filename = args.model_file
+with h5py.File(filename, "r") as f:
+ units = min(f['model_weights']['gru_a']['gru_a']['recurrent_kernel:0'].shape)
+ units2 = min(f['model_weights']['gru_b']['gru_b']['recurrent_kernel:0'].shape)
+ cond_size = min(f['model_weights']['feature_dense1']['feature_dense1']['kernel:0'].shape)
+ e2e = 'rc2lpc' in f['model_weights']
+
+
+model, enc, dec = lpcnet.new_lpcnet_model(training = False, rnn_units1=units, rnn_units2=units2, flag_e2e = e2e, cond_size=cond_size, batch_size=1)
+
+model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['sparse_categorical_accuracy'])
+#model.summary()
+
+
+feature_file = sys.argv[2]
+out_file = sys.argv[3]
+frame_size = model.frame_size
+nb_features = 36
+nb_used_features = model.nb_used_features
+
+features = np.fromfile(feature_file, dtype='float32')
+features = np.resize(features, (-1, nb_features))
+nb_frames = 1
+feature_chunk_size = features.shape[0]
+pcm_chunk_size = frame_size*feature_chunk_size
+
+features = np.reshape(features, (nb_frames, feature_chunk_size, nb_features))
+periods = (.1 + 50*features[:,:,18:19]+100).astype('int16')
+
+
+
+model.load_weights(filename);
+
+order = 16
+
+pcm = np.zeros((nb_frames*pcm_chunk_size, ))
+fexc = np.zeros((1, 1, 3), dtype='int16')+128
+state1 = np.zeros((1, model.rnn_units1), dtype='float32')
+state2 = np.zeros((1, model.rnn_units2), dtype='float32')
+
+mem = 0
+coef = 0.85
+
+lpc_weights = np.array([args.lpc_gamma ** (i + 1) for i in range(16)])
+
+fout = open(out_file, 'wb')
+
+skip = order + 1
+for c in range(0, nb_frames):
+ if not e2e:
+ cfeat = enc.predict([features[c:c+1, :, :nb_used_features], periods[c:c+1, :, :]])
+ else:
+ cfeat,lpcs = enc.predict([features[c:c+1, :, :nb_used_features], periods[c:c+1, :, :]])
+ for fr in range(0, feature_chunk_size):
+ f = c*feature_chunk_size + fr
+ if not e2e:
+ a = features[c, fr, nb_features-order:] * lpc_weights
+ else:
+ a = lpcs[c,fr]
+ for i in range(skip, frame_size):
+ pred = -sum(a*pcm[f*frame_size + i - 1:f*frame_size + i - order-1:-1])
+ fexc[0, 0, 1] = lin2ulaw(pred)
+
+ p, state1, state2 = dec.predict([fexc, cfeat[:, fr:fr+1, :], state1, state2])
+ #Lower the temperature for voiced frames to reduce noisiness
+ p *= np.power(p, np.maximum(0, 1.5*features[c, fr, 19] - .5))
+ p = p/(1e-18 + np.sum(p))
+ #Cut off the tail of the remaining distribution
+ p = np.maximum(p-0.002, 0).astype('float64')
+ p = p/(1e-8 + np.sum(p))
+
+ fexc[0, 0, 2] = np.argmax(np.random.multinomial(1, p[0,0,:], 1))
+ pcm[f*frame_size + i] = pred + ulaw2lin(fexc[0, 0, 2])
+ fexc[0, 0, 0] = lin2ulaw(pcm[f*frame_size + i])
+ mem = coef*mem + pcm[f*frame_size + i]
+ #print(mem)
+ np.array([np.round(mem)], dtype='int16').tofile(fout)
+ skip = 0
diff --git a/dnn/training_tf2/test_plc.py b/dnn/training_tf2/test_plc.py
new file mode 100644
index 00000000..0c0ac25f
--- /dev/null
+++ b/dnn/training_tf2/test_plc.py
@@ -0,0 +1,92 @@
+#!/usr/bin/python3
+'''Copyright (c) 2021-2022 Amazon
+ Copyright (c) 2018-2019 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+# Train an LPCNet model
+
+import argparse
+from plc_loader import PLCLoader
+
+parser = argparse.ArgumentParser(description='Test a PLC model')
+
+parser.add_argument('weights', metavar='<weights file>', help='weights file (.h5)')
+parser.add_argument('features', metavar='<features file>', help='binary features file (float32)')
+parser.add_argument('output', metavar='<output>', help='reconstructed file (float32)')
+parser.add_argument('--model', metavar='<model>', default='lpcnet_plc', help='PLC model python definition (without .py)')
+group1 = parser.add_mutually_exclusive_group()
+
+parser.add_argument('--gru-size', metavar='<units>', default=256, type=int, help='number of units in GRU (default 256)')
+parser.add_argument('--cond-size', metavar='<units>', default=128, type=int, help='number of units in conditioning network (default 128)')
+
+
+args = parser.parse_args()
+
+import importlib
+lpcnet = importlib.import_module(args.model)
+
+import sys
+import numpy as np
+from tensorflow.keras.optimizers import Adam
+from tensorflow.keras.callbacks import ModelCheckpoint, CSVLogger
+import tensorflow.keras.backend as K
+import h5py
+
+import tensorflow as tf
+#gpus = tf.config.experimental.list_physical_devices('GPU')
+#if gpus:
+# try:
+# tf.config.experimental.set_virtual_device_configuration(gpus[0], [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=5120)])
+# except RuntimeError as e:
+# print(e)
+
+model = lpcnet.new_lpcnet_plc_model(rnn_units=args.gru_size, batch_size=1, training=False, quantize=False, cond_size=args.cond_size)
+model.compile()
+
+lpc_order = 16
+
+feature_file = args.features
+nb_features = model.nb_used_features + lpc_order
+nb_used_features = model.nb_used_features
+
+# u for unquantised, load 16 bit PCM samples and convert to mu-law
+
+features = np.loadtxt(feature_file)
+print(features.shape)
+sequence_size = features.shape[0]
+lost = np.reshape(features[:,-1:], (1, sequence_size, 1))
+features = features[:,:nb_used_features]
+features = np.reshape(features, (1, sequence_size, nb_used_features))
+
+
+model.load_weights(args.weights)
+
+features = features*lost
+out = model.predict([features, lost])
+
+out = features + (1-lost)*out
+
+np.savetxt(args.output, out[0,:,:])
diff --git a/dnn/training_tf2/tf_funcs.py b/dnn/training_tf2/tf_funcs.py
new file mode 100644
index 00000000..b86f075c
--- /dev/null
+++ b/dnn/training_tf2/tf_funcs.py
@@ -0,0 +1,70 @@
+"""
+Tensorflow/Keras helper functions to do the following:
+ 1. \mu law <-> Linear domain conversion
+ 2. Differentiable prediction from the input signal and LP coefficients
+ 3. Differentiable transformations Reflection Coefficients (RCs) <-> LP Coefficients
+"""
+from tensorflow.keras.layers import Lambda, Multiply, Layer, Concatenate
+from tensorflow.keras import backend as K
+import tensorflow as tf
+
+# \mu law <-> Linear conversion functions
+scale = 255.0/32768.0
+scale_1 = 32768.0/255.0
+def tf_l2u(x):
+ s = K.sign(x)
+ x = K.abs(x)
+ u = (s*(128*K.log(1+scale*x)/K.log(256.0)))
+ u = K.clip(128 + u, 0, 255)
+ return u
+
+def tf_u2l(u):
+ u = tf.cast(u,"float32")
+ u = u - 128.0
+ s = K.sign(u)
+ u = K.abs(u)
+ return s*scale_1*(K.exp(u/128.*K.log(256.0))-1)
+
+# Differentiable Prediction Layer
+# Computes the LP prediction from the input lag signal and the LP coefficients
+# The inputs xt and lpc conform with the shapes in lpcnet.py (the '2400' is coded keeping this in mind)
+class diff_pred(Layer):
+ def call(self, inputs, lpcoeffs_N = 16, frame_size = 160):
+ xt = inputs[0]
+ lpc = inputs[1]
+
+ rept = Lambda(lambda x: K.repeat_elements(x , frame_size, 1))
+ zpX = Lambda(lambda x: K.concatenate([0*x[:,0:lpcoeffs_N,:], x],axis = 1))
+ cX = Lambda(lambda x: K.concatenate([x[:,(lpcoeffs_N - i):(lpcoeffs_N - i + 2400),:] for i in range(lpcoeffs_N)],axis = 2))
+
+ pred = -Multiply()([rept(lpc),cX(zpX(xt))])
+
+ return K.sum(pred,axis = 2,keepdims = True)
+
+# Differentiable Transformations (RC <-> LPC) computed using the Levinson Durbin Recursion
+class diff_rc2lpc(Layer):
+ def call(self, inputs, lpcoeffs_N = 16):
+ def pred_lpc_recursive(input):
+ temp = (input[0] + K.repeat_elements(input[1],input[0].shape[2],2)*K.reverse(input[0],axes = 2))
+ temp = Concatenate(axis = 2)([temp,input[1]])
+ return temp
+ Llpc = Lambda(pred_lpc_recursive)
+ inputs = inputs[:,:,:lpcoeffs_N]
+ lpc_init = inputs
+ for i in range(1,lpcoeffs_N):
+ lpc_init = Llpc([lpc_init[:,:,:i],K.expand_dims(inputs[:,:,i],axis = -1)])
+ return lpc_init
+
+class diff_lpc2rc(Layer):
+ def call(self, inputs, lpcoeffs_N = 16):
+ def pred_rc_recursive(input):
+ ki = K.repeat_elements(K.expand_dims(input[1][:,:,0],axis = -1),input[0].shape[2],2)
+ temp = (input[0] - ki*K.reverse(input[0],axes = 2))/(1 - ki*ki)
+ temp = Concatenate(axis = 2)([temp,input[1]])
+ return temp
+ Lrc = Lambda(pred_rc_recursive)
+ rc_init = inputs
+ for i in range(1,lpcoeffs_N):
+ j = (lpcoeffs_N - i + 1)
+ rc_init = Lrc([rc_init[:,:,:(j - 1)],rc_init[:,:,(j - 1):]])
+ return rc_init
diff --git a/dnn/training_tf2/train_lpcnet.py b/dnn/training_tf2/train_lpcnet.py
new file mode 100755
index 00000000..60e2b56f
--- /dev/null
+++ b/dnn/training_tf2/train_lpcnet.py
@@ -0,0 +1,214 @@
+#!/usr/bin/python3
+'''Copyright (c) 2018 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+# Train an LPCNet model
+
+import argparse
+import os
+
+from dataloader import LPCNetLoader
+
+parser = argparse.ArgumentParser(description='Train an LPCNet model')
+
+parser.add_argument('features', metavar='<features file>', help='binary features file (float32)')
+parser.add_argument('data', metavar='<audio data file>', help='binary audio data file (uint8)')
+parser.add_argument('output', metavar='<output>', help='trained model file (.h5)')
+parser.add_argument('--model', metavar='<model>', default='lpcnet', help='LPCNet model python definition (without .py)')
+group1 = parser.add_mutually_exclusive_group()
+group1.add_argument('--quantize', metavar='<input weights>', help='quantize model')
+group1.add_argument('--retrain', metavar='<input weights>', help='continue training model')
+parser.add_argument('--density', metavar='<global density>', type=float, help='average density of the recurrent weights (default 0.1)')
+parser.add_argument('--density-split', nargs=3, metavar=('<update>', '<reset>', '<state>'), type=float, help='density of each recurrent gate (default 0.05, 0.05, 0.2)')
+parser.add_argument('--grub-density', metavar='<global GRU B density>', type=float, help='average density of the recurrent weights (default 1.0)')
+parser.add_argument('--grub-density-split', nargs=3, metavar=('<update>', '<reset>', '<state>'), type=float, help='density of each GRU B input gate (default 1.0, 1.0, 1.0)')
+parser.add_argument('--grua-size', metavar='<units>', default=384, type=int, help='number of units in GRU A (default 384)')
+parser.add_argument('--grub-size', metavar='<units>', default=16, type=int, help='number of units in GRU B (default 16)')
+parser.add_argument('--cond-size', metavar='<units>', default=128, type=int, help='number of units in conditioning network, aka frame rate network (default 128)')
+parser.add_argument('--epochs', metavar='<epochs>', default=120, type=int, help='number of epochs to train for (default 120)')
+parser.add_argument('--batch-size', metavar='<batch size>', default=128, type=int, help='batch size to use (default 128)')
+parser.add_argument('--end2end', dest='flag_e2e', action='store_true', help='Enable end-to-end training (with differentiable LPC computation')
+parser.add_argument('--lr', metavar='<learning rate>', type=float, help='learning rate')
+parser.add_argument('--decay', metavar='<decay>', type=float, help='learning rate decay')
+parser.add_argument('--gamma', metavar='<gamma>', type=float, help='adjust u-law compensation (default 2.0, should not be less than 1.0)')
+parser.add_argument('--lookahead', metavar='<nb frames>', default=2, type=int, help='Number of look-ahead frames (default 2)')
+parser.add_argument('--logdir', metavar='<log dir>', help='directory for tensorboard log files')
+parser.add_argument('--lpc-gamma', type=float, default=1, help='gamma for LPC weighting')
+parser.add_argument('--cuda-devices', metavar='<cuda devices>', type=str, default=None, help='string with comma separated cuda device ids')
+
+args = parser.parse_args()
+
+# set visible cuda devices
+if args.cuda_devices != None:
+ os.environ['CUDA_VISIBLE_DEVICES'] = args.cuda_devices
+
+density = (0.05, 0.05, 0.2)
+if args.density_split is not None:
+ density = args.density_split
+elif args.density is not None:
+ density = [0.5*args.density, 0.5*args.density, 2.0*args.density];
+
+grub_density = (1., 1., 1.)
+if args.grub_density_split is not None:
+ grub_density = args.grub_density_split
+elif args.grub_density is not None:
+ grub_density = [0.5*args.grub_density, 0.5*args.grub_density, 2.0*args.grub_density];
+
+gamma = 2.0 if args.gamma is None else args.gamma
+
+import importlib
+lpcnet = importlib.import_module(args.model)
+
+import sys
+import numpy as np
+from tensorflow.keras.optimizers import Adam
+from tensorflow.keras.callbacks import ModelCheckpoint, CSVLogger
+from ulaw import ulaw2lin, lin2ulaw
+import tensorflow.keras.backend as K
+import h5py
+
+import tensorflow as tf
+from tf_funcs import *
+from lossfuncs import *
+#gpus = tf.config.experimental.list_physical_devices('GPU')
+#if gpus:
+# try:
+# tf.config.experimental.set_virtual_device_configuration(gpus[0], [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=5120)])
+# except RuntimeError as e:
+# print(e)
+
+nb_epochs = args.epochs
+
+# Try reducing batch_size if you run out of memory on your GPU
+batch_size = args.batch_size
+
+quantize = args.quantize is not None
+retrain = args.retrain is not None
+
+lpc_order = 16
+
+if quantize:
+ lr = 0.00003
+ decay = 0
+ input_model = args.quantize
+else:
+ lr = 0.001
+ decay = 5e-5
+
+if args.lr is not None:
+ lr = args.lr
+
+if args.decay is not None:
+ decay = args.decay
+
+if retrain:
+ input_model = args.retrain
+
+flag_e2e = args.flag_e2e
+
+opt = Adam(lr, decay=decay, beta_1=0.5, beta_2=0.8)
+strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy()
+
+with strategy.scope():
+ model, _, _ = lpcnet.new_lpcnet_model(rnn_units1=args.grua_size,
+ rnn_units2=args.grub_size,
+ batch_size=batch_size, training=True,
+ quantize=quantize,
+ flag_e2e=flag_e2e,
+ cond_size=args.cond_size,
+ lpc_gamma=args.lpc_gamma,
+ lookahead=args.lookahead
+ )
+ if not flag_e2e:
+ model.compile(optimizer=opt, loss=metric_cel, metrics=metric_cel)
+ else:
+ model.compile(optimizer=opt, loss = [interp_mulaw(gamma=gamma), loss_matchlar()], loss_weights = [1.0, 2.0], metrics={'pdf':[metric_cel,metric_icel,metric_exc_sd,metric_oginterploss]})
+ model.summary()
+
+feature_file = args.features
+pcm_file = args.data # 16 bit unsigned short PCM samples
+frame_size = model.frame_size
+nb_features = model.nb_used_features + lpc_order
+nb_used_features = model.nb_used_features
+feature_chunk_size = 15
+pcm_chunk_size = frame_size*feature_chunk_size
+
+# u for unquantised, load 16 bit PCM samples and convert to mu-law
+
+data = np.memmap(pcm_file, dtype='int16', mode='r')
+nb_frames = (len(data)//(2*pcm_chunk_size)-1)//batch_size*batch_size
+
+features = np.memmap(feature_file, dtype='float32', mode='r')
+
+# limit to discrete number of frames
+data = data[(4-args.lookahead)*2*frame_size:]
+data = data[:nb_frames*2*pcm_chunk_size]
+
+
+data = np.reshape(data, (nb_frames, pcm_chunk_size, 2))
+
+#print("ulaw std = ", np.std(out_exc))
+
+sizeof = features.strides[-1]
+features = np.lib.stride_tricks.as_strided(features, shape=(nb_frames, feature_chunk_size+4, nb_features),
+ strides=(feature_chunk_size*nb_features*sizeof, nb_features*sizeof, sizeof))
+#features = features[:, :, :nb_used_features]
+
+
+periods = (.1 + 50*features[:,:,nb_used_features-2:nb_used_features-1]+100).astype('int16')
+#periods = np.minimum(periods, 255)
+
+# dump models to disk as we go
+checkpoint = ModelCheckpoint('{}_{}_{}.h5'.format(args.output, args.grua_size, '{epoch:02d}'))
+
+if args.retrain is not None:
+ model.load_weights(args.retrain)
+
+if quantize or retrain:
+ #Adapting from an existing model
+ model.load_weights(input_model)
+ if quantize:
+ sparsify = lpcnet.Sparsify(10000, 30000, 100, density, quantize=True)
+ grub_sparsify = lpcnet.SparsifyGRUB(10000, 30000, 100, args.grua_size, grub_density, quantize=True)
+ else:
+ sparsify = lpcnet.Sparsify(0, 0, 1, density)
+ grub_sparsify = lpcnet.SparsifyGRUB(0, 0, 1, args.grua_size, grub_density)
+else:
+ #Training from scratch
+ sparsify = lpcnet.Sparsify(2000, 20000, 400, density)
+ grub_sparsify = lpcnet.SparsifyGRUB(2000, 40000, 400, args.grua_size, grub_density)
+
+model.save_weights('{}_{}_initial.h5'.format(args.output, args.grua_size))
+
+loader = LPCNetLoader(data, features, periods, batch_size, e2e=flag_e2e, lookahead=args.lookahead)
+
+callbacks = [checkpoint, sparsify, grub_sparsify]
+if args.logdir is not None:
+ logdir = '{}/{}_{}_logs'.format(args.logdir, args.output, args.grua_size)
+ tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=logdir)
+ callbacks.append(tensorboard_callback)
+
+model.fit(loader, epochs=nb_epochs, validation_split=0.0, callbacks=callbacks)
diff --git a/dnn/training_tf2/train_plc.py b/dnn/training_tf2/train_plc.py
new file mode 100644
index 00000000..ca30c457
--- /dev/null
+++ b/dnn/training_tf2/train_plc.py
@@ -0,0 +1,197 @@
+#!/usr/bin/python3
+'''Copyright (c) 2021-2022 Amazon
+ Copyright (c) 2018-2019 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+# Train an LPCNet model
+
+import argparse
+from plc_loader import PLCLoader
+
+parser = argparse.ArgumentParser(description='Train a PLC model')
+
+parser.add_argument('features', metavar='<features file>', help='binary features file (float32)')
+parser.add_argument('lost_file', metavar='<packet loss file>', help='packet loss traces (int8)')
+parser.add_argument('output', metavar='<output>', help='trained model file (.h5)')
+parser.add_argument('--model', metavar='<model>', default='lpcnet_plc', help='PLC model python definition (without .py)')
+group1 = parser.add_mutually_exclusive_group()
+group1.add_argument('--quantize', metavar='<input weights>', help='quantize model')
+group1.add_argument('--retrain', metavar='<input weights>', help='continue training model')
+parser.add_argument('--gru-size', metavar='<units>', default=256, type=int, help='number of units in GRU (default 256)')
+parser.add_argument('--cond-size', metavar='<units>', default=128, type=int, help='number of units in conditioning network (default 128)')
+parser.add_argument('--epochs', metavar='<epochs>', default=120, type=int, help='number of epochs to train for (default 120)')
+parser.add_argument('--batch-size', metavar='<batch size>', default=128, type=int, help='batch size to use (default 128)')
+parser.add_argument('--seq-length', metavar='<sequence length>', default=1000, type=int, help='sequence length to use (default 1000)')
+parser.add_argument('--lr', metavar='<learning rate>', type=float, help='learning rate')
+parser.add_argument('--decay', metavar='<decay>', type=float, help='learning rate decay')
+parser.add_argument('--band-loss', metavar='<weight>', default=1.0, type=float, help='weight of band loss (default 1.0)')
+parser.add_argument('--loss-bias', metavar='<bias>', default=0.0, type=float, help='loss bias towards low energy (default 0.0)')
+parser.add_argument('--logdir', metavar='<log dir>', help='directory for tensorboard log files')
+
+
+args = parser.parse_args()
+
+import importlib
+lpcnet = importlib.import_module(args.model)
+
+import sys
+import numpy as np
+from tensorflow.keras.optimizers import Adam
+from tensorflow.keras.callbacks import ModelCheckpoint, CSVLogger
+import tensorflow.keras.backend as K
+import h5py
+
+import tensorflow as tf
+#gpus = tf.config.experimental.list_physical_devices('GPU')
+#if gpus:
+# try:
+# tf.config.experimental.set_virtual_device_configuration(gpus[0], [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=5120)])
+# except RuntimeError as e:
+# print(e)
+
+nb_epochs = args.epochs
+
+# Try reducing batch_size if you run out of memory on your GPU
+batch_size = args.batch_size
+
+quantize = args.quantize is not None
+retrain = args.retrain is not None
+
+if quantize:
+ lr = 0.00003
+ decay = 0
+ input_model = args.quantize
+else:
+ lr = 0.001
+ decay = 2.5e-5
+
+if args.lr is not None:
+ lr = args.lr
+
+if args.decay is not None:
+ decay = args.decay
+
+if retrain:
+ input_model = args.retrain
+
+def plc_loss(alpha=1.0, bias=0.):
+ def loss(y_true,y_pred):
+ mask = y_true[:,:,-1:]
+ y_true = y_true[:,:,:-1]
+ e = (y_pred - y_true)*mask
+ e_bands = tf.signal.idct(e[:,:,:-2], norm='ortho')
+ bias_mask = K.minimum(1., K.maximum(0., 4*y_true[:,:,-1:]))
+ l1_loss = K.mean(K.abs(e)) + 0.1*K.mean(K.maximum(0., -e[:,:,-1:])) + alpha*K.mean(K.abs(e_bands) + bias*bias_mask*K.maximum(0., e_bands)) + K.mean(K.minimum(K.abs(e[:,:,18:19]),1.)) + 8*K.mean(K.minimum(K.abs(e[:,:,18:19]),.4))
+ return l1_loss
+ return loss
+
+def plc_l1_loss():
+ def L1_loss(y_true,y_pred):
+ mask = y_true[:,:,-1:]
+ y_true = y_true[:,:,:-1]
+ e = (y_pred - y_true)*mask
+ l1_loss = K.mean(K.abs(e))
+ return l1_loss
+ return L1_loss
+
+def plc_ceps_loss():
+ def ceps_loss(y_true,y_pred):
+ mask = y_true[:,:,-1:]
+ y_true = y_true[:,:,:-1]
+ e = (y_pred - y_true)*mask
+ l1_loss = K.mean(K.abs(e[:,:,:-2]))
+ return l1_loss
+ return ceps_loss
+
+def plc_band_loss():
+ def L1_band_loss(y_true,y_pred):
+ mask = y_true[:,:,-1:]
+ y_true = y_true[:,:,:-1]
+ e = (y_pred - y_true)*mask
+ e_bands = tf.signal.idct(e[:,:,:-2], norm='ortho')
+ l1_loss = K.mean(K.abs(e_bands))
+ return l1_loss
+ return L1_band_loss
+
+def plc_pitch_loss():
+ def pitch_loss(y_true,y_pred):
+ mask = y_true[:,:,-1:]
+ y_true = y_true[:,:,:-1]
+ e = (y_pred - y_true)*mask
+ l1_loss = K.mean(K.minimum(K.abs(e[:,:,18:19]),.4))
+ return l1_loss
+ return pitch_loss
+
+opt = Adam(lr, decay=decay, beta_2=0.99)
+strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy()
+
+with strategy.scope():
+ model = lpcnet.new_lpcnet_plc_model(rnn_units=args.gru_size, batch_size=batch_size, training=True, quantize=quantize, cond_size=args.cond_size)
+ model.compile(optimizer=opt, loss=plc_loss(alpha=args.band_loss, bias=args.loss_bias), metrics=[plc_l1_loss(), plc_ceps_loss(), plc_band_loss(), plc_pitch_loss()])
+ model.summary()
+
+lpc_order = 16
+
+feature_file = args.features
+nb_features = model.nb_used_features + lpc_order + model.nb_burg_features
+nb_used_features = model.nb_used_features
+nb_burg_features = model.nb_burg_features
+sequence_size = args.seq_length
+
+# u for unquantised, load 16 bit PCM samples and convert to mu-law
+
+
+features = np.memmap(feature_file, dtype='float32', mode='r')
+nb_sequences = len(features)//(nb_features*sequence_size)//batch_size*batch_size
+features = features[:nb_sequences*sequence_size*nb_features]
+
+features = np.reshape(features, (nb_sequences, sequence_size, nb_features))
+
+features = features[:, :, :nb_used_features+model.nb_burg_features]
+
+lost = np.memmap(args.lost_file, dtype='int8', mode='r')
+
+# dump models to disk as we go
+checkpoint = ModelCheckpoint('{}_{}_{}.h5'.format(args.output, args.gru_size, '{epoch:02d}'))
+
+if args.retrain is not None:
+ model.load_weights(args.retrain)
+
+if quantize or retrain:
+ #Adapting from an existing model
+ model.load_weights(input_model)
+
+model.save_weights('{}_{}_initial.h5'.format(args.output, args.gru_size))
+
+loader = PLCLoader(features, lost, nb_burg_features, batch_size)
+
+callbacks = [checkpoint]
+if args.logdir is not None:
+ logdir = '{}/{}_{}_logs'.format(args.logdir, args.output, args.gru_size)
+ tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=logdir)
+ callbacks.append(tensorboard_callback)
+
+model.fit(loader, epochs=nb_epochs, validation_split=0.0, callbacks=callbacks)
diff --git a/dnn/training_tf2/train_rdovae.py b/dnn/training_tf2/train_rdovae.py
new file mode 100644
index 00000000..b474b5f6
--- /dev/null
+++ b/dnn/training_tf2/train_rdovae.py
@@ -0,0 +1,151 @@
+#!/usr/bin/python3
+'''Copyright (c) 2021-2022 Amazon
+ Copyright (c) 2018-2019 Mozilla
+
+ 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 FOUNDATION 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.
+'''
+
+# Train an LPCNet model
+import tensorflow as tf
+strategy = tf.distribute.MultiWorkerMirroredStrategy()
+
+
+import argparse
+#from plc_loader import PLCLoader
+
+parser = argparse.ArgumentParser(description='Train a quantization model')
+
+parser.add_argument('features', metavar='<features file>', help='binary features file (float32)')
+parser.add_argument('output', metavar='<output>', help='trained model file (.h5)')
+parser.add_argument('--model', metavar='<model>', default='rdovae', help='PLC model python definition (without .py)')
+group1 = parser.add_mutually_exclusive_group()
+group1.add_argument('--quantize', metavar='<input weights>', help='quantize model')
+group1.add_argument('--retrain', metavar='<input weights>', help='continue training model')
+parser.add_argument('--cond-size', metavar='<units>', default=1024, type=int, help='number of units in conditioning network (default 1024)')
+parser.add_argument('--epochs', metavar='<epochs>', default=120, type=int, help='number of epochs to train for (default 120)')
+parser.add_argument('--batch-size', metavar='<batch size>', default=128, type=int, help='batch size to use (default 128)')
+parser.add_argument('--seq-length', metavar='<sequence length>', default=1000, type=int, help='sequence length to use (default 1000)')
+parser.add_argument('--lr', metavar='<learning rate>', type=float, help='learning rate')
+parser.add_argument('--decay', metavar='<decay>', type=float, help='learning rate decay')
+parser.add_argument('--logdir', metavar='<log dir>', help='directory for tensorboard log files')
+
+
+args = parser.parse_args()
+
+import importlib
+rdovae = importlib.import_module(args.model)
+
+import sys
+import numpy as np
+from tensorflow.keras.optimizers import Adam
+from tensorflow.keras.callbacks import ModelCheckpoint, CSVLogger
+import tensorflow.keras.backend as K
+import h5py
+
+#gpus = tf.config.experimental.list_physical_devices('GPU')
+#if gpus:
+# try:
+# tf.config.experimental.set_virtual_device_configuration(gpus[0], [tf.config.experimental.VirtualDeviceConfiguration(memory_limit=5120)])
+# except RuntimeError as e:
+# print(e)
+
+nb_epochs = args.epochs
+
+# Try reducing batch_size if you run out of memory on your GPU
+batch_size = args.batch_size
+
+quantize = args.quantize is not None
+retrain = args.retrain is not None
+
+if quantize:
+ lr = 0.00003
+ decay = 0
+ input_model = args.quantize
+else:
+ lr = 0.001
+ decay = 2.5e-5
+
+if args.lr is not None:
+ lr = args.lr
+
+if args.decay is not None:
+ decay = args.decay
+
+if retrain:
+ input_model = args.retrain
+
+
+opt = Adam(lr, decay=decay, beta_2=0.99)
+
+with strategy.scope():
+ model, encoder, decoder, _ = rdovae.new_rdovae_model(nb_used_features=20, nb_bits=80, batch_size=batch_size, cond_size=args.cond_size, nb_quant=16)
+ model.compile(optimizer=opt, loss=[rdovae.feat_dist_loss, rdovae.feat_dist_loss, rdovae.sq1_rate_loss, rdovae.sq2_rate_loss], loss_weights=[.5, .5, 1., .1], metrics={'hard_bits':rdovae.sq_rate_metric})
+ model.summary()
+
+lpc_order = 16
+
+feature_file = args.features
+nb_features = model.nb_used_features + lpc_order
+nb_used_features = model.nb_used_features
+sequence_size = args.seq_length
+
+# u for unquantised, load 16 bit PCM samples and convert to mu-law
+
+
+features = np.memmap(feature_file, dtype='float32', mode='r')
+nb_sequences = len(features)//(nb_features*sequence_size)//batch_size*batch_size
+features = features[:nb_sequences*sequence_size*nb_features]
+
+features = np.reshape(features, (nb_sequences, sequence_size, nb_features))
+print(features.shape)
+features = features[:, :, :nb_used_features]
+
+#lambda_val = np.repeat(np.random.uniform(.0007, .002, (features.shape[0], 1, 1)), features.shape[1]//2, axis=1)
+#quant_id = np.round(10*np.log(lambda_val/.0007)).astype('int16')
+#quant_id = quant_id[:,:,0]
+quant_id = np.repeat(np.random.randint(16, size=(features.shape[0], 1, 1), dtype='int16'), features.shape[1]//2, axis=1)
+lambda_val = .0002*np.exp(quant_id/3.8)
+quant_id = quant_id[:,:,0]
+
+# dump models to disk as we go
+checkpoint = ModelCheckpoint('{}_{}_{}.h5'.format(args.output, args.cond_size, '{epoch:02d}'))
+
+if args.retrain is not None:
+ model.load_weights(args.retrain)
+
+if quantize or retrain:
+ #Adapting from an existing model
+ model.load_weights(input_model)
+
+model.save_weights('{}_{}_initial.h5'.format(args.output, args.cond_size))
+
+callbacks = [checkpoint]
+#callbacks = []
+
+if args.logdir is not None:
+ logdir = '{}/{}_{}_logs'.format(args.logdir, args.output, args.cond_size)
+ tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=logdir)
+ callbacks.append(tensorboard_callback)
+
+model.fit([features, quant_id, lambda_val], [features, features, features, features], batch_size=batch_size, epochs=nb_epochs, validation_split=0.0, callbacks=callbacks)
diff --git a/dnn/training_tf2/ulaw.py b/dnn/training_tf2/ulaw.py
new file mode 100644
index 00000000..b79d4315
--- /dev/null
+++ b/dnn/training_tf2/ulaw.py
@@ -0,0 +1,19 @@
+
+import numpy as np
+import math
+
+scale = 255.0/32768.0
+scale_1 = 32768.0/255.0
+def ulaw2lin(u):
+ u = u - 128
+ s = np.sign(u)
+ u = np.abs(u)
+ return s*scale_1*(np.exp(u/128.*math.log(256))-1)
+
+
+def lin2ulaw(x):
+ s = np.sign(x)
+ x = np.abs(x)
+ u = (s*(128*np.log(1+scale*x)/math.log(256)))
+ u = np.clip(128 + np.round(u), 0, 255)
+ return u.astype('int16')
diff --git a/dnn/training_tf2/uniform_noise.py b/dnn/training_tf2/uniform_noise.py
new file mode 100644
index 00000000..6197dd5f
--- /dev/null
+++ b/dnn/training_tf2/uniform_noise.py
@@ -0,0 +1,78 @@
+# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Contains the UniformNoise layer."""
+
+
+import tensorflow.compat.v2 as tf
+
+from tensorflow.keras import backend
+
+from tensorflow.keras.layers import Layer
+
+class UniformNoise(Layer):
+ """Apply additive zero-centered uniform noise.
+
+ This is useful to mitigate overfitting
+ (you could see it as a form of random data augmentation).
+ Gaussian Noise (GS) is a natural choice as corruption process
+ for real valued inputs.
+
+ As it is a regularization layer, it is only active at training time.
+
+ Args:
+ stddev: Float, standard deviation of the noise distribution.
+ seed: Integer, optional random seed to enable deterministic behavior.
+
+ Call arguments:
+ inputs: Input tensor (of any rank).
+ training: Python boolean indicating whether the layer should behave in
+ training mode (adding noise) or in inference mode (doing nothing).
+
+ Input shape:
+ Arbitrary. Use the keyword argument `input_shape`
+ (tuple of integers, does not include the samples axis)
+ when using this layer as the first layer in a model.
+
+ Output shape:
+ Same shape as input.
+ """
+
+
+
+
+ def __init__(self, stddev=0.5, seed=None, **kwargs):
+ super().__init__(**kwargs)
+ self.supports_masking = True
+ self.stddev = stddev
+
+
+ def call(self, inputs, training=None):
+ def noised():
+ return inputs + backend.random_uniform(
+ shape=tf.shape(inputs),
+ minval=-self.stddev,
+ maxval=self.stddev,
+ dtype=inputs.dtype,
+ )
+
+ return backend.in_train_phase(noised, inputs, training=training)
+
+ def get_config(self):
+ config = {"stddev": self.stddev}
+ base_config = super().get_config()
+ return dict(list(base_config.items()) + list(config.items()))
+
+ def compute_output_shape(self, input_shape):
+ return input_shape
diff --git a/dnn/vec.h b/dnn/vec.h
new file mode 100644
index 00000000..8e96cbf3
--- /dev/null
+++ b/dnn/vec.h
@@ -0,0 +1,389 @@
+/* Copyright (c) 2018 Mozilla
+ 2008-2011 Octasic Inc.
+ 2012-2017 Jean-Marc Valin */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifndef VEC_H
+#define VEC_H
+
+#include "opus_types.h"
+#include <math.h>
+#include "arch.h"
+#include "x86/x86_arch_macros.h"
+
+
+#if defined(__AVX__) || defined(__SSE2__)
+#include "vec_avx.h"
+#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) && !defined(DISABLE_NEON)
+#include "vec_neon.h"
+#else
+
+#include "os_support.h"
+
+#define MAX_INPUTS (2048)
+
+#define NO_OPTIMIZATIONS
+
+static inline void sgemv16x1(float *out, const float *weights, int rows, int cols, int col_stride, const float *x)
+{
+ int i, j;
+ OPUS_CLEAR(out, rows);
+ for (i=0;i<rows;i+=16)
+ {
+ for (j=0;j<cols;j++)
+ {
+ const float * restrict w;
+ float * restrict y;
+ float xj;
+ w = &weights[j*col_stride + i];
+ xj = x[j];
+ y = &out[i];
+ y[0] += w[0]*xj;
+ y[1] += w[1]*xj;
+ y[2] += w[2]*xj;
+ y[3] += w[3]*xj;
+ y[4] += w[4]*xj;
+ y[5] += w[5]*xj;
+ y[6] += w[6]*xj;
+ y[7] += w[7]*xj;
+ y[8] += w[8]*xj;
+ y[9] += w[9]*xj;
+ y[10] += w[10]*xj;
+ y[11] += w[11]*xj;
+ y[12] += w[12]*xj;
+ y[13] += w[13]*xj;
+ y[14] += w[14]*xj;
+ y[15] += w[15]*xj;
+ }
+ }
+}
+
+static inline void sgemv8x1(float *out, const float *weights, int rows, int cols, int col_stride, const float *x)
+{
+ int i, j;
+ OPUS_CLEAR(out, rows);
+ for (i=0;i<rows;i+=8)
+ {
+ for (j=0;j<cols;j++)
+ {
+ const float * restrict w;
+ float * restrict y;
+ float xj;
+ w = &weights[j*col_stride + i];
+ xj = x[j];
+ y = &out[i];
+ y[0] += w[0]*xj;
+ y[1] += w[1]*xj;
+ y[2] += w[2]*xj;
+ y[3] += w[3]*xj;
+ y[4] += w[4]*xj;
+ y[5] += w[5]*xj;
+ y[6] += w[6]*xj;
+ y[7] += w[7]*xj;
+ }
+ }
+}
+
+static inline void sgemv(float *out, const float *weights, int rows, int cols, int col_stride, const float *x)
+{
+ if ((rows&0xf) == 0) sgemv16x1(out, weights, rows, cols, col_stride, x);
+ else if ((rows&0x7) == 0) sgemv8x1(out, weights, rows, cols, col_stride, x);
+ else {
+ int i, j;
+ for (i=0;i<rows;i++)
+ {
+ out[i] = 0;
+ for (j=0;j<cols;j++) out[i] += weights[j*col_stride + i]*x[j];
+ }
+ }
+}
+
+static inline void sparse_sgemv8x4(float *out, const float *w, const int *idx, int rows, const float *x)
+{
+ int i, j;
+ OPUS_CLEAR(out, rows);
+ for (i=0;i<rows;i+=8)
+ {
+ int cols;
+ cols = *idx++;
+ for (j=0;j<cols;j++)
+ {
+ int pos;
+ float * restrict y;
+ float xj0, xj1, xj2, xj3;
+ pos = (*idx++);
+ xj0 = x[pos+0];
+ xj1 = x[pos+1];
+ xj2 = x[pos+2];
+ xj3 = x[pos+3];
+ y = &out[i];
+ y[0] += w[0]*xj0;
+ y[1] += w[1]*xj0;
+ y[2] += w[2]*xj0;
+ y[3] += w[3]*xj0;
+ y[4] += w[4]*xj0;
+ y[5] += w[5]*xj0;
+ y[6] += w[6]*xj0;
+ y[7] += w[7]*xj0;
+
+ y[0] += w[8]*xj1;
+ y[1] += w[9]*xj1;
+ y[2] += w[10]*xj1;
+ y[3] += w[11]*xj1;
+ y[4] += w[12]*xj1;
+ y[5] += w[13]*xj1;
+ y[6] += w[14]*xj1;
+ y[7] += w[15]*xj1;
+
+ y[0] += w[16]*xj2;
+ y[1] += w[17]*xj2;
+ y[2] += w[18]*xj2;
+ y[3] += w[19]*xj2;
+ y[4] += w[20]*xj2;
+ y[5] += w[21]*xj2;
+ y[6] += w[22]*xj2;
+ y[7] += w[23]*xj2;
+
+ y[0] += w[24]*xj3;
+ y[1] += w[25]*xj3;
+ y[2] += w[26]*xj3;
+ y[3] += w[27]*xj3;
+ y[4] += w[28]*xj3;
+ y[5] += w[29]*xj3;
+ y[6] += w[30]*xj3;
+ y[7] += w[31]*xj3;
+ w += 32;
+ }
+ }
+}
+
+#ifdef USE_SU_BIAS
+static inline void sparse_cgemv8x4(float *out, const opus_int8 *w, const int *idx, const float *scale, int rows, int cols, const float *_x)
+{
+ int i, j;
+ unsigned char x[MAX_INPUTS];
+ for (i=0;i<rows;i++) out[i] = 0;
+ for (i=0;i<cols;i++) x[i] = 127+floor(.5+127*_x[i]);
+ for (i=0;i<rows;i+=8)
+ {
+ int colblocks;
+ colblocks = *idx++;
+ for (j=0;j<colblocks;j++)
+ {
+ int pos;
+ float * restrict y;
+ int xj0, xj1, xj2, xj3;
+ pos = (*idx++);
+ xj0 = x[pos+0];
+ xj1 = x[pos+1];
+ xj2 = x[pos+2];
+ xj3 = x[pos+3];
+ y = &out[i];
+ y[0] += (w[0]*xj0+w[1]*xj1+w[2]*xj2+w[3]*xj3);
+ y[1] += (w[4]*xj0+w[5]*xj1+w[6]*xj2+w[7]*xj3);
+ y[2] += (w[8]*xj0+w[9]*xj1+w[10]*xj2+w[11]*xj3);
+ y[3] += (w[12]*xj0+w[13]*xj1+w[14]*xj2+w[15]*xj3);
+ y[4] += (w[16]*xj0+w[17]*xj1+w[18]*xj2+w[19]*xj3);
+ y[5] += (w[20]*xj0+w[21]*xj1+w[22]*xj2+w[23]*xj3);
+ y[6] += (w[24]*xj0+w[25]*xj1+w[26]*xj2+w[27]*xj3);
+ y[7] += (w[28]*xj0+w[29]*xj1+w[30]*xj2+w[31]*xj3);
+ w += 32;
+ }
+ }
+ for (i=0;i<rows;i++) out[i] *= scale[i];
+}
+static inline void cgemv8x4(float *out, const opus_int8 *w, const float *scale, int rows, int cols, const float *_x)
+{
+ int i, j;
+ unsigned char x[MAX_INPUTS];
+ for (i=0;i<rows;i++) out[i] = 0;
+ for (i=0;i<cols;i++) x[i] = 127+(int)floor(.5+127*_x[i]);
+ for (i=0;i<rows;i+=8)
+ {
+ for (j=0;j<cols;j+=4)
+ {
+ float *y;
+ float xj0, xj1, xj2, xj3;
+ xj0 = x[j+0];
+ xj1 = x[j+1];
+ xj2 = x[j+2];
+ xj3 = x[j+3];
+ y = &out[i];
+ y[0] += (w[0]*xj0+w[1]*xj1+w[2]*xj2+w[3]*xj3);
+ y[1] += (w[4]*xj0+w[5]*xj1+w[6]*xj2+w[7]*xj3);
+ y[2] += (w[8]*xj0+w[9]*xj1+w[10]*xj2+w[11]*xj3);
+ y[3] += (w[12]*xj0+w[13]*xj1+w[14]*xj2+w[15]*xj3);
+ y[4] += (w[16]*xj0+w[17]*xj1+w[18]*xj2+w[19]*xj3);
+ y[5] += (w[20]*xj0+w[21]*xj1+w[22]*xj2+w[23]*xj3);
+ y[6] += (w[24]*xj0+w[25]*xj1+w[26]*xj2+w[27]*xj3);
+ y[7] += (w[28]*xj0+w[29]*xj1+w[30]*xj2+w[31]*xj3);
+ w += 32;
+ }
+ }
+ for (i=0;i<rows;i++) out[i] *= scale[i];
+}
+#else
+static inline void sparse_cgemv8x4(float *out, const opus_int8 *w, const int *idx, const float *scale, int rows, int cols, const float *_x)
+{
+ int i, j;
+ opus_int8 x[MAX_INPUTS];
+ for (i=0;i<rows;i++) out[i] = 0;
+ for (i=0;i<cols;i++) x[i] = (int)floor(.5+127*_x[i]);
+ for (i=0;i<rows;i+=8)
+ {
+ int colblocks;
+ colblocks = *idx++;
+ for (j=0;j<colblocks;j++)
+ {
+ int pos;
+ float * restrict y;
+ int xj0, xj1, xj2, xj3;
+ pos = (*idx++);
+ xj0 = x[pos+0];
+ xj1 = x[pos+1];
+ xj2 = x[pos+2];
+ xj3 = x[pos+3];
+ y = &out[i];
+ y[0] += (w[0]*xj0+w[1]*xj1+w[2]*xj2+w[3]*xj3);
+ y[1] += (w[4]*xj0+w[5]*xj1+w[6]*xj2+w[7]*xj3);
+ y[2] += (w[8]*xj0+w[9]*xj1+w[10]*xj2+w[11]*xj3);
+ y[3] += (w[12]*xj0+w[13]*xj1+w[14]*xj2+w[15]*xj3);
+ y[4] += (w[16]*xj0+w[17]*xj1+w[18]*xj2+w[19]*xj3);
+ y[5] += (w[20]*xj0+w[21]*xj1+w[22]*xj2+w[23]*xj3);
+ y[6] += (w[24]*xj0+w[25]*xj1+w[26]*xj2+w[27]*xj3);
+ y[7] += (w[28]*xj0+w[29]*xj1+w[30]*xj2+w[31]*xj3);
+ w += 32;
+ }
+ }
+ for (i=0;i<rows;i++) out[i] *= scale[i];
+}
+static inline void cgemv8x4(float *out, const opus_int8 *w, const float *scale, int rows, int cols, const float *_x)
+{
+ int i, j;
+ opus_int8 x[MAX_INPUTS];
+ for (i=0;i<rows;i++) out[i] = 0;
+ for (i=0;i<cols;i++) x[i] = (int)floor(.5+127*_x[i]);
+ for (i=0;i<rows;i+=8)
+ {
+ for (j=0;j<cols;j+=4)
+ {
+ float *y;
+ float xj0, xj1, xj2, xj3;
+ xj0 = x[j+0];
+ xj1 = x[j+1];
+ xj2 = x[j+2];
+ xj3 = x[j+3];
+ y = &out[i];
+ y[0] += (w[0]*xj0+w[1]*xj1+w[2]*xj2+w[3]*xj3);
+ y[1] += (w[4]*xj0+w[5]*xj1+w[6]*xj2+w[7]*xj3);
+ y[2] += (w[8]*xj0+w[9]*xj1+w[10]*xj2+w[11]*xj3);
+ y[3] += (w[12]*xj0+w[13]*xj1+w[14]*xj2+w[15]*xj3);
+ y[4] += (w[16]*xj0+w[17]*xj1+w[18]*xj2+w[19]*xj3);
+ y[5] += (w[20]*xj0+w[21]*xj1+w[22]*xj2+w[23]*xj3);
+ y[6] += (w[24]*xj0+w[25]*xj1+w[26]*xj2+w[27]*xj3);
+ y[7] += (w[28]*xj0+w[29]*xj1+w[30]*xj2+w[31]*xj3);
+ w += 32;
+ }
+ }
+ for (i=0;i<rows;i++) out[i] *= scale[i];
+}
+#endif
+
+/* No AVX2/FMA support */
+#ifndef LPCNET_TEST
+static inline float lpcnet_exp2(float x)
+{
+ int integer;
+ float frac;
+ union {
+ float f;
+ opus_uint32 i;
+ } res;
+ integer = floor(x);
+ if (integer < -50)
+ return 0;
+ frac = x-integer;
+ /* K0 = 1, K1 = log(2), K2 = 3-4*log(2), K3 = 3*log(2) - 2 */
+ res.f = 0.99992522f + frac * (0.69583354f
+ + frac * (0.22606716f + 0.078024523f*frac));
+ res.i = (res.i + (integer<<23)) & 0x7fffffff;
+ return res.f;
+}
+#define lpcnet_exp(x) lpcnet_exp2((x)*1.44269504f)
+
+#define fmadd(a, b, c) ((a)*(b)+(c))
+static OPUS_INLINE float tanh_approx(float x)
+{
+ const float N0 = 952.52801514f;
+ const float N1 = 96.39235687f;
+ const float N2 = 0.60863042f;
+ const float D0 = 952.72399902f;
+ const float D1 = 413.36801147f;
+ const float D2 = 11.88600922f;
+ float X2, num, den;
+ X2 = x*x;
+ num = fmadd(fmadd(N2, X2, N1), X2, N0);
+ den = fmadd(fmadd(D2, X2, D1), X2, D0);
+ num = num*x/den;
+ return MAX32(-1.f, MIN32(1.f, num));
+}
+
+static inline float sigmoid_approx(float x)
+{
+ return .5f + .5f*tanh_approx(.5f*x);
+}
+
+static inline void softmax(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N;i++)
+ y[i] = lpcnet_exp(x[i]);
+}
+
+static inline void vec_tanh(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N;i++)
+ {
+ y[i] = tanh_approx(x[i]);
+ }
+}
+
+static inline void vec_sigmoid(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N;i++)
+ {
+ y[i] = sigmoid_approx(x[i]);
+ }
+}
+#endif
+
+#define SCALE (128.f*127.f)
+#define SCALE_1 (1.f/128.f/127.f)
+
+#endif /*no optimizations*/
+#endif /*VEC_H*/
diff --git a/dnn/vec_avx.h b/dnn/vec_avx.h
new file mode 100644
index 00000000..979e77fe
--- /dev/null
+++ b/dnn/vec_avx.h
@@ -0,0 +1,884 @@
+/* Copyright (c) 2018 Mozilla
+ 2012-2017 Jean-Marc Valin */
+/*
+ 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 FOUNDATION 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.
+*/
+/*
+ AVX implementation of vector operations, compile with -mavx
+ AVX2/FMA implementation of vector operations, compile with -mavx2 -mfma
+*/
+
+#ifndef VEC_AVX_H
+#define VEC_AVX_H
+
+#include <immintrin.h>
+#include <math.h>
+#include "celt/x86/x86cpu.h"
+
+#define MAX_INPUTS (2048)
+
+#define USE_SU_BIAS
+
+#ifndef __SSE_4_1__
+static inline __m128 mm_floor_ps(__m128 x) {
+ __m128 half = _mm_set1_ps(0.5);
+ return _mm_cvtepi32_ps(_mm_cvtps_epi32(_mm_sub_ps(x, half)));
+}
+#undef _mm_floor_ps
+#define _mm_floor_ps(x) mm_floor_ps(x)
+#endif
+
+
+/* If we don't have AVX available, emulate what we need with SSE up to 4.1. */
+#ifndef __AVX__
+
+typedef struct {
+ __m128 lo;
+ __m128 hi;
+} mm256_emu;
+#define __m256 mm256_emu
+
+static inline mm256_emu mm256_loadu_ps(const float *src) {
+ mm256_emu ret;
+ ret.lo = _mm_loadu_ps(&src[0]);
+ ret.hi = _mm_loadu_ps(&src[4]);
+ return ret;
+}
+#define _mm256_loadu_ps(src) mm256_loadu_ps(src)
+
+
+static inline void mm256_storeu_ps(float *dst, mm256_emu src) {
+ _mm_storeu_ps(dst, src.lo);
+ _mm_storeu_ps(&dst[4], src.hi);
+}
+#define _mm256_storeu_ps(dst, src) mm256_storeu_ps(dst, src)
+
+
+static inline mm256_emu mm256_setzero_ps(void) {
+ mm256_emu ret;
+ ret.lo = _mm_setzero_ps();
+ ret.hi = ret.lo;
+ return ret;
+}
+#define _mm256_setzero_ps mm256_setzero_ps
+
+static inline mm256_emu mm256_broadcast_ss(const float *x) {
+ mm256_emu ret;
+ ret.lo = _mm_set1_ps(*x);
+ ret.hi = ret.lo;
+ return ret;
+}
+#define _mm256_broadcast_ss(x) mm256_broadcast_ss(x)
+
+static inline mm256_emu mm256_set1_ps(float x) {
+ mm256_emu ret;
+ ret.lo = _mm_set1_ps(x);
+ ret.hi = ret.lo;
+ return ret;
+}
+#define _mm256_set1_ps(x) mm256_set1_ps(x)
+
+
+
+static inline mm256_emu mm256_mul_ps(mm256_emu a, mm256_emu b) {
+ mm256_emu ret;
+ ret.lo = _mm_mul_ps(a.lo, b.lo);
+ ret.hi = _mm_mul_ps(a.hi, b.hi);
+ return ret;
+}
+#define _mm256_mul_ps(a,b) mm256_mul_ps(a,b)
+
+static inline mm256_emu mm256_add_ps(mm256_emu a, mm256_emu b) {
+ mm256_emu ret;
+ ret.lo = _mm_add_ps(a.lo, b.lo);
+ ret.hi = _mm_add_ps(a.hi, b.hi);
+ return ret;
+}
+#define _mm256_add_ps(a,b) mm256_add_ps(a,b)
+
+
+static inline mm256_emu mm256_max_ps(mm256_emu a, mm256_emu b) {
+ mm256_emu ret;
+ ret.lo = _mm_max_ps(a.lo, b.lo);
+ ret.hi = _mm_max_ps(a.hi, b.hi);
+ return ret;
+}
+#define _mm256_max_ps(a,b) mm256_max_ps(a,b)
+
+static inline mm256_emu mm256_min_ps(mm256_emu a, mm256_emu b) {
+ mm256_emu ret;
+ ret.lo = _mm_min_ps(a.lo, b.lo);
+ ret.hi = _mm_min_ps(a.hi, b.hi);
+ return ret;
+}
+#define _mm256_min_ps(a,b) mm256_min_ps(a,b)
+
+static inline mm256_emu mm256_rcp_ps(mm256_emu a) {
+ mm256_emu ret;
+ ret.lo = _mm_rcp_ps(a.lo);
+ ret.hi = _mm_rcp_ps(a.hi);
+ return ret;
+}
+#define _mm256_rcp_ps(a) mm256_rcp_ps(a)
+
+
+static inline __m128 mm256_extractf128_ps(mm256_emu x, int i) {
+ return (i==0) ? x.lo : x.hi;
+}
+#undef _mm256_extractf128_ps
+#define _mm256_extractf128_ps(x,i) mm256_extractf128_ps(x,i)
+
+static inline mm256_emu mm256_insertf128_ps(mm256_emu dst, __m128 src, int i) {
+ if (i==0) dst.lo = src;
+ else dst.hi = src;
+ return dst;
+}
+#undef _mm256_insertf128_ps
+#define _mm256_insertf128_ps(dst,src,i) mm256_insertf128_ps(dst,src,i)
+
+#endif /* __AVX__ */
+
+
+
+/* If we don't have AVX2 available, emulate what we need with SSE up to 4.1. */
+#ifndef __AVX2__
+
+typedef struct {
+ __m128i lo;
+ __m128i hi;
+} mm256i_emu;
+typedef __m256i real_m256i;
+#define __m256i mm256i_emu
+
+static inline mm256i_emu mm256_setzero_si256(void) {
+ mm256i_emu ret;
+ ret.lo = _mm_setzero_si128();
+ ret.hi = ret.lo;
+ return ret;
+}
+#define _mm256_setzero_si256 mm256_setzero_si256
+
+
+static inline mm256i_emu mm256_loadu_si256(const mm256i_emu *src) {
+ mm256i_emu ret;
+ ret.lo = _mm_loadu_si128((const __m128i*)src);
+ ret.hi = _mm_loadu_si128(&((const __m128i*)src)[1]);
+ return ret;
+}
+#define _mm256_loadu_si256(src) mm256_loadu_si256(src)
+
+
+static inline void mm256_storeu_si256(mm256i_emu *dst, mm256i_emu src) {
+ _mm_storeu_si128((__m128i*)dst, src.lo);
+ _mm_storeu_si128(&((__m128i*)dst)[1], src.hi);
+}
+#define _mm256_storeu_si256(dst, src) mm256_storeu_si256(dst, src)
+
+
+static inline mm256i_emu mm256_broadcastd_epi32(__m128i x) {
+ mm256i_emu ret;
+ ret.hi = ret.lo = _mm_shuffle_epi32(x, 0);
+ return ret;
+}
+#define _mm256_broadcastd_epi32(x) mm256_broadcastd_epi32(x)
+
+
+static inline mm256i_emu mm256_set1_epi32(int x) {
+ mm256i_emu ret;
+ ret.lo = _mm_set1_epi32(x);
+ ret.hi = ret.lo;
+ return ret;
+}
+#define _mm256_set1_epi32(x) mm256_set1_epi32(x)
+
+static inline mm256i_emu mm256_set1_epi16(int x) {
+ mm256i_emu ret;
+ ret.lo = _mm_set1_epi16(x);
+ ret.hi = ret.lo;
+ return ret;
+}
+#define _mm256_set1_epi16(x) mm256_set1_epi16(x)
+
+
+static inline mm256i_emu mm256_add_epi32(mm256i_emu a, mm256i_emu b) {
+ mm256i_emu ret;
+ ret.lo = _mm_add_epi32(a.lo, b.lo);
+ ret.hi = _mm_add_epi32(a.hi, b.hi);
+ return ret;
+}
+#define _mm256_add_epi32(a,b) mm256_add_epi32(a,b)
+
+static inline mm256i_emu mm256_madd_epi16(mm256i_emu a, mm256i_emu b) {
+ mm256i_emu ret;
+ ret.lo = _mm_madd_epi16(a.lo, b.lo);
+ ret.hi = _mm_madd_epi16(a.hi, b.hi);
+ return ret;
+}
+#define _mm256_madd_epi16(a,b) mm256_madd_epi16(a,b)
+
+static inline mm256i_emu mm256_maddubs_epi16(mm256i_emu a, mm256i_emu b) {
+ mm256i_emu ret;
+ ret.lo = _mm_maddubs_epi16(a.lo, b.lo);
+ ret.hi = _mm_maddubs_epi16(a.hi, b.hi);
+ return ret;
+}
+#define _mm256_maddubs_epi16(a,b) mm256_maddubs_epi16(a,b)
+
+
+
+/* Emulating the conversion functions is tricky because they use __m256i but are defined in AVX.
+ So we need to make a special when only AVX is available. */
+#ifdef __AVX__
+
+typedef union {
+ mm256i_emu fake;
+ real_m256i real;
+} mm256_union;
+
+static inline __m256 mm256_cvtepi32_ps(mm256i_emu a) {
+ mm256_union src;
+ src.fake = a;
+ return _mm256_cvtepi32_ps(src.real);
+}
+#define _mm256_cvtepi32_ps(a) mm256_cvtepi32_ps(a)
+
+static inline mm256i_emu mm256_cvtps_epi32(__m256 a) {
+ mm256_union ret;
+ ret.real = _mm256_cvtps_epi32(a);
+ return ret.fake;
+}
+#define _mm256_cvtps_epi32(a) mm256_cvtps_epi32(a)
+
+
+#else
+
+static inline mm256_emu mm256_cvtepi32_ps(mm256i_emu a) {
+ mm256_emu ret;
+ ret.lo = _mm_cvtepi32_ps(a.lo);
+ ret.hi = _mm_cvtepi32_ps(a.hi);
+ return ret;
+}
+#define _mm256_cvtepi32_ps(a) mm256_cvtepi32_ps(a)
+
+static inline mm256i_emu mm256_cvtps_epi32(mm256_emu a) {
+ mm256i_emu ret;
+ ret.lo = _mm_cvtps_epi32(a.lo);
+ ret.hi = _mm_cvtps_epi32(a.hi);
+ return ret;
+}
+#define _mm256_cvtps_epi32(a) mm256_cvtps_epi32(a)
+
+#endif /* __AVX__ */
+
+
+#endif /* __AVX2__ */
+
+/* In case we don't have FMA, make it a mul and an add. */
+#if !(defined(__FMA__) && defined(__AVX__))
+#define _mm256_fmadd_ps(a,b,c) _mm256_add_ps(_mm256_mul_ps(a, b), c)
+#define _mm_fmadd_ps(a,b,c) _mm_add_ps(_mm_mul_ps(a, b), c)
+#endif
+
+#ifdef __AVX2__
+static inline __m256 exp8_approx(__m256 X)
+{
+ const __m256 K0 = _mm256_set1_ps(0.99992522f);
+ const __m256 K1 = _mm256_set1_ps(0.69583354f);
+ const __m256 K2 = _mm256_set1_ps(0.22606716f);
+ const __m256 K3 = _mm256_set1_ps(0.078024523f);
+ const __m256 log2_E = _mm256_set1_ps(1.44269504f);
+ const __m256 max_in = _mm256_set1_ps(50.f);
+ const __m256 min_in = _mm256_set1_ps(-50.f);
+ __m256 XF, Y;
+ __m256i I;
+ X = _mm256_mul_ps(X, log2_E);
+ X = _mm256_max_ps(min_in, _mm256_min_ps(max_in, X));
+ XF = _mm256_floor_ps(X);
+ I = _mm256_cvtps_epi32(XF);
+ X = _mm256_sub_ps(X, XF);
+ Y = _mm256_fmadd_ps(_mm256_fmadd_ps(_mm256_fmadd_ps(K3, X, K2), X, K1), X, K0);
+ I = _mm256_slli_epi32(I, 23);
+ Y = _mm256_castsi256_ps(_mm256_add_epi32(I, _mm256_castps_si256(Y)));
+ return Y;
+}
+
+static inline void vector_ps_to_epi8(unsigned char *x, const float *_x, int len) {
+ int i;
+ __m256 const127 = _mm256_set1_ps(127.f);
+ for (i=0;i<len;i+=8) {
+ __m256 xf;
+ __m256i xi;
+ xf = _mm256_loadu_ps(&_x[i]);
+ xf = _mm256_fmadd_ps(xf, const127, const127);
+ xi = _mm256_cvtps_epi32(xf);
+ xi = _mm256_packus_epi32(xi, _mm256_setzero_si256());
+ xi = _mm256_permute4x64_epi64(xi, 0xD8);
+ xi = _mm256_packus_epi16(xi, _mm256_setzero_si256());
+ xi = _mm256_permutevar8x32_epi32(xi, _mm256_setr_epi32(0,1, 0,0, 0,0, 0,0));
+ _mm256_storeu_si256 ((__m256i *)(void*)&x[i], xi);
+ }
+}
+
+#else
+static inline __m128 exp4_approx(__m128 X)
+{
+ const __m128 K0 = _mm_set1_ps(0.99992522f);
+ const __m128 K1 = _mm_set1_ps(0.69583354f);
+ const __m128 K2 = _mm_set1_ps(0.22606716f);
+ const __m128 K3 = _mm_set1_ps(0.078024523f);
+ const __m128 log2_E = _mm_set1_ps(1.44269504);
+ const __m128 max_in = _mm_set1_ps(50.f);
+ const __m128 min_in = _mm_set1_ps(-50.f);
+ const __m128i mask = _mm_set1_epi32(0x7fffffff);
+ __m128 XF, Y;
+ __m128i I;
+ X = _mm_mul_ps(X, log2_E);
+ X = _mm_max_ps(min_in, _mm_min_ps(max_in, X));
+ XF = _mm_floor_ps(X);
+ I = _mm_cvtps_epi32(XF);
+ X = _mm_sub_ps(X, XF);
+ Y = _mm_fmadd_ps(_mm_fmadd_ps(_mm_fmadd_ps(K3, X, K2), X, K1), X, K0);
+ I = _mm_slli_epi32(I, 23);
+ Y = _mm_castsi128_ps(_mm_and_si128(mask, _mm_add_epi32(I, _mm_castps_si128(Y))));
+ return Y;
+}
+static inline __m256 exp8_approx(__m256 X)
+{
+ __m256 Y;
+ __m128 Xhi, Xlo, Yhi, Ylo;
+ Xhi = _mm256_extractf128_ps(X, 1);
+ Xlo = _mm256_extractf128_ps(X, 0);
+ Yhi = exp4_approx(Xhi);
+ Ylo = exp4_approx(Xlo);
+ Y = _mm256_insertf128_ps(_mm256_setzero_ps(), Yhi, 1);
+ Y = _mm256_insertf128_ps(Y, Ylo, 0);
+ return Y;
+}
+
+static inline void vector_ps_to_epi8(unsigned char *x, const float *_x, int len) {
+ int i;
+ for (i=0;i<len;i++) x[i] = 127+(int)floor(.5+127*_x[i]);
+}
+
+#endif
+
+
+#ifdef __AVX__
+
+/* Approximating tanh() using a Padé-like rational function:
+ tanh(x) ~= x * (N0 + N1*x^2 + N2*x^4)/(D0 + D1*x^2 + D2*x^4)
+ subject to the +/- 1 bounds.
+ The coefficients were determined by gradient descent trying to minimize
+ the maximum deviation over the whole range (this is only possible because
+ of the bounds). The max error is around 3e-4 and is dominated by the
+ reciprocal approximation (the max error of the rational function is
+ around 6e-5).
+ */
+static inline __m256 tanh8_approx(__m256 X)
+{
+ const __m256 N0 = _mm256_set1_ps(952.52801514f);
+ const __m256 N1 = _mm256_set1_ps(96.39235687f);
+ const __m256 N2 = _mm256_set1_ps(0.60863042f);
+ const __m256 D0 = _mm256_set1_ps(952.72399902f);
+ const __m256 D1 = _mm256_set1_ps(413.36801147f);
+ const __m256 D2 = _mm256_set1_ps(11.88600922f);
+ const __m256 max_out = _mm256_set1_ps(1.f);
+ const __m256 min_out = _mm256_set1_ps(-1.f);
+ __m256 X2, num, den;
+ X2 = _mm256_mul_ps(X, X);
+ num = _mm256_fmadd_ps(_mm256_fmadd_ps(N2, X2, N1), X2, N0);
+ den = _mm256_fmadd_ps(_mm256_fmadd_ps(D2, X2, D1), X2, D0);
+ num = _mm256_mul_ps(num, X);
+ den = _mm256_rcp_ps(den);
+ num = _mm256_mul_ps(num, den);
+ return _mm256_max_ps(min_out, _mm256_min_ps(max_out, num));
+}
+
+/* Sigmoid approximation using a Padé-like rational function:
+ 1/(1+exp(-x)) ~= 0.5 + x * (N0 + N1*x^2 + N2*x^4)/(D0 + D1*x^2 + D2*x^4)
+ subject to the [0, 1] bounds.
+ The coefficients are directly derived by dividing the tanh() coefficients
+ by powers of two to get the correct scaling. The max error is around 1.5e-4
+ and is dominated by the reciprocal approximation (the max error of the
+ rational function is around 3e-5).
+ */
+static inline __m256 sigmoid8_approx(__m256 X)
+{
+ const __m256 N0 = _mm256_set1_ps(238.13200378f);
+ const __m256 N1 = _mm256_set1_ps(6.02452230f);
+ const __m256 N2 = _mm256_set1_ps(0.00950985f);
+ const __m256 D0 = _mm256_set1_ps(952.72399902f);
+ const __m256 D1 = _mm256_set1_ps(103.34200287f);
+ const __m256 D2 = _mm256_set1_ps(0.74287558f);
+ const __m256 half = _mm256_set1_ps(0.5);
+ const __m256 max_out = _mm256_set1_ps(1.f);
+ const __m256 min_out = _mm256_set1_ps(0.f);
+ __m256 X2, num, den;
+ X2 = _mm256_mul_ps(X, X);
+ num = _mm256_fmadd_ps(_mm256_fmadd_ps(N2, X2, N1), X2, N0);
+ den = _mm256_fmadd_ps(_mm256_fmadd_ps(D2, X2, D1), X2, D0);
+ num = _mm256_mul_ps(num, X);
+ den = _mm256_rcp_ps(den);
+ num = _mm256_fmadd_ps(num, den, half);
+ return _mm256_max_ps(min_out, _mm256_min_ps(max_out, num));
+}
+
+static inline float tanh_approx(float x)
+{
+ float out[8];
+ __m256 X, Y;
+ X = _mm256_set1_ps(x);
+ Y = tanh8_approx(X);
+ _mm256_storeu_ps(out, Y);
+ return out[0];
+}
+
+static inline float sigmoid_approx(float x)
+{
+ float out[8];
+ __m256 X, Y;
+ X = _mm256_set1_ps(x);
+ Y = sigmoid8_approx(X);
+ _mm256_storeu_ps(out, Y);
+ return out[0];
+}
+
+#else
+
+static inline __m128 tanh4_approx(__m128 X)
+{
+ const __m128 N0 = _mm_set1_ps(952.52801514f);
+ const __m128 N1 = _mm_set1_ps(96.39235687f);
+ const __m128 N2 = _mm_set1_ps(0.60863042f);
+ const __m128 D0 = _mm_set1_ps(952.72399902f);
+ const __m128 D1 = _mm_set1_ps(413.36801147f);
+ const __m128 D2 = _mm_set1_ps(11.88600922f);
+ const __m128 max_out = _mm_set1_ps(1.f);
+ const __m128 min_out = _mm_set1_ps(-1.f);
+ __m128 X2, num, den;
+ X2 = _mm_mul_ps(X, X);
+ num = _mm_fmadd_ps(_mm_fmadd_ps(N2, X2, N1), X2, N0);
+ den = _mm_fmadd_ps(_mm_fmadd_ps(D2, X2, D1), X2, D0);
+ num = _mm_mul_ps(num, X);
+ den = _mm_rcp_ps(den);
+ num = _mm_mul_ps(num, den);
+ return _mm_max_ps(min_out, _mm_min_ps(max_out, num));
+}
+
+static inline __m128 sigmoid4_approx(__m128 X)
+{
+ const __m128 N0 = _mm_set1_ps(238.13200378f);
+ const __m128 N1 = _mm_set1_ps(6.02452230f);
+ const __m128 N2 = _mm_set1_ps(0.00950985f);
+ const __m128 D0 = _mm_set1_ps(952.72399902f);
+ const __m128 D1 = _mm_set1_ps(103.34200287f);
+ const __m128 D2 = _mm_set1_ps(0.74287558f);
+ const __m128 half = _mm_set1_ps(0.5);
+ const __m128 max_out = _mm_set1_ps(1.f);
+ const __m128 min_out = _mm_set1_ps(0.f);
+ __m128 X2, num, den;
+ X2 = _mm_mul_ps(X, X);
+ num = _mm_fmadd_ps(_mm_fmadd_ps(N2, X2, N1), X2, N0);
+ den = _mm_fmadd_ps(_mm_fmadd_ps(D2, X2, D1), X2, D0);
+ num = _mm_mul_ps(num, X);
+ den = _mm_rcp_ps(den);
+ num = _mm_fmadd_ps(num, den, half);
+ return _mm_max_ps(min_out, _mm_min_ps(max_out, num));
+}
+
+static inline float tanh_approx(float x)
+{
+ float out[4];
+ __m128 X, Y;
+ X = _mm_set1_ps(x);
+ Y = tanh4_approx(X);
+ _mm_storeu_ps(out, Y);
+ return out[0];
+}
+
+static inline float sigmoid_approx(float x)
+{
+ float out[4];
+ __m128 X, Y;
+ X = _mm_set1_ps(x);
+ Y = sigmoid4_approx(X);
+ _mm_storeu_ps(out, Y);
+ return out[0];
+}
+
+#endif
+
+static inline float lpcnet_exp(float x)
+{
+ float out[8];
+ __m256 X, Y;
+ X = _mm256_set1_ps(x);
+ Y = exp8_approx(X);
+ _mm256_storeu_ps(out, Y);
+ return out[0];
+}
+
+static inline void softmax(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N-7;i+=8)
+ {
+ __m256 X, Y;
+ X = _mm256_loadu_ps(&x[i]);
+ Y = exp8_approx(X);
+ _mm256_storeu_ps(&y[i], Y);
+ }
+ for (;i<N;i++)
+ y[i] = lpcnet_exp(x[i]);
+}
+
+#ifdef __AVX__
+static inline void vec_tanh(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N-7;i+=8)
+ {
+ __m256 X, Y;
+ X = _mm256_loadu_ps(&x[i]);
+ Y = tanh8_approx(X);
+ _mm256_storeu_ps(&y[i], Y);
+ }
+ for (;i<N;i++)
+ {
+ y[i] = tanh_approx(x[i]);
+ }
+}
+
+static inline void vec_sigmoid(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N-7;i+=8)
+ {
+ __m256 X, Y;
+ X = _mm256_loadu_ps(&x[i]);
+ Y = sigmoid8_approx(X);
+ _mm256_storeu_ps(&y[i], Y);
+ }
+ for (;i<N;i++)
+ {
+ y[i] = sigmoid_approx(x[i]);
+ }
+}
+#else
+static inline void vec_tanh(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N-3;i+=4)
+ {
+ __m128 X, Y;
+ X = _mm_loadu_ps(&x[i]);
+ Y = tanh4_approx(X);
+ _mm_storeu_ps(&y[i], Y);
+ }
+ for (;i<N;i++)
+ {
+ y[i] = tanh_approx(x[i]);
+ }
+}
+
+static inline void vec_sigmoid(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N-3;i+=4)
+ {
+ __m128 X, Y;
+ X = _mm_loadu_ps(&x[i]);
+ Y = sigmoid4_approx(X);
+ _mm_storeu_ps(&y[i], Y);
+ }
+ for (;i<N;i++)
+ {
+ y[i] = sigmoid_approx(x[i]);
+ }
+}
+
+#endif
+
+#if defined(__AVXVNNI__) || defined(__AVX512VNNI__)
+
+#define opus_mm256_dpbusds_epi32(src, a, b) _mm256_dpbusds_epi32(src, a, b)
+
+#elif defined(__AVX2__)
+
+static inline __m256i opus_mm256_dpbusds_epi32(__m256i src, __m256i a, __m256i b) {
+ __m256i ones, tmp;
+ ones = _mm256_set1_epi16(1);
+ tmp = _mm256_maddubs_epi16(a, b);
+ tmp = _mm256_madd_epi16(tmp, ones);
+ return _mm256_add_epi32(src, tmp);
+}
+
+#elif defined(__SSSE3__)
+
+static inline mm256i_emu opus_mm256_dpbusds_epi32(mm256i_emu src, mm256i_emu a, mm256i_emu b) {
+ mm256i_emu ones, tmp;
+ ones = _mm256_set1_epi16(1);
+ tmp = _mm256_maddubs_epi16(a, b);
+ tmp = _mm256_madd_epi16(tmp, ones);
+ return _mm256_add_epi32(src, tmp);
+}
+
+#elif defined(__SSE2__)
+
+static inline __m128i mm_dpbusds_epi32(__m128i src, __m128i a, __m128i b) {
+ __m128i ah, al, bh, bl, tmp;
+ ah = _mm_srli_epi16(a, 8);
+ bh = _mm_srai_epi16(b, 8);
+ al = _mm_srli_epi16(_mm_slli_epi16(a, 8), 8);
+ bl = _mm_srai_epi16(_mm_slli_epi16(b, 8), 8);
+ tmp = _mm_add_epi32(_mm_madd_epi16(ah, bh), _mm_madd_epi16(al, bl));
+ return _mm_add_epi32(src, tmp);
+}
+
+static inline mm256i_emu opus_mm256_dpbusds_epi32(mm256i_emu src, mm256i_emu a, mm256i_emu b) {
+ mm256i_emu res;
+ res.hi = mm_dpbusds_epi32(src.hi, a.hi, b.hi);
+ res.lo = mm_dpbusds_epi32(src.lo, a.lo, b.lo);
+ return res;
+}
+
+
+#else
+
+#error "No optimizations in vec_avx.h. This should never happen. "
+#endif
+
+static inline void sgemv(float *out, const float *weights, int rows, int cols, int col_stride, const float *x)
+{
+ int i, j;
+ i=0;
+ for (;i<rows-15;i+=16)
+ {
+ float *y;
+ __m256 vy0, vy8;
+ y = &out[i];
+ vy0 = _mm256_setzero_ps();
+ vy8 = _mm256_setzero_ps();
+ for (j=0;j<cols;j++)
+ {
+ __m256 vxj;
+ __m256 vw;
+ vxj = _mm256_broadcast_ss(&x[j]);
+
+ vw = _mm256_loadu_ps(&weights[j*col_stride + i]);
+ vy0 = _mm256_fmadd_ps(vw, vxj, vy0);
+
+ vw = _mm256_loadu_ps(&weights[j*col_stride + i + 8]);
+ vy8 = _mm256_fmadd_ps(vw, vxj, vy8);
+ }
+ _mm256_storeu_ps (&y[0], vy0);
+ _mm256_storeu_ps (&y[8], vy8);
+ }
+ for (;i<rows-7;i+=8)
+ {
+ float *y;
+ __m256 vy0;
+ y = &out[i];
+ vy0 = _mm256_setzero_ps();
+ for (j=0;j<cols;j++)
+ {
+ __m256 vxj;
+ __m256 vw;
+ vxj = _mm256_broadcast_ss(&x[j]);
+
+ vw = _mm256_loadu_ps(&weights[j*col_stride + i]);
+ vy0 = _mm256_fmadd_ps(vw, vxj, vy0);
+ }
+ _mm256_storeu_ps (&y[0], vy0);
+ }
+ for (;i<rows-3;i+=4)
+ {
+ float *y;
+ __m128 vy0;
+ y = &out[i];
+ vy0 = _mm_setzero_ps();
+ for (j=0;j<cols;j++)
+ {
+ __m128 vxj;
+ __m128 vw;
+ vxj = _mm_set1_ps(x[j]);
+
+ vw = _mm_loadu_ps(&weights[j*col_stride + i]);
+ vy0 = _mm_fmadd_ps(vw, vxj, vy0);
+ }
+ _mm_storeu_ps (&y[0], vy0);
+ }
+ for (;i<rows;i++)
+ {
+ out[i] = 0;
+ for (j=0;j<cols;j++) out[i] += weights[j*col_stride + i]*x[j];
+ }
+}
+
+static inline void sparse_sgemv8x4(float *out, const float *weights, const int *idx, int rows, const float *x)
+{
+ int i, j;
+ for (i=0;i<rows;i+=8)
+ {
+ float *y;
+ int cols;
+ __m256 vy0;
+ y = &out[i];
+ vy0 = _mm256_setzero_ps();
+ cols = *idx++;
+ for (j=0;j<cols;j++)
+ {
+ int id;
+ __m256 vxj;
+ __m256 vw;
+ id = *idx++;
+ vxj = _mm256_broadcast_ss(&x[id]);
+ vw = _mm256_loadu_ps(&weights[0]);
+ vy0 = _mm256_fmadd_ps(vw, vxj, vy0);
+
+ vxj = _mm256_broadcast_ss(&x[id+1]);
+ vw = _mm256_loadu_ps(&weights[8]);
+ vy0 = _mm256_fmadd_ps(vw, vxj, vy0);
+
+ vxj = _mm256_broadcast_ss(&x[id+2]);
+ vw = _mm256_loadu_ps(&weights[16]);
+ vy0 = _mm256_fmadd_ps(vw, vxj, vy0);
+
+ vxj = _mm256_broadcast_ss(&x[id+3]);
+ vw = _mm256_loadu_ps(&weights[24]);
+ vy0 = _mm256_fmadd_ps(vw, vxj, vy0);
+
+ weights += 32;
+ }
+ _mm256_storeu_ps (&y[0], vy0);
+ }
+}
+
+static inline void sparse_cgemv8x4(float *_out, const opus_int8 *w, const int *idx, const float *scale, int rows, int cols, const float *_x)
+{
+ int i, j;
+ unsigned char x[MAX_INPUTS];
+ /*for (i=0;i<cols;i++) x[i] = 127+floor(.5+127*_x[i]);*/
+ vector_ps_to_epi8(x, _x, cols);
+ for (i=0;i<rows;i+=8)
+ {
+ int colblocks;
+ __m256i vy0;
+ __m256 vout;
+ colblocks = *idx++;
+ vy0 = _mm256_setzero_si256();
+ j=0;
+#if 1 /* Unrolling by 4 gives some gain, comment out if it does not. */
+ for (;j<colblocks-3;j+=4)
+ {
+ __m256i vxj;
+ __m256i vw;
+ vxj = _mm256_broadcastd_epi32(_mm_loadu_si32(&x[*idx++]));
+ vw = _mm256_loadu_si256((const __m256i *)(void*)w);
+ vy0 = opus_mm256_dpbusds_epi32(vy0, vxj, vw);
+ w += 32;
+ vxj = _mm256_broadcastd_epi32(_mm_loadu_si32(&x[*idx++]));
+ vw = _mm256_loadu_si256((const __m256i *)(void*)w);
+ vy0 = opus_mm256_dpbusds_epi32(vy0, vxj, vw);
+ w += 32;
+ vxj = _mm256_broadcastd_epi32(_mm_loadu_si32(&x[*idx++]));
+ vw = _mm256_loadu_si256((const __m256i *)(void*)w);
+ vy0 = opus_mm256_dpbusds_epi32(vy0, vxj, vw);
+ w += 32;
+ vxj = _mm256_broadcastd_epi32(_mm_loadu_si32(&x[*idx++]));
+ vw = _mm256_loadu_si256((const __m256i *)(void*)w);
+ vy0 = opus_mm256_dpbusds_epi32(vy0, vxj, vw);
+ w += 32;
+ }
+#endif
+ for (;j<colblocks;j++)
+ {
+ __m256i vxj;
+ __m256i vw;
+ vxj = _mm256_broadcastd_epi32(_mm_loadu_si32(&x[*idx++]));
+ vw = _mm256_loadu_si256((const __m256i *)(void*)w);
+ vy0 = opus_mm256_dpbusds_epi32(vy0, vxj, vw);
+ w += 32;
+ }
+ vout = _mm256_cvtepi32_ps(vy0);
+ vout = _mm256_mul_ps(vout, _mm256_loadu_ps(&scale[i]));
+ _mm256_storeu_ps(&_out[i], vout);
+ }
+}
+static inline void cgemv8x4(float *_out, const opus_int8 *w, const float *scale, int rows, int cols, const float *_x)
+{
+ int i, j;
+ unsigned char x[MAX_INPUTS];
+ /*for (i=0;i<cols;i++) x[i] = 127+floor(.5+127*_x[i]);*/
+ vector_ps_to_epi8(x, _x, cols);
+ for (i=0;i<rows;i+=8)
+ {
+ __m256i vy0;
+ __m256 vout;
+ vy0 = _mm256_setzero_si256();
+ j=0;
+#if 1 /* Unrolling by 4 gives some gain, comment out if it does not. */
+ for (;j<cols-12;j+=16)
+ {
+ __m256i vxj;
+ __m256i vw;
+ vxj = _mm256_broadcastd_epi32(_mm_loadu_si32(&x[j]));
+ vw = _mm256_loadu_si256((const __m256i *)(void*)w);
+ vy0 = opus_mm256_dpbusds_epi32(vy0, vxj, vw);
+ w += 32;
+ vxj = _mm256_broadcastd_epi32(_mm_loadu_si32(&x[j+4]));
+ vw = _mm256_loadu_si256((const __m256i *)(void*)w);
+ vy0 = opus_mm256_dpbusds_epi32(vy0, vxj, vw);
+ w += 32;
+ vxj = _mm256_broadcastd_epi32(_mm_loadu_si32(&x[j+8]));
+ vw = _mm256_loadu_si256((const __m256i *)(void*)w);
+ vy0 = opus_mm256_dpbusds_epi32(vy0, vxj, vw);
+ w += 32;
+ vxj = _mm256_broadcastd_epi32(_mm_loadu_si32(&x[j+12]));
+ vw = _mm256_loadu_si256((const __m256i *)(void*)w);
+ vy0 = opus_mm256_dpbusds_epi32(vy0, vxj, vw);
+ w += 32;
+ }
+#endif
+ for (;j<cols;j+=4)
+ {
+ __m256i vxj;
+ __m256i vw;
+ vxj = _mm256_broadcastd_epi32(_mm_loadu_si32(&x[j]));
+ vw = _mm256_loadu_si256((const __m256i *)(void*)w);
+ vy0 = opus_mm256_dpbusds_epi32(vy0, vxj, vw);
+ w += 32;
+ }
+ vout = _mm256_cvtepi32_ps(vy0);
+ vout = _mm256_mul_ps(vout, _mm256_loadu_ps(&scale[i]));
+ _mm256_storeu_ps(&_out[i], vout);
+ }
+}
+
+#define SCALE (128.f*127.f)
+#define SCALE_1 (1.f/128.f/127.f)
+#define USE_SU_BIAS
+
+
+#endif /*VEC_AVX_H*/
diff --git a/dnn/vec_neon.h b/dnn/vec_neon.h
new file mode 100644
index 00000000..acf49f47
--- /dev/null
+++ b/dnn/vec_neon.h
@@ -0,0 +1,473 @@
+/* Copyright (c) 2018 David Rowe
+ 2018 Mozilla
+ 2008-2011 Octasic Inc.
+ 2012-2017 Jean-Marc Valin */
+/*
+ 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 FOUNDATION 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.
+*/
+/* NEON support for ARM machines */
+
+#ifndef VEC_NEON_H
+#define VEC_NEON_H
+
+#include <arm_neon.h>
+#include "os_support.h"
+
+#if defined(__arm__) && !defined(__aarch64__)
+/* Emulate vcvtnq_s32_f32() for ARMv7 Neon. */
+static OPUS_INLINE int32x4_t vcvtnq_s32_f32(float32x4_t x) {
+ return vrshrq_n_s32(vcvtq_n_s32_f32(x, 8), 8);
+}
+
+static OPUS_INLINE int16x8_t vpaddq_s16(int16x8_t a, int16x8_t b) {
+ return vcombine_s16(vpadd_s16(vget_low_s16(a), vget_high_s16(a)), vpadd_s16(vget_low_s16(b), vget_high_s16(b)));
+}
+
+static OPUS_INLINE int16x8_t vmull_high_s8(int8x16_t a, int8x16_t b) {
+ return vmull_s8(vget_high_s8(a), vget_high_s8(b));
+}
+#endif
+
+#ifdef __ARM_FEATURE_FMA
+/* If we can, force the compiler to use an FMA instruction rather than break
+ vmlaq_f32() into fmul/fadd. */
+#define vmlaq_f32(a,b,c) vfmaq_f32(a,b,c)
+#endif
+
+#ifndef LPCNET_TEST
+static inline float32x4_t exp4_approx(float32x4_t x) {
+ int32x4_t i;
+ float32x4_t xf;
+
+ x = vmaxq_f32(vminq_f32(x, vdupq_n_f32(88.f)), vdupq_n_f32(-88.f));
+
+ /* express exp(x) as exp2(x/log(2)), add 127 for the exponent later */
+ x = vmlaq_f32(vdupq_n_f32(127.f), x, vdupq_n_f32(1.44269504f));
+
+ /* split into integer and fractional parts */
+ i = vcvtq_s32_f32(x);
+ xf = vcvtq_f32_s32(i);
+ x = vsubq_f32(x, xf);
+
+ float32x4_t K0 = vdupq_n_f32(0.99992522f);
+ float32x4_t K1 = vdupq_n_f32(0.69583354f);
+ float32x4_t K2 = vdupq_n_f32(0.22606716f);
+ float32x4_t K3 = vdupq_n_f32(0.078024523f);
+ float32x4_t Y = vmlaq_f32(K0, x, vmlaq_f32(K1, x, vmlaq_f32(K2, K3, x)));
+
+ /* compute 2^i */
+ float32x4_t exponent = vreinterpretq_f32_s32(vshlq_n_s32(i, 23));
+
+ Y = vmulq_f32(Y, exponent);
+ return Y;
+}
+
+static inline float32x4_t tanh4_approx(float32x4_t X)
+{
+ const float32x4_t N0 = vdupq_n_f32(952.52801514f);
+ const float32x4_t N1 = vdupq_n_f32(96.39235687f);
+ const float32x4_t N2 = vdupq_n_f32(0.60863042f);
+ const float32x4_t D0 = vdupq_n_f32(952.72399902f);
+ const float32x4_t D1 = vdupq_n_f32(413.36801147f);
+ const float32x4_t D2 = vdupq_n_f32(11.88600922f);
+ const float32x4_t max_out = vdupq_n_f32(1.f);
+ const float32x4_t min_out = vdupq_n_f32(-1.f);
+ float32x4_t X2, num, den;
+ X2 = vmulq_f32(X, X);
+ num = vmlaq_f32(N0, X2, vmlaq_f32(N1, N2, X2));
+ den = vmlaq_f32(D0, X2, vmlaq_f32(D1, D2, X2));
+ num = vmulq_f32(num, X);
+ den = vrecpeq_f32(den);
+ num = vmulq_f32(num, den);
+ return vmaxq_f32(min_out, vminq_f32(max_out, num));
+}
+
+static inline float32x4_t sigmoid4_approx(float32x4_t X)
+{
+ const float32x4_t N0 = vdupq_n_f32(238.13200378f);
+ const float32x4_t N1 = vdupq_n_f32(6.02452230f);
+ const float32x4_t N2 = vdupq_n_f32(0.00950985f);
+ const float32x4_t D0 = vdupq_n_f32(952.72399902f);
+ const float32x4_t D1 = vdupq_n_f32(103.34200287f);
+ const float32x4_t D2 = vdupq_n_f32(0.74287558f);
+ const float32x4_t half = vdupq_n_f32(0.5f);
+ const float32x4_t max_out = vdupq_n_f32(1.f);
+ const float32x4_t min_out = vdupq_n_f32(0.f);
+ float32x4_t X2, num, den;
+ X2 = vmulq_f32(X, X);
+ num = vmlaq_f32(N0, X2, vmlaq_f32(N1, N2, X2));
+ den = vmlaq_f32(D0, X2, vmlaq_f32(D1, D2, X2));
+ num = vmulq_f32(num, X);
+ den = vrecpeq_f32(den);
+ num = vmlaq_f32(half, num, den);
+ return vmaxq_f32(min_out, vminq_f32(max_out, num));
+}
+
+static inline float lpcnet_exp(float x)
+{
+ float out[4];
+ float32x4_t X, Y;
+ X = vdupq_n_f32(x);
+ Y = exp4_approx(X);
+ vst1q_f32(out, Y);
+ return out[0];
+}
+
+static inline float tanh_approx(float x)
+{
+ float out[4];
+ float32x4_t X, Y;
+ X = vdupq_n_f32(x);
+ Y = tanh4_approx(X);
+ vst1q_f32(out, Y);
+ return out[0];
+}
+
+static inline float sigmoid_approx(float x)
+{
+ float out[4];
+ float32x4_t X, Y;
+ X = vdupq_n_f32(x);
+ Y = sigmoid4_approx(X);
+ vst1q_f32(out, Y);
+ return out[0];
+}
+
+static inline void softmax(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N-3;i+=4)
+ {
+ float32x4_t X, Y;
+ X = vld1q_f32(&x[i]);
+ Y = exp4_approx(X);
+ vst1q_f32(&y[i], Y);
+ }
+ for (;i<N;i++)
+ y[i] = lpcnet_exp(x[i]);
+}
+
+static inline void vec_tanh(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N-3;i+=4)
+ {
+ float32x4_t X, Y;
+ X = vld1q_f32(&x[i]);
+ Y = tanh4_approx(X);
+ vst1q_f32(&y[i], Y);
+ }
+ for (;i<N;i++)
+ {
+ float ex2;
+ ex2 = lpcnet_exp(2*x[i]);
+ y[i] = (ex2-1)/(ex2+1);
+ }
+}
+
+static inline void vec_sigmoid(float *y, const float *x, int N)
+{
+ int i;
+ for (i=0;i<N-3;i+=4)
+ {
+ float32x4_t X, Y;
+ X = vld1q_f32(&x[i]);
+ Y = sigmoid4_approx(X);
+ vst1q_f32(&y[i], Y);
+ }
+ for (;i<N;i++)
+ {
+ float ex;
+ ex = lpcnet_exp(x[i]);
+ y[i] = (ex)/(ex+1);
+ }
+}
+#endif
+
+static inline void sgemv16x1(float *out, const float *weights, int rows, int cols, int col_stride, const float *x)
+{
+ int i, j;
+ for (i=0;i<rows;i+=16)
+ {
+ float * restrict y = &out[i];
+
+ /* keep y[0..15] in registers for duration of inner loop */
+
+ float32x4_t y0_3 = vdupq_n_f32(0);
+ float32x4_t y4_7 = vdupq_n_f32(0);
+ float32x4_t y8_11 = vdupq_n_f32(0);
+ float32x4_t y12_15 = vdupq_n_f32(0);
+
+ for (j=0;j<cols;j++)
+ {
+ const float * restrict w;
+ float32x4_t wvec0_3, wvec4_7, wvec8_11, wvec12_15;
+ float32x4_t xj;
+
+ w = &weights[j*col_stride + i];
+ wvec0_3 = vld1q_f32(&w[0]);
+ wvec4_7 = vld1q_f32(&w[4]);
+ wvec8_11 = vld1q_f32(&w[8]);
+ wvec12_15 = vld1q_f32(&w[12]);
+
+ xj = vld1q_dup_f32(&x[j]);
+
+ y0_3 = vmlaq_f32(y0_3, wvec0_3, xj);
+ y4_7 = vmlaq_f32(y4_7, wvec4_7, xj);
+ y8_11 = vmlaq_f32(y8_11, wvec8_11, xj);
+ y12_15 = vmlaq_f32(y12_15, wvec12_15, xj);
+ }
+
+ /* save y[0..15] back to memory */
+
+ vst1q_f32(&y[0], y0_3);
+ vst1q_f32(&y[4], y4_7);
+ vst1q_f32(&y[8], y8_11);
+ vst1q_f32(&y[12], y12_15);
+
+ }
+}
+
+static inline void sgemv8x1(float *out, const float *weights, int rows, int cols, int col_stride, const float *x)
+{
+ int i, j;
+ for (i=0;i<rows;i+=8)
+ {
+ float * restrict y = &out[i];
+
+ /* keep y[0..15] in registers for duration of inner loop */
+
+ float32x4_t y0_3 = vdupq_n_f32(0);
+ float32x4_t y4_7 = vdupq_n_f32(0);
+
+ for (j=0;j<cols;j++)
+ {
+ const float * restrict w;
+ float32x4_t wvec0_3, wvec4_7;
+ float32x4_t xj;
+
+ w = &weights[j*col_stride + i];
+ wvec0_3 = vld1q_f32(&w[0]);
+ wvec4_7 = vld1q_f32(&w[4]);
+
+ xj = vld1q_dup_f32(&x[j]);
+
+ y0_3 = vmlaq_f32(y0_3, wvec0_3, xj);
+ y4_7 = vmlaq_f32(y4_7, wvec4_7, xj);
+ }
+
+ /* save y[0..15] back to memory */
+
+ vst1q_f32(&y[0], y0_3);
+ vst1q_f32(&y[4], y4_7);
+ }
+}
+
+static inline void sgemv(float *out, const float *weights, int rows, int cols, int col_stride, const float *x)
+{
+ if ((rows&0xf) == 0) sgemv16x1(out, weights, rows, cols, col_stride, x);
+ else if ((rows&0x7) == 0) sgemv8x1(out, weights, rows, cols, col_stride, x);
+ else {
+ int i, j;
+ for (i=0;i<rows;i++)
+ {
+ out[i] = 0;
+ for (j=0;j<cols;j++) out[i] += weights[j*col_stride + i]*x[j];
+ }
+ }
+}
+
+/* Temporarily use unoptimized version */
+static inline void sparse_sgemv8x4(float *out, const float *w, const int *idx, int rows, const float *x)
+{
+ int i, j;
+ OPUS_CLEAR(out, rows);
+ for (i=0;i<rows;i+=8)
+ {
+ int cols;
+ cols = *idx++;
+ for (j=0;j<cols;j++)
+ {
+ int pos;
+ float * restrict y;
+ float xj0, xj1, xj2, xj3;
+ pos = (*idx++);
+ xj0 = x[pos+0];
+ xj1 = x[pos+1];
+ xj2 = x[pos+2];
+ xj3 = x[pos+3];
+ y = &out[i];
+ y[0] += w[0]*xj0;
+ y[1] += w[1]*xj0;
+ y[2] += w[2]*xj0;
+ y[3] += w[3]*xj0;
+ y[4] += w[4]*xj0;
+ y[5] += w[5]*xj0;
+ y[6] += w[6]*xj0;
+ y[7] += w[7]*xj0;
+
+ y[0] += w[8]*xj1;
+ y[1] += w[9]*xj1;
+ y[2] += w[10]*xj1;
+ y[3] += w[11]*xj1;
+ y[4] += w[12]*xj1;
+ y[5] += w[13]*xj1;
+ y[6] += w[14]*xj1;
+ y[7] += w[15]*xj1;
+
+ y[0] += w[16]*xj2;
+ y[1] += w[17]*xj2;
+ y[2] += w[18]*xj2;
+ y[3] += w[19]*xj2;
+ y[4] += w[20]*xj2;
+ y[5] += w[21]*xj2;
+ y[6] += w[22]*xj2;
+ y[7] += w[23]*xj2;
+
+ y[0] += w[24]*xj3;
+ y[1] += w[25]*xj3;
+ y[2] += w[26]*xj3;
+ y[3] += w[27]*xj3;
+ y[4] += w[28]*xj3;
+ y[5] += w[29]*xj3;
+ y[6] += w[30]*xj3;
+ y[7] += w[31]*xj3;
+ w += 32;
+ }
+ }
+}
+
+
+#define SCALE (128.f*127.f)
+#define SCALE_1 (1.f/128.f/127.f)
+
+#define MAX_INPUTS 2048
+#define MAX_OUTPUTS 8192
+
+#if __ARM_FEATURE_DOTPROD
+static inline int32x4_t vdotprod(int32x4_t acc, int8x16_t a, int8x16_t b) {
+ return vdotq_s32(acc, a, b);
+}
+#else
+static inline int32x4_t vdotprod(int32x4_t acc, int8x16_t a, int8x16_t b)
+{
+ return vpadalq_s16(acc, vpaddq_s16(vmull_s8(vget_low_s8(a), vget_low_s8(b)), vmull_high_s8(a, b)));
+}
+#endif
+
+static inline void cgemv8x4(float *_out, const opus_int8 *w, const float *scale, int rows, int cols, const float *_x)
+{
+ int i, j;
+ opus_int32 x_int[MAX_INPUTS/4];
+ opus_int8 *x = (opus_int8*) x_int;
+ const float32x4_t const127 = vdupq_n_f32(127.);
+ for (i=0;i<cols;i+=8) {
+ int32x4_t xi0, xi4;
+ int16x8_t x_short;
+ xi0 = vcvtnq_s32_f32(vmulq_f32(const127, vld1q_f32(&_x[i])));
+ xi4 = vcvtnq_s32_f32(vmulq_f32(const127, vld1q_f32(&_x[i+4])));
+ x_short = vcombine_s16(vmovn_s32(xi0), vmovn_s32(xi4));
+ vst1_s8(&x[i], vmovn_s16(x_short));
+ }
+ for (i=0;i<rows;i+=8)
+ {
+ int32x4_t acc0, acc1;
+ int32x4_t acc2, acc3;
+ acc0 = vdupq_n_s32(0);
+ acc1 = vdupq_n_s32(0);
+ acc2 = vdupq_n_s32(0);
+ acc3 = vdupq_n_s32(0);
+ j=0;
+ for (;j<cols-4;j+=8)
+ {
+ int8x16_t vw0, vw1, vw2, vw3, vx0, vx1;
+ vx0 = (int8x16_t)vld1q_dup_s32((int*)(void*)&x[j]);
+ vw0 = vld1q_s8(w);
+ vw1 = vld1q_s8(&w[16]);
+ acc0 = vdotprod(acc0, vw0, vx0);
+ acc1 = vdotprod(acc1, vw1, vx0);
+ vx1 = (int8x16_t)vld1q_dup_s32((int*)(void*)&x[j+4]);
+ vw2 = vld1q_s8(&w[32]);
+ vw3 = vld1q_s8(&w[48]);
+ acc2 = vdotprod(acc2, vw2, vx1);
+ acc3 = vdotprod(acc3, vw3, vx1);
+ w += 64;
+ }
+ acc0 = vaddq_s32(acc0, acc2);
+ acc1 = vaddq_s32(acc1, acc3);
+ for (;j<cols;j+=4)
+ {
+ int8x16_t vw0, vw1, vx;
+ vx = (int8x16_t)vld1q_dup_s32((int*)(void*)&x[j]);
+ vw0 = vld1q_s8(w);
+ vw1 = vld1q_s8(&w[16]);
+ acc0 = vdotprod(acc0, vw0, vx);
+ acc1 = vdotprod(acc1, vw1, vx);
+ w += 32;
+ }
+ vst1q_f32(&_out[i], vmulq_f32(vld1q_f32(&scale[i]), vcvtq_f32_s32(acc0)));
+ vst1q_f32(&_out[i+4], vmulq_f32(vld1q_f32(&scale[i+4]), vcvtq_f32_s32(acc1)));
+ }
+}
+
+static inline void sparse_cgemv8x4(float *_out, const opus_int8 *w, const int *idx, const float *scale, int rows, int cols, const float *_x)
+{
+ int i, j;
+ opus_int32 x_int[MAX_INPUTS/4];
+ opus_int8 *x = (opus_int8*) x_int;
+ const float32x4_t const127 = vdupq_n_f32(127.);
+ for (i=0;i<cols;i+=8) {
+ int32x4_t xi0, xi4;
+ int16x8_t x_short;
+ xi0 = vcvtnq_s32_f32(vmulq_f32(const127, vld1q_f32(&_x[i])));
+ xi4 = vcvtnq_s32_f32(vmulq_f32(const127, vld1q_f32(&_x[i+4])));
+ x_short = vcombine_s16(vmovn_s32(xi0), vmovn_s32(xi4));
+ vst1_s8(&x[i], vmovn_s16(x_short));
+ }
+ for (i=0;i<rows;i+=8)
+ {
+ int colblocks;
+ int32x4_t acc0, acc1;
+ acc0 = vdupq_n_s32(0);
+ acc1 = vdupq_n_s32(0);
+ colblocks = *idx++;
+ for (j=0;j<colblocks;j++)
+ {
+ int pos;
+ pos = (*idx++);
+ int8x16_t vw0, vw1, vx;
+ vx = (int8x16_t)vld1q_dup_s32((int*)(void*)&x[pos]);
+ vw0 = vld1q_s8(w);
+ vw1 = vld1q_s8(&w[16]);
+ acc0 = vdotprod(acc0, vw0, vx);
+ acc1 = vdotprod(acc1, vw1, vx);
+ w += 32;
+ }
+ vst1q_f32(&_out[i], vmulq_f32(vld1q_f32(&scale[i]), vcvtq_f32_s32(acc0)));
+ vst1q_f32(&_out[i+4], vmulq_f32(vld1q_f32(&scale[i+4]), vcvtq_f32_s32(acc1)));
+ }
+}
+
+
+#endif
diff --git a/dnn/write_lpcnet_weights.c b/dnn/write_lpcnet_weights.c
new file mode 100644
index 00000000..2f80b962
--- /dev/null
+++ b/dnn/write_lpcnet_weights.c
@@ -0,0 +1,97 @@
+/* Copyright (c) 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stddef.h>
+#include "nnet.h"
+#include "os_support.h"
+#include "arch.h"
+
+/* This is a bit of a hack because we need to build nnet_data.c and plc_data.c without USE_WEIGHTS_FILE,
+ but USE_WEIGHTS_FILE is defined in config.h. */
+#undef HAVE_CONFIG_H
+#ifdef USE_WEIGHTS_FILE
+#undef USE_WEIGHTS_FILE
+#endif
+#include "pitchdnn_data.c"
+#include "fargan_data.c"
+#include "plc_data.c"
+#include "dred_rdovae_enc_data.c"
+#include "dred_rdovae_dec_data.c"
+#ifdef ENABLE_OSCE
+#include "lace_data.c"
+#include "nolace_data.c"
+#endif
+
+void write_weights(const WeightArray *list, FILE *fout)
+{
+ int i=0;
+ unsigned char zeros[WEIGHT_BLOCK_SIZE] = {0};
+ while (list[i].name != NULL) {
+ WeightHead h;
+ if (strlen(list[i].name) >= sizeof(h.name) - 1) {
+ printf("[write_weights] warning: name %s too long\n", list[i].name);
+ }
+ memcpy(h.head, "DNNw", 4);
+ h.version = WEIGHT_BLOB_VERSION;
+ h.type = list[i].type;
+ h.size = list[i].size;
+ h.block_size = (h.size+WEIGHT_BLOCK_SIZE-1)/WEIGHT_BLOCK_SIZE*WEIGHT_BLOCK_SIZE;
+ OPUS_CLEAR(h.name, sizeof(h.name));
+ strncpy(h.name, list[i].name, sizeof(h.name));
+ h.name[sizeof(h.name)-1] = 0;
+ celt_assert(sizeof(h) == WEIGHT_BLOCK_SIZE);
+ fwrite(&h, 1, WEIGHT_BLOCK_SIZE, fout);
+ fwrite(list[i].data, 1, h.size, fout);
+ fwrite(zeros, 1, h.block_size-h.size, fout);
+ i++;
+ }
+}
+
+int main(void)
+{
+ FILE *fout = fopen("weights_blob.bin", "w");
+ write_weights(pitchdnn_arrays, fout);
+ write_weights(fargan_arrays, fout);
+ write_weights(plcmodel_arrays, fout);
+ write_weights(rdovaeenc_arrays, fout);
+ write_weights(rdovaedec_arrays, fout);
+#ifdef ENABLE_OSCE
+#ifndef DISABLE_LACE
+ write_weights(lacelayers_arrays, fout);
+#endif
+#ifndef DISABLE_NOLACE
+ write_weights(nolacelayers_arrays, fout);
+#endif
+#endif
+ fclose(fout);
+ return 0;
+}
diff --git a/dnn/x86/dnn_x86.h b/dnn/x86/dnn_x86.h
new file mode 100644
index 00000000..f2183327
--- /dev/null
+++ b/dnn/x86/dnn_x86.h
@@ -0,0 +1,121 @@
+/* Copyright (c) 2011-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifndef DNN_X86_H
+#define DNN_X86_H
+
+#include "cpu_support.h"
+#include "opus_types.h"
+
+#if defined(OPUS_X86_MAY_HAVE_SSE2)
+void compute_linear_sse2(const LinearLayer *linear, float *out, const float *in);
+void compute_activation_sse2(float *output, const float *input, int N, int activation);
+void compute_conv2d_sse2(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation);
+#endif
+
+#if defined(OPUS_X86_MAY_HAVE_SSE4_1)
+void compute_linear_sse4_1(const LinearLayer *linear, float *out, const float *in);
+void compute_activation_sse4_1(float *output, const float *input, int N, int activation);
+void compute_conv2d_sse4_1(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation);
+#endif
+
+#if defined(OPUS_X86_MAY_HAVE_AVX2)
+void compute_linear_avx2(const LinearLayer *linear, float *out, const float *in);
+void compute_activation_avx2(float *output, const float *input, int N, int activation);
+void compute_conv2d_avx2(const Conv2dLayer *conv, float *out, float *mem, const float *in, int height, int hstride, int activation);
+#endif
+
+
+#if defined(OPUS_X86_PRESUME_AVX2)
+
+#define OVERRIDE_COMPUTE_LINEAR
+#define compute_linear(linear, out, in, arch) ((void)(arch),compute_linear_avx2(linear, out, in))
+#define OVERRIDE_COMPUTE_ACTIVATION
+#define compute_activation(output, input, N, activation, arch) ((void)(arch),compute_activation_avx2(output, input, N, activation))
+#define OVERRIDE_COMPUTE_CONV2D
+#define compute_conv2d(conv, out, mem, in, height, hstride, activation, arch) ((void)(arch),compute_conv2d_avx2(conv, out, mem, in, height, hstride, activation))
+
+#elif defined(OPUS_X86_PRESUME_SSE4_1) && !defined(OPUS_X86_MAY_HAVE_AVX2)
+
+#define OVERRIDE_COMPUTE_LINEAR
+#define compute_linear(linear, out, in, arch) ((void)(arch),compute_linear_sse4_1(linear, out, in))
+#define OVERRIDE_COMPUTE_ACTIVATION
+#define compute_activation(output, input, N, activation, arch) ((void)(arch),compute_activation_sse4_1(output, input, N, activation))
+#define OVERRIDE_COMPUTE_CONV2D
+#define compute_conv2d(conv, out, mem, in, height, hstride, activation, arch) ((void)(arch),compute_conv2d_sse4_1(conv, out, mem, in, height, hstride, activation))
+
+#elif defined(OPUS_X86_PRESUME_SSE2) && !defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(OPUS_X86_MAY_HAVE_SSE4_1)
+
+#define OVERRIDE_COMPUTE_LINEAR
+#define compute_linear(linear, out, in, arch) ((void)(arch),compute_linear_sse2(linear, out, in))
+#define OVERRIDE_COMPUTE_ACTIVATION
+#define compute_activation(output, input, N, activation, arch) ((void)(arch),compute_activation_sse2(output, input, N, activation))
+#define OVERRIDE_COMPUTE_CONV2D
+#define compute_conv2d(conv, out, mem, in, height, hstride, activation, arch) ((void)(arch),compute_conv2d_sse2(conv, out, mem, in, height, hstride, activation))
+
+#elif defined(OPUS_HAVE_RTCD) && (defined(OPUS_X86_MAY_HAVE_AVX2) || defined(OPUS_X86_MAY_HAVE_SSE4_1) || defined(OPUS_X86_MAY_HAVE_SSE2))
+
+extern void (*const DNN_COMPUTE_LINEAR_IMPL[OPUS_ARCHMASK + 1])(
+ const LinearLayer *linear,
+ float *out,
+ const float *in
+ );
+#define OVERRIDE_COMPUTE_LINEAR
+#define compute_linear(linear, out, in, arch) \
+ ((*DNN_COMPUTE_LINEAR_IMPL[(arch) & OPUS_ARCHMASK])(linear, out, in))
+
+
+extern void (*const DNN_COMPUTE_ACTIVATION_IMPL[OPUS_ARCHMASK + 1])(
+ float *output,
+ const float *input,
+ int N,
+ int activation
+ );
+#define OVERRIDE_COMPUTE_ACTIVATION
+#define compute_activation(output, input, N, activation, arch) \
+ ((*DNN_COMPUTE_ACTIVATION_IMPL[(arch) & OPUS_ARCHMASK])(output, input, N, activation))
+
+
+extern void (*const DNN_COMPUTE_CONV2D_IMPL[OPUS_ARCHMASK + 1])(
+ const Conv2dLayer *conv,
+ float *out,
+ float *mem,
+ const float *in,
+ int height,
+ int hstride,
+ int activation
+ );
+#define OVERRIDE_COMPUTE_CONV2D
+#define compute_conv2d(conv, out, mem, in, height, hstride, activation, arch) \
+ ((*DNN_COMPUTE_CONV2D_IMPL[(arch) & OPUS_ARCHMASK])(conv, out, mem, in, height, hstride, activation))
+
+
+#endif
+
+
+
+#endif /* DNN_X86_H */
diff --git a/dnn/x86/nnet_avx2.c b/dnn/x86/nnet_avx2.c
new file mode 100644
index 00000000..41037fcc
--- /dev/null
+++ b/dnn/x86/nnet_avx2.c
@@ -0,0 +1,40 @@
+/* Copyright (c) 2018-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "x86/x86_arch_macros.h"
+
+#ifndef __AVX2__
+#error nnet_avx2.c is being compiled without AVX2 enabled
+#endif
+
+#define RTCD_ARCH avx2
+
+#include "nnet_arch.h"
diff --git a/dnn/x86/nnet_sse2.c b/dnn/x86/nnet_sse2.c
new file mode 100644
index 00000000..447b947c
--- /dev/null
+++ b/dnn/x86/nnet_sse2.c
@@ -0,0 +1,40 @@
+/* Copyright (c) 2018-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "x86/x86_arch_macros.h"
+
+#ifndef __SSE2__
+#error nnet_sse2.c is being compiled without SSE2 enabled
+#endif
+
+#define RTCD_ARCH sse2
+
+#include "nnet_arch.h"
diff --git a/dnn/x86/nnet_sse4_1.c b/dnn/x86/nnet_sse4_1.c
new file mode 100644
index 00000000..224926e5
--- /dev/null
+++ b/dnn/x86/nnet_sse4_1.c
@@ -0,0 +1,40 @@
+/* Copyright (c) 2018-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "x86/x86_arch_macros.h"
+
+#ifndef __SSE4_1__
+#error nnet_sse4_1.c is being compiled without SSE4.1 enabled
+#endif
+
+#define RTCD_ARCH sse4_1
+
+#include "nnet_arch.h"
diff --git a/dnn/x86/x86_dnn_map.c b/dnn/x86/x86_dnn_map.c
new file mode 100644
index 00000000..d673e134
--- /dev/null
+++ b/dnn/x86/x86_dnn_map.c
@@ -0,0 +1,83 @@
+/* Copyright (c) 2018-2019 Mozilla
+ 2023 Amazon */
+/*
+ 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 FOUNDATION 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "x86/x86cpu.h"
+#include "nnet.h"
+
+#if defined(OPUS_HAVE_RTCD)
+
+#if (defined(OPUS_X86_MAY_HAVE_SSE2) && !defined(OPUS_X86_PRESUME_AVX2))
+
+void (*const DNN_COMPUTE_LINEAR_IMPL[OPUS_ARCHMASK + 1])(
+ const LinearLayer *linear,
+ float *out,
+ const float *in
+) = {
+ compute_linear_c, /* non-sse */
+ compute_linear_c,
+ MAY_HAVE_SSE2(compute_linear),
+ MAY_HAVE_SSE4_1(compute_linear), /* sse4.1 */
+ MAY_HAVE_AVX2(compute_linear) /* avx */
+};
+
+void (*const DNN_COMPUTE_ACTIVATION_IMPL[OPUS_ARCHMASK + 1])(
+ float *output,
+ const float *input,
+ int N,
+ int activation
+) = {
+ compute_activation_c, /* non-sse */
+ compute_activation_c,
+ MAY_HAVE_SSE2(compute_activation),
+ MAY_HAVE_SSE4_1(compute_activation), /* sse4.1 */
+ MAY_HAVE_AVX2(compute_activation) /* avx */
+};
+
+void (*const DNN_COMPUTE_CONV2D_IMPL[OPUS_ARCHMASK + 1])(
+ const Conv2dLayer *conv,
+ float *out,
+ float *mem,
+ const float *in,
+ int height,
+ int hstride,
+ int activation
+) = {
+ compute_conv2d_c, /* non-sse */
+ compute_conv2d_c,
+ MAY_HAVE_SSE2(compute_conv2d),
+ MAY_HAVE_SSE4_1(compute_conv2d), /* sse4.1 */
+ MAY_HAVE_AVX2(compute_conv2d) /* avx */
+};
+
+#endif
+
+
+#endif
diff --git a/doc/build_isobmff.sh b/doc/build_isobmff.sh
index 95ea202e..676ca4e0 100755
--- a/doc/build_isobmff.sh
+++ b/doc/build_isobmff.sh
@@ -46,5 +46,5 @@ cat ${HTML} | sed -e 's/ *$//g' > ${HTML}+ && mv ${HTML}+ ${HTML}
cat ${CSS} | sed -e 's/ *$//g' > ${CSS}+ && mv ${CSS}+ ${CSS}
-VERSION=$(fgrep Version ${HTML} | sed 's/.*Version \([0-9]\.[0-9]\.[0-9]\).*/\1/')
+VERSION=$(grep -F Version ${HTML} | sed 's/.*Version \([0-9]\.[0-9]\.[0-9]\).*/\1/')
echo Now at version ${VERSION}
diff --git a/doc/draft-ietf-codec-opus-update.xml b/doc/draft-ietf-codec-opus-update.xml
index 3124e22c..0dfb16bf 100644
--- a/doc/draft-ietf-codec-opus-update.xml
+++ b/doc/draft-ietf-codec-opus-update.xml
@@ -109,7 +109,7 @@
+ sizeof(((silk_decoder *)decState)->sStereo));
+ /* Not strictly needed, but it's cleaner that way */
+ ((silk_decoder *)decState)->prev_decode_only_middle = 0;
-
+
return ret;
}
<CODE ENDS>
@@ -189,29 +189,29 @@
RESAMPLER_ORDER_FIR_12 ];
+ opus_int16 buf[ 2*RESAMPLER_MAX_BATCH_SIZE_IN + \
RESAMPLER_ORDER_FIR_12 ];
-
+
/* Copy buffered samples to start of buffer */
- silk_memcpy( buf, S->sFIR, RESAMPLER_ORDER_FIR_12 \
* sizeof( opus_int32 ) );
+ silk_memcpy( buf, S->sFIR, RESAMPLER_ORDER_FIR_12 \
* sizeof( opus_int16 ) );
-
+
/* Iterate over blocks of frameSizeIn input samples */
index_increment_Q16 = S->invRatio_Q16;
while( 1 ) {
nSamplesIn = silk_min( inLen, S->batchSize );
-
+
/* Upsample 2x */
silk_resampler_private_up2_HQ( S->sIIR, &buf[ \
RESAMPLER_ORDER_FIR_12 ], in, nSamplesIn );
-
+
max_index_Q16 = silk_LSHIFT32( nSamplesIn, 16 + 1 \
); /* + 1 because 2x upsampling */
out = silk_resampler_private_IIR_FIR_INTERPOL( out, \
buf, max_index_Q16, index_increment_Q16 );
in += nSamplesIn;
inLen -= nSamplesIn;
-
+
if( inLen > 0 ) {
/* More iterations to do; copy last part of \
filtered signal to beginning of buffer */
@@ -223,7 +223,7 @@ RESAMPLER_ORDER_FIR_12 * sizeof( opus_int16 ) );
break;
}
}
-
+
/* Copy last part of filtered signal to the state for \
the next call */
- silk_memcpy( S->sFIR, &buf[ nSamplesIn << 1 ], \
@@ -284,7 +284,7 @@ rc_mult2 ), mult2Q);
NLSF_Q15[i-1] + NDeltaMin_Q15[i] );
+ NLSF_Q15[i] = silk_max_int( NLSF_Q15[i], \
silk_ADD_SAT16( NLSF_Q15[i-1], NDeltaMin_Q15[i] ) );
-
+
/* Last NLSF should be no higher than 1 - NDeltaMin[L] */
<CODE ENDS>
]]></artwork>
@@ -334,13 +334,13 @@ silk_ADD_SAT16( NLSF_Q15[i-1], NDeltaMin_Q15[i] ) );
<CODE BEGINS>
b = 0;
}
-
+
- if (resynth && M*eBands[i]-N >= M*eBands[start] && \
(update_lowband || lowband_offset==0))
+ if (resynth && (M*eBands[i]-N >= M*eBands[start] || \
i==start+1) && (update_lowband || lowband_offset==0))
lowband_offset = i;
-
+
+ if (i == start+1)
+ {
+ int n1, n2;
diff --git a/doc/release.txt b/doc/release.txt
index 411ab7f4..ad5afb69 100644
--- a/doc/release.txt
+++ b/doc/release.txt
@@ -21,7 +21,7 @@
and update the links.
- Update /topic in #opus IRC channel.
-Releases are commited to https://svn.xiph.org/releases/opus/
+Releases are committed to https://svn.xiph.org/releases/opus/
which propagates to downloads.xiph.org, and copied manually
to https://archive.mozilla.org/pub/opus/
diff --git a/include/opus.h b/include/opus.h
index 0c69c627..eadeda75 100644
--- a/include/opus.h
+++ b/include/opus.h
@@ -103,7 +103,7 @@ extern "C" {
* @endcode
*
* where opus_encoder_get_size() returns the required size for the encoder state. Note that
- * future versions of this code may change the size, so no assuptions should be made about it.
+ * future versions of this code may change the size, so no assumptions should be made about it.
*
* The encoder state is always continuous in memory and only a shallow copy is sufficient
* to copy it (e.g. memcpy())
@@ -357,7 +357,7 @@ OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NON
* error = opus_decoder_init(dec, Fs, channels);
* @endcode
* where opus_decoder_get_size() returns the required size for the decoder state. Note that
- * future versions of this code may change the size, so no assuptions should be made about it.
+ * future versions of this code may change the size, so no assumptions should be made about it.
*
* The decoder state is always continuous in memory and only a shallow copy is sufficient
* to copy it (e.g. memcpy())
@@ -398,6 +398,21 @@ OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NON
*/
typedef struct OpusDecoder OpusDecoder;
+/** Opus DRED decoder.
+ * This contains the complete state of an Opus DRED decoder.
+ * It is position independent and can be freely copied.
+ * @see opus_dred_decoder_create,opus_dred_decoder_init
+ */
+typedef struct OpusDREDDecoder OpusDREDDecoder;
+
+
+/** Opus DRED state.
+ * This contains the complete state of an Opus DRED packet.
+ * It is position independent and can be freely copied.
+ * @see opus_dred_create,opus_dred_init
+ */
+typedef struct OpusDRED OpusDRED;
+
/** Gets the size of an <code>OpusDecoder</code> structure.
* @param [in] channels <tt>int</tt>: Number of channels.
* This must be 1 or 2.
@@ -511,6 +526,101 @@ OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NON
*/
OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st);
+/** Gets the size of an <code>OpusDREDDecoder</code> structure.
+ * @returns The size in bytes.
+ */
+OPUS_EXPORT int opus_dred_decoder_get_size(void);
+
+/** Allocates and initializes an OpusDREDDecoder state.
+ * @param [out] error <tt>int*</tt>: #OPUS_OK Success or @ref opus_errorcodes
+ */
+OPUS_EXPORT OpusDREDDecoder *opus_dred_decoder_create(int *error);
+
+/** Initializes an <code>OpusDREDDecoder</code> state.
+ * @param[in] dec <tt>OpusDREDDecoder*</tt>: State to be initialized.
+ */
+OPUS_EXPORT int opus_dred_decoder_init(OpusDREDDecoder *dec);
+
+/** Frees an <code>OpusDREDDecoder</code> allocated by opus_dred_decoder_create().
+ * @param[in] dec <tt>OpusDREDDecoder*</tt>: State to be freed.
+ */
+OPUS_EXPORT void opus_dred_decoder_destroy(OpusDREDDecoder *dec);
+
+/** Perform a CTL function on an Opus DRED decoder.
+ *
+ * Generally the request and subsequent arguments are generated
+ * by a convenience macro.
+ * @param dred_dec <tt>OpusDREDDecoder*</tt>: DRED Decoder state.
+ * @param request This and all remaining parameters should be replaced by one
+ * of the convenience macros in @ref opus_genericctls or
+ * @ref opus_decoderctls.
+ * @see opus_genericctls
+ * @see opus_decoderctls
+ */
+OPUS_EXPORT int opus_dred_decoder_ctl(OpusDREDDecoder *dred_dec, int request, ...);
+
+/** Gets the size of an <code>OpusDRED</code> structure.
+ * @returns The size in bytes.
+ */
+OPUS_EXPORT int opus_dred_get_size(void);
+
+/** Allocates and initializes a DRED state.
+ * @param [out] error <tt>int*</tt>: #OPUS_OK Success or @ref opus_errorcodes
+ */
+OPUS_EXPORT OpusDRED *opus_dred_alloc(int *error);
+
+/** Frees an <code>OpusDRED</code> allocated by opus_dred_create().
+ * @param[in] dec <tt>OpusDRED*</tt>: State to be freed.
+ */
+OPUS_EXPORT void opus_dred_free(OpusDRED *dec);
+
+/** Decode an Opus DRED packet.
+ * @param [in] dred_dec <tt>OpusDRED*</tt>: DRED Decoder state
+ * @param [in] dred <tt>OpusDRED*</tt>: DRED state
+ * @param [in] data <tt>char*</tt>: Input payload
+ * @param [in] len <tt>opus_int32</tt>: Number of bytes in payload
+ * @param [in] max_dred_samples <tt>opus_int32</tt>: Maximum number of DRED samples that may be needed (if available in the packet).
+ * @param [in] sampling_rate <tt>opus_int32</tt>: Sampling rate used for max_dred_samples argument. Needs not match the actual sampling rate of the decoder.
+ * @param [out] dred_end <tt>opus_int32*</tt>: Number of non-encoded (silence) samples between the DRED timestamp and the last DRED sample.
+ * @param [in] defer_processing <tt>int</tt>: Flag (0 or 1). If set to one, the CPU-intensive part of the DRED decoding is deferred until opus_dred_process() is called.
+ * @returns Offset (positive) of the first decoded DRED samples, zero if no DRED is present, or @ref opus_errorcodes
+ */
+OPUS_EXPORT int opus_dred_parse(OpusDREDDecoder *dred_dec, OpusDRED *dred, const unsigned char *data, opus_int32 len, opus_int32 max_dred_samples, opus_int32 sampling_rate, int *dred_end, int defer_processing) OPUS_ARG_NONNULL(1);
+
+/** Finish decoding an Opus DRED packet. The function only needs to be called if opus_dred_parse() was called with defer_processing=1.
+ * The source and destination will often be the same DRED state.
+ * @param [in] dred_dec <tt>OpusDRED*</tt>: DRED Decoder state
+ * @param [in] src <tt>OpusDRED*</tt>: Source DRED state to start the processing from.
+ * @param [out] dst <tt>OpusDRED*</tt>: Destination DRED state to store the updated state after processing.
+ * @returns @ref opus_errorcodes
+ */
+OPUS_EXPORT int opus_dred_process(OpusDREDDecoder *dred_dec, const OpusDRED *src, OpusDRED *dst);
+
+/** Decode audio from an Opus DRED packet with floating point output.
+ * @param [in] st <tt>OpusDecoder*</tt>: Decoder state
+ * @param [in] dred <tt>OpusDRED*</tt>: DRED state
+ * @param [in] dred_offset <tt>opus_int32</tt>: position of the redundancy to decode (in samples before the beginning of the real audio data in the packet).
+ * @param [out] pcm <tt>opus_int16*</tt>: Output signal (interleaved if 2 channels). length
+ * is frame_size*channels*sizeof(opus_int16)
+ * @param [in] frame_size Number of samples per channel to decode in \a pcm.
+ * frame_size <b>must</b> be a multiple of 2.5 ms.
+ * @returns Number of decoded samples or @ref opus_errorcodes
+ */
+OPUS_EXPORT int opus_decoder_dred_decode(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, opus_int16 *pcm, opus_int32 frame_size);
+
+/** Decode audio from an Opus DRED packet with floating point output.
+ * @param [in] st <tt>OpusDecoder*</tt>: Decoder state
+ * @param [in] dred <tt>OpusDRED*</tt>: DRED state
+ * @param [in] dred_offset <tt>opus_int32</tt>: position of the redundancy to decode (in samples before the beginning of the real audio data in the packet).
+ * @param [out] pcm <tt>float*</tt>: Output signal (interleaved if 2 channels). length
+ * is frame_size*channels*sizeof(float)
+ * @param [in] frame_size Number of samples per channel to decode in \a pcm.
+ * frame_size <b>must</b> be a multiple of 2.5 ms.
+ * @returns Number of decoded samples or @ref opus_errorcodes
+ */
+OPUS_EXPORT int opus_decoder_dred_decode_float(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, float *pcm, opus_int32 frame_size);
+
+
/** Parse an opus packet into one or more frames.
* Opus_decode will perform this operation internally so most applications do
* not need to use this function.
@@ -583,6 +693,14 @@ OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned
*/
OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1);
+/** Checks whether an Opus packet has LBRR.
+ * @param [in] packet <tt>char*</tt>: Opus packet
+ * @param [in] len <tt>opus_int32</tt>: Length of packet
+ * @returns 1 is LBRR is present, 0 otherwise
+ * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type
+ */
+OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_has_lbrr(const unsigned char packet[], opus_int32 len);
+
/** Gets the number of samples of an Opus packet.
* @param [in] dec <tt>OpusDecoder*</tt>: Decoder state
* @param [in] packet <tt>char*</tt>: Opus packet
diff --git a/include/opus_defines.h b/include/opus_defines.h
index 94b9e0d9..cd8f4dde 100644
--- a/include/opus_defines.h
+++ b/include/opus_defines.h
@@ -169,15 +169,32 @@ extern "C" {
#define OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST 4046
#define OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST 4047
#define OPUS_GET_IN_DTX_REQUEST 4049
+#define OPUS_SET_DRED_DURATION_REQUEST 4050
+#define OPUS_GET_DRED_DURATION_REQUEST 4051
+#define OPUS_SET_DNN_BLOB_REQUEST 4052
+/*#define OPUS_GET_DNN_BLOB_REQUEST 4053 */
/** Defines for the presence of extended APIs. */
#define OPUS_HAVE_OPUS_PROJECTION_H
/* Macros to trigger compilation errors when the wrong types are provided to a CTL */
#define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x))
+
+#ifdef DISABLE_PTR_CHECK
+/* Disable checks to prevent ubsan from complaining about NULL checks
+ in test_opus_api. */
+#define __opus_check_int_ptr(ptr) (ptr)
+#define __opus_check_uint_ptr(ptr) (ptr)
+#define __opus_check_uint8_ptr(ptr) (ptr)
+#define __opus_check_val16_ptr(ptr) (ptr)
+#define __opus_check_void_ptr(ptr) (ptr)
+#else
#define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr)))
#define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr)))
+#define __opus_check_uint8_ptr(ptr) ((ptr) + ((ptr) - (opus_uint8*)(ptr)))
#define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr)))
+#define __opus_check_void_ptr(x) ((void)((void *)0 == (x)), (x))
+#endif
/** @endcond */
/** @defgroup opus_ctlvalues Pre-defined values for CTL interface
@@ -620,6 +637,18 @@ extern "C" {
* @hideinitializer */
#define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x)
+/** If non-zero, enables Deep Redundancy (DRED) and use the specified maximum number of 10-ms redundant frames
+ * @hideinitializer */
+#define OPUS_SET_DRED_DURATION(x) OPUS_SET_DRED_DURATION_REQUEST, __opus_check_int(x)
+/** Gets the encoder's configured Deep Redundancy (DRED) maximum number of frames.
+ * @hideinitializer */
+#define OPUS_GET_DRED_DURATION(x) OPUS_GET_DRED_DURATION_REQUEST, __opus_check_int_ptr(x)
+
+/** Provide external DNN weights from binary object (only when explicitly built without the weights)
+ * @hideinitializer */
+#define OPUS_SET_DNN_BLOB(data, len) OPUS_SET_DNN_BLOB_REQUEST, __opus_check_void_ptr(data), __opus_check_int(len)
+
+
/**@}*/
/** @defgroup opus_genericctls Generic CTLs
diff --git a/include/opus_multistream.h b/include/opus_multistream.h
index babcee69..824cc55a 100644
--- a/include/opus_multistream.h
+++ b/include/opus_multistream.h
@@ -143,7 +143,7 @@ extern "C" {
* <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
* channel ordering</a>. A decoder may wish to apply an additional permutation
* to the mapping the encoder used to achieve a different output channel
- * order (e.g. for outputing in WAV order).
+ * order (e.g. for outputting in WAV order).
*
* Each multistream packet contains an Opus packet for each stream, and all of
* the Opus packets in a single multistream packet must have the same
diff --git a/lpcnet_headers.mk b/lpcnet_headers.mk
new file mode 100644
index 00000000..520274d7
--- /dev/null
+++ b/lpcnet_headers.mk
@@ -0,0 +1,44 @@
+DEEP_PLC_HEAD = \
+dnn/lpcnet.h \
+dnn/burg.h \
+dnn/common.h \
+dnn/freq.h \
+dnn/fargan.h \
+dnn/fargan_data.h \
+dnn/lpcnet_private.h \
+dnn/nnet.h \
+dnn/plc_data.h \
+dnn/vec.h \
+dnn/vec_avx.h \
+dnn/vec_neon.h \
+dnn/pitchdnn.h \
+dnn/pitchdnn_data.h \
+dnn/x86/dnn_x86.h \
+dnn/nnet_arch.h \
+dnn/arm/dnn_arm.h
+
+DRED_HEAD = \
+dnn/dred_coding.h \
+dnn/dred_config.h \
+dnn/dred_decoder.h \
+dnn/dred_encoder.h \
+dnn/dred_rdovae.h \
+dnn/dred_rdovae_constants.h \
+dnn/dred_rdovae_enc.h \
+dnn/dred_rdovae_enc_data.h \
+dnn/dred_rdovae_dec.h \
+dnn/dred_rdovae_dec_data.h \
+dnn/dred_rdovae_stats_data.h
+
+OSCE_HEAD= \
+dnn/osce.h \
+dnn/osce_config.h \
+dnn/osce_structs.h \
+dnn/osce_features.h \
+dnn/nndsp.h \
+dnn/lace_data.h \
+dnn/nolace_data.h
+
+LOSSGEN_HEAD = \
+dnn/lossgen.h \
+dnn/lossgen_data.h
diff --git a/lpcnet_sources.mk b/lpcnet_sources.mk
new file mode 100644
index 00000000..991a4109
--- /dev/null
+++ b/lpcnet_sources.mk
@@ -0,0 +1,44 @@
+DEEP_PLC_SOURCES = \
+dnn/burg.c \
+dnn/freq.c \
+dnn/fargan.c \
+dnn/fargan_data.c \
+dnn/lpcnet_enc.c \
+dnn/lpcnet_plc.c \
+dnn/lpcnet_tables.c \
+dnn/nnet.c \
+dnn/nnet_default.c \
+dnn/plc_data.c \
+dnn/parse_lpcnet_weights.c \
+dnn/pitchdnn.c \
+dnn/pitchdnn_data.c
+
+DRED_SOURCES = \
+dnn/dred_rdovae_enc.c \
+dnn/dred_rdovae_enc_data.c \
+dnn/dred_rdovae_dec.c \
+dnn/dred_rdovae_dec_data.c \
+dnn/dred_rdovae_stats_data.c \
+dnn/dred_encoder.c \
+dnn/dred_coding.c \
+dnn/dred_decoder.c
+
+OSCE_SOURCES = \
+dnn/osce.c \
+dnn/osce_features.c \
+dnn/nndsp.c \
+dnn/lace_data.c \
+dnn/nolace_data.c
+
+LOSSGEN_SOURCES = \
+dnn/lossgen.c \
+dnn/lossgen_data.c
+
+DNN_SOURCES_X86_RTCD = dnn/x86/x86_dnn_map.c
+DNN_SOURCES_AVX2 = dnn/x86/nnet_avx2.c
+DNN_SOURCES_SSE4_1 = dnn/x86/nnet_sse4_1.c
+DNN_SOURCES_SSE2 = dnn/x86/nnet_sse2.c
+
+DNN_SOURCES_ARM_RTCD = dnn/arm/arm_dnn_map.c
+DNN_SOURCES_DOTPROD = dnn/arm/nnet_dotprod.c
+DNN_SOURCES_NEON = dnn/arm/nnet_neon.c
diff --git a/meson.build b/meson.build
index ed66d380..56232609 100644
--- a/meson.build
+++ b/meson.build
@@ -14,7 +14,7 @@ host_cpu_family = host_machine.cpu_family()
top_srcdir = meson.current_source_dir()
top_builddir = meson.current_build_dir()
-opus_includes = include_directories('.', 'include', 'celt', 'silk')
+opus_includes = include_directories('.', 'include', 'celt', 'silk', 'dnn')
opus_public_includes = include_directories('include')
add_project_arguments('-DOPUS_BUILD', language: 'c')
@@ -146,6 +146,9 @@ opts = [
[ 'fixed-point-debug', 'FIXED_DEBUG' ],
[ 'custom-modes', 'CUSTOM_MODES' ],
[ 'float-approx', 'FLOAT_APPROX' ],
+ [ 'enable-deep-plc', 'ENABLE_DEEP_PLC' ],
+ [ 'enable-dred', 'ENABLE_DRED' ],
+ [ 'enable-osce', 'ENABLE_OSCE' ],
[ 'assertions', 'ENABLE_ASSERTIONS' ],
[ 'hardening', 'ENABLE_HARDENING' ],
[ 'fuzzing', 'FUZZING' ],
@@ -172,6 +175,10 @@ if disable_float_api
opus_conf.set('DISABLE_FLOAT_API', 1)
endif
+if not get_option('enable-dnn-debug-float')
+ opus_conf.set('DISABLE_DEBUG_FLOAT', 1)
+endif
+
# This is for the description in the pkg-config .pc file
if opt_fixed_point
pc_build = 'fixed-point'
@@ -312,8 +319,17 @@ if not opt_asm.disabled()
rtcd_support += ['NEON']
endif
endif
+ if opus_arm_may_have_dotprod
+ opus_conf.set('OPUS_ARM_MAY_HAVE_DOTPROD', 1)
+ if opus_arm_presume_dotprod
+ opus_conf.set('OPUS_ARM_PRESUME_DOTPROD', 1)
+ asm_optimization += ['DOTPROD']
+ else
+ rtcd_support += ['DOTPROD']
+ endif
+ endif
- if cc.get_define('__APPLE__')
+ if cc.get_define('__APPLE__') != ''
arm2gnu_args = ['--apple']
else
arm2gnu_args = []
@@ -338,12 +354,14 @@ endif
# -mfloat-abi=softfp to enable NEON. F.ex., on Android. It should
# be set in the cross file.
arm_neon_intr_link_args = ['-mfpu=neon']
+arm_dotprod_intr_link_args = ['-march=armv8.2-a+dotprod']
have_sse = false
have_sse2 = false
have_sse4_1 = false
-have_avx = false # no avx opus code yet
+have_avx2 = false
have_neon_intr = false
+have_dotprod_intr = false
intrinsics_support = []
if not opt_intrinsics.disabled()
@@ -419,13 +437,53 @@ if not opt_intrinsics.disabled()
else
message('Compiler does not support @0@ intrinsics'.format(intrin_name))
endif
+
+ # Check for ARMv8.2 dotprod intrinsics
+ intrin_check = '''
+ #include <arm_neon.h>
+ int main (void) {
+ static int8x16_t a, b;
+ static int32x4_t SUMM;
+ SUMM = vdotq_s32(SUMM, a, b);
+ return (int)vgetq_lane_s32(SUMM, 0);
+ }'''
+ intrin_name = 'AArch64 DOTPROD'
+ if cc.links(intrin_check,
+ name: 'compiler supports @0@ intrinsics'.format(intrin_name))
+ opus_arm_presume_dotprod_intr = opus_can_presume_simd
+ opus_arm_may_have_dotprod_intr = true
+ else
+ opus_arm_presume_dotprod_intr = false
+ if cc.links(intrin_check,
+ args: arm_dotprod_intr_link_args,
+ name: 'compiler supports @0@ intrinsics with @1@'.format(intrin_name, ' '.join(arm_dotprod_intr_link_args)))
+ opus_arm_may_have_dotprod_intr = true
+ else
+ opus_arm_may_have_dotprod_intr = false
+ endif
+ endif
+
+ if opus_arm_may_have_dotprod_intr
+ have_dotprod_intr = true
+ intrinsics_support += [intrin_name]
+ opus_conf.set('OPUS_ARM_MAY_HAVE_DOTPROD', 1)
+ if opus_arm_presume_dotprod_intr
+ opus_conf.set('OPUS_ARM_PRESUME_DOTPROD', 1)
+ else
+ rtcd_support += [intrin_name]
+ opus_dotprod_intr_args = arm_dotprod_intr_link_args
+ endif
+ else
+ message('Compiler does not support @0@ intrinsics'.format(intrin_name))
+ endif
+
elif host_cpu_family in ['x86', 'x86_64']
# XXX: allow external override/specification of the flags
x86_intrinsics = [
[ 'SSE', 'xmmintrin.h', '__m128', '_mm_setzero_ps()', ['-msse'] ],
[ 'SSE2', 'emmintrin.h', '__m128i', '_mm_setzero_si128()', ['-msse2'] ],
[ 'SSE4.1', 'smmintrin.h', '__m128i', '_mm_setzero_si128(); mtest = _mm_cmpeq_epi64(mtest, mtest)', ['-msse4.1'] ],
- [ 'AVX', 'immintrin.h', '__m256', '_mm256_setzero_ps()', ['-mavx'] ],
+ [ 'AVX2', 'immintrin.h', '__m256i', '_mm256_abs_epi32(_mm256_setzero_si256())', ['-mavx', '-mfma', '-mavx2'] ],
]
foreach intrin : x86_intrinsics
@@ -563,7 +621,12 @@ else
endif
# extract source file lists from .mk files
-mk_files = ['silk_sources.mk', 'opus_headers.mk', 'opus_sources.mk', 'silk_headers.mk', 'celt_sources.mk', 'celt_headers.mk']
+mk_files = [
+ 'opus_headers.mk', 'opus_sources.mk',
+ 'silk_headers.mk', 'silk_sources.mk',
+ 'celt_sources.mk', 'celt_headers.mk',
+ 'lpcnet_headers.mk', 'lpcnet_sources.mk',
+]
lines = run_command('meson/read-sources-list.py', mk_files, check: true).stdout().strip().split('\n')
sources = {}
foreach l : lines
@@ -574,8 +637,9 @@ foreach l : lines
endforeach
subdir('include')
-subdir('silk')
subdir('celt')
+subdir('silk')
+subdir('dnn')
subdir('src')
configure_file(output: 'config.h', configuration: opus_conf)
diff --git a/meson/README.md b/meson/README.md
new file mode 100644
index 00000000..08f426b2
--- /dev/null
+++ b/meson/README.md
@@ -0,0 +1,88 @@
+# Using the Meson Build System for the Opus Project
+
+This guide provides instructions for using the Meson build system to build the Opus project with various configuration options. Meson is a fast and efficient build system that aims to be easy to use and understand.
+
+Please note that software documentation can sometimes become outdated as new versions are released. For the most up-to-date and accurate information, we recommend referring to the official Meson documentation, which can be found at [mesonbuild.com](https://mesonbuild.com/).
+
+## Prerequisites
+
+Before proceeding, ensure that you have the following prerequisites installed:
+
+- [Meson](https://mesonbuild.com/Quick-guide.html)
+- [Ninja](https://ninja-build.org/) (recommended as the build backend, although other backends are also available)
+- [Git](https://git-scm.com/) (optional, but recommended for version control integration)
+- A working C compiler
+
+## Build and Test Instructions
+
+Follow the steps below to build the Opus project using Meson.
+
+### Check out Source
+Clone the Opus repository using Git:
+
+```shell
+git clone https://gitlab.xiph.org/xiph/opus
+cd opus
+```
+
+### Configure
+To configure the build with Meson, you can set the desired configuration options using the -D flag followed by the option name and value. For the Opus project-specific build options, please refer to the [meson_options.txt](./../meson_options.txt) file. For general Meson options use the command meson `setup --help` to get a list of these options.
+
+For example, to setup and disable tests, use the following command:
+
+```shell
+meson setup builddir -Dtests=disabled
+```
+
+### Build
+
+```shell
+meson compile -C builddir
+```
+
+After a successful build, you can find the compiled Opus library and associated files in the builddir directory.
+
+### Testing with Meson
+
+Opus provides a comprehensive test suite to ensure the functionality and correctness of the project. You can execute the tests using Meson's built-in testing functionality.
+
+To run the Opus tests using Meson:
+
+```shell
+meson test -C builddir
+```
+
+## Platform Support and Bug Reporting
+
+The Opus Meson build system aims to provide support for the same platforms as [GStreamer](https://gstreamer.freedesktop.org/), a widely used multimedia framework. GStreamer supports a wide range of operating systems and platforms, including Linux, Windows (MSVC and MingW), Android, macOS, iOS, and various BSD systems.
+
+While efforts are made to ensure compatibility and stability across these platforms, bugs or issues may still arise in specific configurations. If you encounter any problems during the configuration process or while building the Opus project, we encourage you to file an issue in the [project's issue tracker](https://gitlab.xiph.org/xiph/opus/-/issues).
+
+When reporting an issue, please provide the following information to help us understand and effectively reproduce the configuration problem:
+
+1. Detailed description of the issue, including any error messages or unexpected behavior observed.
+2. Steps to reproduce the problem, including the Meson command and any specific configuration options used.
+3. Operating system and version (e.g., Windows 10, macOS Big Sur, Ubuntu 20.04).
+4. Meson version (e.g., Meson 0.60.0).
+5. Any relevant information about the platform, toolchain, or dependencies used.
+6. Additional context or details that might assist in troubleshooting the issue.
+
+By providing thorough information when reporting configuration issues, you contribute to improving the Opus project's compatibility and reliability across different platforms.
+
+We appreciate your help in identifying and addressing any configuration-related problems, ensuring a better experience for all users of the Opus project.
+
+## Platform Specific Examples
+
+Note: Examples can become outdated. Always refer to the documentation for the latest reference.
+
+### Windows Visual Studio
+
+To generate Visual Studio projects, Meson needs to know the settings of your installed version of Visual Studio. The recommended approach is to run Meson under the Visual Studio Command Prompt.
+
+You can find the Visual Studio Command Prompt by searching from the Start Menu. However, the name varies for each Visual Studio version. For Visual Studio 2022, look for "x64 Native Tools Command Prompt for VS 2022". The following steps remain the same:
+
+```shell
+meson setup builddir -Dtests=enabled --backend vs
+```
+
+For more information about the Visual Studio backend options and additional customization, please refer to the [Using with Visual Studio](https://mesonbuild.com/Using-with-Visual-Studio.html) documentation. \ No newline at end of file
diff --git a/meson_options.txt b/meson_options.txt
index 360ff263..46099276 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -7,6 +7,11 @@ option('rtcd', type : 'feature', value : 'auto', description : 'Run-time CPU cap
option('asm', type : 'feature', value : 'auto', description : 'Assembly optimizations for ARM (fixed-point)')
option('intrinsics', type : 'feature', value : 'auto', description : 'Intrinsics optimizations for ARM NEON or x86')
+option('enable-deep-plc', type : 'boolean', value : false, description : 'Enable Deep Packet Loss Concealment (PLC)')
+option('enable-dred', type : 'boolean', value : false, description : 'Enable Deep Redundancy (DRED)')
+option('enable-osce', type : 'boolean', value : false, description : 'Enable Opus Speech Coding Enhancement (OSCE)')
+option('enable-dnn-debug-float', type : 'boolean', value : false, description : 'Compute DNN using float weights')
+
option('custom-modes', type : 'boolean', value : false, description : 'Enable non-Opus modes, e.g. 44.1 kHz & 2^n frames')
option('extra-programs', type : 'feature', value : 'auto', description : 'Extra programs (demo and tests)')
option('assertions', type : 'boolean', value : false, description : 'Additional software error checking')
diff --git a/opus_headers.mk b/opus_headers.mk
index 27596f2a..4c73e935 100644
--- a/opus_headers.mk
+++ b/opus_headers.mk
@@ -5,5 +5,4 @@ include/opus_projection.h \
src/opus_private.h \
src/analysis.h \
src/mapping_matrix.h \
-src/mlp.h \
-src/tansig_table.h
+src/mlp.h
diff --git a/opus_sources.mk b/opus_sources.mk
index 44153b57..91589efa 100644
--- a/opus_sources.mk
+++ b/opus_sources.mk
@@ -2,6 +2,7 @@ OPUS_SOURCES = \
src/opus.c \
src/opus_decoder.c \
src/opus_encoder.c \
+src/extensions.c \
src/opus_multistream.c \
src/opus_multistream_encoder.c \
src/opus_multistream_decoder.c \
diff --git a/scripts/shrink_model.sh b/scripts/shrink_model.sh
new file mode 100755
index 00000000..c79a2d59
--- /dev/null
+++ b/scripts/shrink_model.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+for i in fargan_data.c pitchdnn_data.c dred_rdovae_dec_data.c dred_rdovae_enc_data.c plc_data.c lace_data.c nolace_data.c
+do
+ cat dnn/$i | perl -ne 'if (/DEBUG/ || /#else/) {$skip=1} if (!$skip && !/ifdef DOT_PROD/) {s/^ *//; s/, /,/g; print $_} elsif (/endif/) {$skip=0}' > tmp_data.c
+ mv tmp_data.c dnn/$i
+done
diff --git a/silk/API.h b/silk/API.h
index 4d90ff9a..878965c7 100644
--- a/silk/API.h
+++ b/silk/API.h
@@ -34,6 +34,10 @@ POSSIBILITY OF SUCH DAMAGE.
#include "entenc.h"
#include "entdec.h"
+#ifdef ENABLE_DEEP_PLC
+#include "lpcnet_private.h"
+#endif
+
#ifdef __cplusplus
extern "C"
{
@@ -88,6 +92,16 @@ opus_int silk_Encode( /* O Returns error co
/* Decoder functions */
/****************************************/
+
+/***********************************************/
+/* Load OSCE models from external data pointer */
+/***********************************************/
+opus_int silk_LoadOSCEModels(
+ void *decState, /* O I/O State */
+ const unsigned char *data, /* I pointer to binary blob */
+ int len /* I length of binary blob data */
+);
+
/***********************************************/
/* Get size in bytes of the Silk decoder state */
/***********************************************/
@@ -96,8 +110,12 @@ opus_int silk_Get_Decoder_Size( /* O Returns error co
);
/*************************/
-/* Init or Reset decoder */
+/* Init and Reset decoder */
/*************************/
+opus_int silk_ResetDecoder( /* O Returns error code */
+ void *decState /* I/O State */
+);
+
opus_int silk_InitDecoder( /* O Returns error code */
void *decState /* I/O State */
);
@@ -113,6 +131,9 @@ opus_int silk_Decode( /* O Returns error co
ec_dec *psRangeDec, /* I/O Compressor data structure */
opus_int16 *samplesOut, /* O Decoded output speech vector */
opus_int32 *nSamplesOut, /* O Number of samples decoded */
+#ifdef ENABLE_DEEP_PLC
+ LPCNetPLCState *lpcnet,
+#endif
int arch /* I Run-time architecture */
);
diff --git a/silk/NSQ.c b/silk/NSQ.c
index 45dd45ce..1caa829b 100644
--- a/silk/NSQ.c
+++ b/silk/NSQ.c
@@ -80,7 +80,7 @@ void silk_NSQ_c
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -258,8 +258,8 @@ void silk_noise_shape_quantizer(
celt_assert( lag > 0 || signalType != TYPE_VOICED );
/* Combine prediction and noise shaping signals */
- tmp1 = silk_SUB32( silk_LSHIFT32( LPC_pred_Q10, 2 ), n_AR_Q12 ); /* Q12 */
- tmp1 = silk_SUB32( tmp1, n_LF_Q12 ); /* Q12 */
+ tmp1 = silk_SUB32_ovflw( silk_LSHIFT32( LPC_pred_Q10, 2 ), n_AR_Q12 ); /* Q12 */
+ tmp1 = silk_SUB32_ovflw( tmp1, n_LF_Q12 ); /* Q12 */
if( lag > 0 ) {
/* Symmetric, packed FIR coefficients */
n_LTP_Q13 = silk_SMULWB( silk_ADD_SAT32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 );
@@ -268,7 +268,7 @@ void silk_noise_shape_quantizer(
shp_lag_ptr++;
tmp2 = silk_SUB32( LTP_pred_Q13, n_LTP_Q13 ); /* Q13 */
- tmp1 = silk_ADD_LSHIFT32( tmp2, tmp1, 1 ); /* Q13 */
+ tmp1 = silk_ADD32_ovflw( tmp2, silk_LSHIFT32( tmp1, 1 ) ); /* Q13 */
tmp1 = silk_RSHIFT_ROUND( tmp1, 3 ); /* Q10 */
} else {
tmp1 = silk_RSHIFT_ROUND( tmp1, 2 ); /* Q10 */
@@ -340,7 +340,7 @@ void silk_noise_shape_quantizer(
/* Add predictions */
LPC_exc_Q14 = silk_ADD_LSHIFT32( exc_Q14, LTP_pred_Q13, 1 );
- xq_Q14 = silk_ADD_LSHIFT32( LPC_exc_Q14, LPC_pred_Q10, 4 );
+ xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, silk_LSHIFT32( LPC_pred_Q10, 4 ) );
/* Scale XQ back to normal level before saving */
xq[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( xq_Q14, Gain_Q10 ), 8 ) );
@@ -349,10 +349,10 @@ void silk_noise_shape_quantizer(
psLPC_Q14++;
*psLPC_Q14 = xq_Q14;
NSQ->sDiff_shp_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_sc_Q10[ i ], 4 );
- sLF_AR_shp_Q14 = silk_SUB_LSHIFT32( NSQ->sDiff_shp_Q14, n_AR_Q12, 2 );
+ sLF_AR_shp_Q14 = silk_SUB32_ovflw( NSQ->sDiff_shp_Q14, silk_LSHIFT32( n_AR_Q12, 2 ) );
NSQ->sLF_AR_shp_Q14 = sLF_AR_shp_Q14;
- NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx ] = silk_SUB_LSHIFT32( sLF_AR_shp_Q14, n_LF_Q12, 2 );
+ NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx ] = silk_SUB32_ovflw(sLF_AR_shp_Q14, silk_LSHIFT32(n_LF_Q12, 2));
sLTP_Q15[ NSQ->sLTP_buf_idx ] = silk_LSHIFT( LPC_exc_Q14, 1 );
NSQ->sLTP_shp_buf_idx++;
NSQ->sLTP_buf_idx++;
diff --git a/silk/NSQ_del_dec.c b/silk/NSQ_del_dec.c
index 41f3fc93..e8dadf15 100644
--- a/silk/NSQ_del_dec.c
+++ b/silk/NSQ_del_dec.c
@@ -120,7 +120,7 @@ void silk_NSQ_del_dec_c(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -423,18 +423,18 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec(
/* Output of lowpass section */
tmp2 = silk_SMLAWB( psDD->Diff_Q14, psDD->sAR2_Q14[ 0 ], warping_Q16 );
/* Output of allpass section */
- tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ 0 ], psDD->sAR2_Q14[ 1 ] - tmp2, warping_Q16 );
+ tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ 0 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ 1 ], tmp2), warping_Q16 );
psDD->sAR2_Q14[ 0 ] = tmp2;
n_AR_Q14 = silk_RSHIFT( shapingLPCOrder, 1 );
n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ 0 ] );
/* Loop over allpass sections */
for( j = 2; j < shapingLPCOrder; j += 2 ) {
/* Output of allpass section */
- tmp2 = silk_SMLAWB( psDD->sAR2_Q14[ j - 1 ], psDD->sAR2_Q14[ j + 0 ] - tmp1, warping_Q16 );
+ tmp2 = silk_SMLAWB( psDD->sAR2_Q14[ j - 1 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ j + 0 ], tmp1), warping_Q16 );
psDD->sAR2_Q14[ j - 1 ] = tmp1;
n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp1, AR_shp_Q13[ j - 1 ] );
/* Output of allpass section */
- tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ j + 0 ], psDD->sAR2_Q14[ j + 1 ] - tmp2, warping_Q16 );
+ tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ j + 0 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ j + 1 ], tmp2), warping_Q16 );
psDD->sAR2_Q14[ j + 0 ] = tmp2;
n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ j ] );
}
@@ -452,7 +452,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec(
/* Input minus prediction plus noise feedback */
/* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */
tmp1 = silk_ADD_SAT32( n_AR_Q14, n_LF_Q14 ); /* Q14 */
- tmp2 = silk_ADD32( n_LTP_Q14, LPC_pred_Q14 ); /* Q13 */
+ tmp2 = silk_ADD32_ovflw( n_LTP_Q14, LPC_pred_Q14 ); /* Q13 */
tmp1 = silk_SUB_SAT32( tmp2, tmp1 ); /* Q13 */
tmp1 = silk_RSHIFT_ROUND( tmp1, 4 ); /* Q10 */
@@ -530,11 +530,11 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec(
/* Add predictions */
LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 );
- xq_Q14 = silk_ADD32( LPC_exc_Q14, LPC_pred_Q14 );
+ xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, LPC_pred_Q14 );
/* Update states */
- psSS[ 0 ].Diff_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_Q10[ i ], 4 );
- sLF_AR_shp_Q14 = silk_SUB32( psSS[ 0 ].Diff_Q14, n_AR_Q14 );
+ psSS[ 0 ].Diff_Q14 = silk_SUB32_ovflw( xq_Q14, silk_LSHIFT32( x_Q10[ i ], 4 ) );
+ sLF_AR_shp_Q14 = silk_SUB32_ovflw( psSS[ 0 ].Diff_Q14, n_AR_Q14 );
psSS[ 0 ].sLTP_shp_Q14 = silk_SUB_SAT32( sLF_AR_shp_Q14, n_LF_Q14 );
psSS[ 0 ].LF_AR_Q14 = sLF_AR_shp_Q14;
psSS[ 0 ].LPC_exc_Q14 = LPC_exc_Q14;
@@ -550,11 +550,11 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec(
/* Add predictions */
LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 );
- xq_Q14 = silk_ADD32( LPC_exc_Q14, LPC_pred_Q14 );
+ xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, LPC_pred_Q14 );
/* Update states */
- psSS[ 1 ].Diff_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_Q10[ i ], 4 );
- sLF_AR_shp_Q14 = silk_SUB32( psSS[ 1 ].Diff_Q14, n_AR_Q14 );
+ psSS[ 1 ].Diff_Q14 = silk_SUB32_ovflw( xq_Q14, silk_LSHIFT32( x_Q10[ i ], 4 ) );
+ sLF_AR_shp_Q14 = silk_SUB32_ovflw( psSS[ 1 ].Diff_Q14, n_AR_Q14 );
psSS[ 1 ].sLTP_shp_Q14 = silk_SUB_SAT32( sLF_AR_shp_Q14, n_LF_Q14 );
psSS[ 1 ].LF_AR_Q14 = sLF_AR_shp_Q14;
psSS[ 1 ].LPC_exc_Q14 = LPC_exc_Q14;
diff --git a/silk/PLC.c b/silk/PLC.c
index 4667440d..b35bf750 100644
--- a/silk/PLC.c
+++ b/silk/PLC.c
@@ -33,6 +33,10 @@ POSSIBILITY OF SUCH DAMAGE.
#include "stack_alloc.h"
#include "PLC.h"
+#ifdef ENABLE_DEEP_PLC
+#include "lpcnet.h"
+#endif
+
#define NB_ATT 2
static const opus_int16 HARM_ATT_Q15[NB_ATT] = { 32440, 31130 }; /* 0.99, 0.95 */
static const opus_int16 PLC_RAND_ATTENUATE_V_Q15[NB_ATT] = { 31130, 26214 }; /* 0.95, 0.8 */
@@ -47,6 +51,9 @@ static OPUS_INLINE void silk_PLC_conceal(
silk_decoder_state *psDec, /* I/O Decoder state */
silk_decoder_control *psDecCtrl, /* I/O Decoder control */
opus_int16 frame[], /* O LPC residual signal */
+#ifdef ENABLE_DEEP_PLC
+ LPCNetPLCState *lpcnet,
+#endif
int arch /* I Run-time architecture */
);
@@ -67,6 +74,9 @@ void silk_PLC(
silk_decoder_control *psDecCtrl, /* I/O Decoder control */
opus_int16 frame[], /* I/O signal */
opus_int lost, /* I Loss flag */
+#ifdef ENABLE_DEEP_PLC
+ LPCNetPLCState *lpcnet,
+#endif
int arch /* I Run-time architecture */
)
{
@@ -80,7 +90,11 @@ void silk_PLC(
/****************************/
/* Generate Signal */
/****************************/
- silk_PLC_conceal( psDec, psDecCtrl, frame, arch );
+ silk_PLC_conceal( psDec, psDecCtrl, frame,
+#ifdef ENABLE_DEEP_PLC
+ lpcnet,
+#endif
+ arch );
psDec->lossCnt++;
} else {
@@ -88,6 +102,14 @@ void silk_PLC(
/* Update state */
/****************************/
silk_PLC_update( psDec, psDecCtrl );
+#ifdef ENABLE_DEEP_PLC
+ if ( lpcnet != NULL && psDec->sPLC.fs_kHz == 16 ) {
+ int k;
+ for( k = 0; k < psDec->nb_subfr; k += 2 ) {
+ lpcnet_plc_update( lpcnet, frame + k * psDec->subfr_length );
+ }
+ }
+#endif
}
}
@@ -195,6 +217,9 @@ static OPUS_INLINE void silk_PLC_conceal(
silk_decoder_state *psDec, /* I/O Decoder state */
silk_decoder_control *psDecCtrl, /* I/O Decoder control */
opus_int16 frame[], /* O LPC residual signal */
+#ifdef ENABLE_DEEP_PLC
+ LPCNetPLCState *lpcnet,
+#endif
int arch /* I Run-time architecture */
)
{
@@ -371,6 +396,24 @@ static OPUS_INLINE void silk_PLC_conceal(
/* Scale with Gain */
frame[ i ] = (opus_int16)silk_SAT16( silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( sLPC_Q14_ptr[ MAX_LPC_ORDER + i ], prevGain_Q10[ 1 ] ), 8 ) ) );
}
+#ifdef ENABLE_DEEP_PLC
+ if ( lpcnet != NULL && lpcnet->loaded && psDec->sPLC.fs_kHz == 16 ) {
+ int run_deep_plc = psDec->sPLC.enable_deep_plc || lpcnet->fec_fill_pos != 0;
+ if( run_deep_plc ) {
+ for( k = 0; k < psDec->nb_subfr; k += 2 ) {
+ lpcnet_plc_conceal( lpcnet, frame + k * psDec->subfr_length );
+ }
+ /* We *should* be able to copy only from psDec->frame_length-MAX_LPC_ORDER, i.e. the last MAX_LPC_ORDER samples. */
+ for( i = 0; i < psDec->frame_length; i++ ) {
+ sLPC_Q14_ptr[ MAX_LPC_ORDER + i ] = (int)floor(.5 + frame[ i ] * (float)(1 << 24) / prevGain_Q10[ 1 ] );
+ }
+ } else {
+ for( k = 0; k < psDec->nb_subfr; k += 2 ) {
+ lpcnet_plc_update( lpcnet, frame + k * psDec->subfr_length );
+ }
+ }
+ }
+#endif
/* Save LPC state */
silk_memcpy( psDec->sLPC_Q14_buf, &sLPC_Q14_ptr[ psDec->frame_length ], MAX_LPC_ORDER * sizeof( opus_int32 ) );
@@ -431,12 +474,16 @@ void silk_PLC_glue_frames(
slope_Q16 = silk_DIV32_16( ( (opus_int32)1 << 16 ) - gain_Q16, length );
/* Make slope 4x steeper to avoid missing onsets after DTX */
slope_Q16 = silk_LSHIFT( slope_Q16, 2 );
-
- for( i = 0; i < length; i++ ) {
- frame[ i ] = silk_SMULWB( gain_Q16, frame[ i ] );
- gain_Q16 += slope_Q16;
- if( gain_Q16 > (opus_int32)1 << 16 ) {
- break;
+#ifdef ENABLE_DEEP_PLC
+ if ( psDec->sPLC.fs_kHz != 16 )
+#endif
+ {
+ for( i = 0; i < length; i++ ) {
+ frame[ i ] = silk_SMULWB( gain_Q16, frame[ i ] );
+ gain_Q16 += slope_Q16;
+ if( gain_Q16 > (opus_int32)1 << 16 ) {
+ break;
+ }
}
}
}
diff --git a/silk/PLC.h b/silk/PLC.h
index 6438f516..1bebb786 100644
--- a/silk/PLC.h
+++ b/silk/PLC.h
@@ -49,6 +49,9 @@ void silk_PLC(
silk_decoder_control *psDecCtrl, /* I/O Decoder control */
opus_int16 frame[], /* I/O signal */
opus_int lost, /* I Loss flag */
+#ifdef ENABLE_DEEP_PLC
+ LPCNetPLCState *lpcnet,
+#endif
int arch /* I Run-time architecture */
);
diff --git a/silk/arm/NSQ_del_dec_arm.h b/silk/arm/NSQ_del_dec_arm.h
index 9e76e169..0c4fcfcc 100644
--- a/silk/arm/NSQ_del_dec_arm.h
+++ b/silk/arm/NSQ_del_dec_arm.h
@@ -34,7 +34,7 @@ POSSIBILITY OF SUCH DAMAGE.
void silk_NSQ_del_dec_neon(
const silk_encoder_state *psEncC, silk_nsq_state *NSQ,
SideInfoIndices *psIndices, const opus_int16 x16[], opus_int8 pulses[],
- const opus_int16 PredCoef_Q12[2 * MAX_LPC_ORDER],
+ const opus_int16 *PredCoef_Q12,
const opus_int16 LTPCoef_Q14[LTP_ORDER * MAX_NB_SUBFR],
const opus_int16 AR_Q13[MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER],
const opus_int HarmShapeGain_Q14[MAX_NB_SUBFR],
@@ -65,7 +65,7 @@ void silk_NSQ_del_dec_neon(
extern void (*const SILK_NSQ_DEL_DEC_IMPL[OPUS_ARCHMASK + 1])(
const silk_encoder_state *psEncC, silk_nsq_state *NSQ,
SideInfoIndices *psIndices, const opus_int16 x16[], opus_int8 pulses[],
- const opus_int16 PredCoef_Q12[2 * MAX_LPC_ORDER],
+ const opus_int16 *PredCoef_Q12,
const opus_int16 LTPCoef_Q14[LTP_ORDER * MAX_NB_SUBFR],
const opus_int16 AR_Q13[MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER],
const opus_int HarmShapeGain_Q14[MAX_NB_SUBFR],
diff --git a/silk/arm/NSQ_del_dec_neon_intr.c b/silk/arm/NSQ_del_dec_neon_intr.c
index 212410f3..668dde6d 100644
--- a/silk/arm/NSQ_del_dec_neon_intr.c
+++ b/silk/arm/NSQ_del_dec_neon_intr.c
@@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE.
#endif
#include "main.h"
#include "stack_alloc.h"
+#include "os_support.h"
/* NEON intrinsics optimization now can only parallelize up to 4 delay decision states. */
/* If there are more states, C function is called, and this optimization must be expanded. */
@@ -220,7 +221,7 @@ void silk_NSQ_del_dec_neon(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -279,6 +280,7 @@ void silk_NSQ_del_dec_neon(
/* Initialize delayed decision states */
ALLOC( psDelDec, 1, NSQ_del_decs_struct );
+ OPUS_CLEAR(psDelDec, 1);
/* Only RandState and RD_Q10 need to be initialized to 0. */
silk_memset( psDelDec->RandState, 0, sizeof( psDelDec->RandState ) );
vst1q_s32( psDelDec->RD_Q10, vdupq_n_s32( 0 ) );
@@ -587,6 +589,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_neon(
silk_assert( nStatesDelayedDecision > 0 );
silk_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */
ALLOC( psSampleState, 2, NSQ_samples_struct );
+ OPUS_CLEAR(psSampleState, 2);
shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ];
pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ];
@@ -711,23 +714,26 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_neon(
const int rdo_offset = Lambda_Q10/2 - 512;
const uint16x4_t greaterThanRdo = vcgt_s16( q1_Q10_s16x4, vdup_n_s16( rdo_offset ) );
const uint16x4_t lessThanMinusRdo = vclt_s16( q1_Q10_s16x4, vdup_n_s16( -rdo_offset ) );
+ int16x4_t signed_offset = vbsl_s16( greaterThanRdo, vdup_n_s16( -rdo_offset ), vdup_n_s16( 0 ) );
+ signed_offset = vbsl_s16( lessThanMinusRdo, vdup_n_s16( rdo_offset ), signed_offset );
/* If Lambda_Q10 > 32767, then q1_Q0, q1_Q10 and q2_Q10 must change to 32-bit. */
silk_assert( Lambda_Q10 <= 32767 );
q1_Q0_s16x4 = vreinterpret_s16_u16( vclt_s16( q1_Q10_s16x4, vdup_n_s16( 0 ) ) );
- q1_Q0_s16x4 = vbsl_s16( greaterThanRdo, vsub_s16( q1_Q10_s16x4, vdup_n_s16( rdo_offset ) ), q1_Q0_s16x4 );
- q1_Q0_s16x4 = vbsl_s16( lessThanMinusRdo, vadd_s16( q1_Q10_s16x4, vdup_n_s16( rdo_offset ) ), q1_Q0_s16x4 );
+ q1_Q0_s16x4 = vbsl_s16(vorr_u16(greaterThanRdo, lessThanMinusRdo), vadd_s16( q1_Q10_s16x4 , signed_offset), q1_Q0_s16x4);
q1_Q0_s16x4 = vshr_n_s16( q1_Q0_s16x4, 10 );
}
{
const uint16x4_t equal0_u16x4 = vceq_s16( q1_Q0_s16x4, vdup_n_s16( 0 ) );
const uint16x4_t equalMinus1_u16x4 = vceq_s16( q1_Q0_s16x4, vdup_n_s16( -1 ) );
const uint16x4_t lessThanMinus1_u16x4 = vclt_s16( q1_Q0_s16x4, vdup_n_s16( -1 ) );
- int16x4_t tmp1_s16x4, tmp2_s16x4;
+ int16x4_t tmp1_s16x4, tmp2_s16x4, tmp_summand_s16x4;
q1_Q10_s16x4 = vshl_n_s16( q1_Q0_s16x4, 10 );
- tmp1_s16x4 = vadd_s16( q1_Q10_s16x4, vdup_n_s16( offset_Q10 - QUANT_LEVEL_ADJUST_Q10 ) );
- q1_Q10_s16x4 = vadd_s16( q1_Q10_s16x4, vdup_n_s16( offset_Q10 + QUANT_LEVEL_ADJUST_Q10 ) );
+ tmp_summand_s16x4 = vand_s16( vreinterpret_s16_u16(vcge_s16(q1_Q0_s16x4, vdup_n_s16(0))), vdup_n_s16( offset_Q10 - QUANT_LEVEL_ADJUST_Q10 ) );
+ tmp1_s16x4 = vadd_s16( q1_Q10_s16x4, tmp_summand_s16x4 );
+ tmp_summand_s16x4 = vbsl_s16( lessThanMinus1_u16x4, vdup_n_s16( offset_Q10 + QUANT_LEVEL_ADJUST_Q10 ), vdup_n_s16(0) );
+ q1_Q10_s16x4 = vadd_s16( q1_Q10_s16x4, tmp_summand_s16x4);
q1_Q10_s16x4 = vbsl_s16( lessThanMinus1_u16x4, q1_Q10_s16x4, tmp1_s16x4 );
q1_Q10_s16x4 = vbsl_s16( equal0_u16x4, vdup_n_s16( offset_Q10 ), q1_Q10_s16x4 );
q1_Q10_s16x4 = vbsl_s16( equalMinus1_u16x4, vdup_n_s16( offset_Q10 - ( 1024 - QUANT_LEVEL_ADJUST_Q10 ) ), q1_Q10_s16x4 );
@@ -818,6 +824,13 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_neon(
}
}
+ /* clear unused part of RD_Q10 to avoid overflows */
+ if( nStatesDelayedDecision < NEON_MAX_DEL_DEC_STATES )
+ {
+ OPUS_CLEAR(psSampleState[0].RD_Q10 + nStatesDelayedDecision, NEON_MAX_DEL_DEC_STATES - nStatesDelayedDecision);
+ OPUS_CLEAR(psSampleState[1].RD_Q10 + nStatesDelayedDecision, NEON_MAX_DEL_DEC_STATES - nStatesDelayedDecision);
+ }
+
/* Increase RD values of expired states */
{
uint32x4_t t_u32x4;
@@ -896,7 +909,8 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_neon(
vst1q_s32( psDelDec->Pred_Q15[ *smpl_buf_idx ], vshlq_n_s32( vld1q_s32( psSampleState[ 0 ].LPC_exc_Q14 ), 1 ) );
vst1q_s32( psDelDec->Shape_Q14[ *smpl_buf_idx ], vld1q_s32( psSampleState[ 0 ].sLTP_shp_Q14 ) );
tmp1_s32x4 = vrshrq_n_s32( tmp1_s32x4, 10 );
- tmp1_s32x4 = vaddq_s32( vld1q_s32( psDelDec->Seed ), tmp1_s32x4 );
+ tmp1_s32x4 = vreinterpretq_s32_u32( vaddq_u32( vreinterpretq_u32_s32(
+ vld1q_s32( psDelDec->Seed ) ), vreinterpretq_u32_s32( tmp1_s32x4 ) ) );
vst1q_s32( psDelDec->Seed, tmp1_s32x4 );
vst1q_s32( psDelDec->RandState[ *smpl_buf_idx ], tmp1_s32x4 );
vst1q_s32( psDelDec->RD_Q10, vld1q_s32( psSampleState[ 0 ].RD_Q10 ) );
diff --git a/silk/arm/NSQ_neon.h b/silk/arm/NSQ_neon.h
index b31d9442..f03d8ddd 100644
--- a/silk/arm/NSQ_neon.h
+++ b/silk/arm/NSQ_neon.h
@@ -73,7 +73,7 @@ static OPUS_INLINE void silk_short_prediction_create_arch_coef_neon(opus_int32 *
#elif defined(OPUS_HAVE_RTCD) && defined(OPUS_ARM_MAY_HAVE_NEON_INTR)
#define silk_short_prediction_create_arch_coef(out, in, order) \
- do { if (arch == OPUS_ARCH_ARM_NEON) { silk_short_prediction_create_arch_coef_neon(out, in, order); } } while (0)
+ do { if (arch >= OPUS_ARCH_ARM_NEON) { silk_short_prediction_create_arch_coef_neon(out, in, order); } } while (0)
#endif
@@ -95,7 +95,7 @@ opus_int32 silk_NSQ_noise_shape_feedback_loop_neon(const opus_int32 *data0, opus
(coef vs. coefRev) so can't use the usual IMPL table implementation */
#undef silk_noise_shape_quantizer_short_prediction
#define silk_noise_shape_quantizer_short_prediction(in, coef, coefRev, order, arch) \
- (arch == OPUS_ARCH_ARM_NEON ? \
+ (arch >= OPUS_ARCH_ARM_NEON ? \
silk_noise_shape_quantizer_short_prediction_neon(in, coefRev, order) : \
silk_noise_shape_quantizer_short_prediction_c(in, coef, order))
diff --git a/silk/arm/arm_silk_map.c b/silk/arm/arm_silk_map.c
index 0b9bfec2..a91f79b5 100644
--- a/silk/arm/arm_silk_map.c
+++ b/silk/arm/arm_silk_map.c
@@ -49,6 +49,7 @@ void (*const SILK_BIQUAD_ALT_STRIDE2_IMPL[OPUS_ARCHMASK + 1])(
silk_biquad_alt_stride2_c, /* EDSP */
silk_biquad_alt_stride2_c, /* Media */
silk_biquad_alt_stride2_neon, /* Neon */
+ silk_biquad_alt_stride2_neon, /* dotprod */
};
opus_int32 (*const SILK_LPC_INVERSE_PRED_GAIN_IMPL[OPUS_ARCHMASK + 1])( /* O Returns inverse prediction gain in energy domain, Q30 */
@@ -59,6 +60,7 @@ opus_int32 (*const SILK_LPC_INVERSE_PRED_GAIN_IMPL[OPUS_ARCHMASK + 1])( /* O R
silk_LPC_inverse_pred_gain_c, /* EDSP */
silk_LPC_inverse_pred_gain_c, /* Media */
silk_LPC_inverse_pred_gain_neon, /* Neon */
+ silk_LPC_inverse_pred_gain_neon, /* dotprod */
};
void (*const SILK_NSQ_DEL_DEC_IMPL[OPUS_ARCHMASK + 1])(
@@ -67,7 +69,7 @@ void (*const SILK_NSQ_DEL_DEC_IMPL[OPUS_ARCHMASK + 1])(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -82,6 +84,7 @@ void (*const SILK_NSQ_DEL_DEC_IMPL[OPUS_ARCHMASK + 1])(
silk_NSQ_del_dec_c, /* EDSP */
silk_NSQ_del_dec_c, /* Media */
silk_NSQ_del_dec_neon, /* Neon */
+ silk_NSQ_del_dec_neon, /* dotprod */
};
/*There is no table for silk_noise_shape_quantizer_short_prediction because the
@@ -97,6 +100,7 @@ opus_int32
silk_NSQ_noise_shape_feedback_loop_c, /* EDSP */
silk_NSQ_noise_shape_feedback_loop_c, /* Media */
silk_NSQ_noise_shape_feedback_loop_neon, /* NEON */
+ silk_NSQ_noise_shape_feedback_loop_neon, /* dotprod */
};
# endif
@@ -116,6 +120,7 @@ void (*const SILK_WARPED_AUTOCORRELATION_FIX_IMPL[OPUS_ARCHMASK + 1])(
silk_warped_autocorrelation_FIX_c, /* EDSP */
silk_warped_autocorrelation_FIX_c, /* Media */
silk_warped_autocorrelation_FIX_neon, /* Neon */
+ silk_warped_autocorrelation_FIX_neon, /* dotprod */
};
# endif
diff --git a/silk/control.h b/silk/control.h
index b76ec33c..f5633e62 100644
--- a/silk/control.h
+++ b/silk/control.h
@@ -77,6 +77,9 @@ typedef struct {
/* I: Flag to enable in-band Forward Error Correction (FEC); 0/1 */
opus_int useInBandFEC;
+ /* I: Flag to enable in-band Deep REDundancy (DRED); 0/1 */
+ opus_int useDRED;
+
/* I: Flag to actually code in-band Forward Error Correction (FEC) in the current packet; 0/1 */
opus_int LBRR_coded;
@@ -141,6 +144,14 @@ typedef struct {
/* O: Pitch lag of previous frame (0 if unvoiced), measured in samples at 48 kHz */
opus_int prevPitchLag;
+
+ /* I: Enable Deep PLC */
+ opus_int enable_deep_plc;
+
+#ifdef ENABLE_OSCE
+ /* I: OSCE method */
+ opus_int osce_method;
+#endif
} silk_DecControlStruct;
#ifdef __cplusplus
diff --git a/silk/dec_API.c b/silk/dec_API.c
index 7d5ca7fb..c1091d13 100644
--- a/silk/dec_API.c
+++ b/silk/dec_API.c
@@ -33,6 +33,11 @@ POSSIBILITY OF SUCH DAMAGE.
#include "stack_alloc.h"
#include "os_support.h"
+#ifdef ENABLE_OSCE
+#include "osce.h"
+#include "osce_structs.h"
+#endif
+
/************************/
/* Decoder Super Struct */
/************************/
@@ -42,12 +47,33 @@ typedef struct {
opus_int nChannelsAPI;
opus_int nChannelsInternal;
opus_int prev_decode_only_middle;
+#ifdef ENABLE_OSCE
+ OSCEModel osce_model;
+#endif
} silk_decoder;
/*********************/
/* Decoder functions */
/*********************/
+
+
+opus_int silk_LoadOSCEModels(void *decState, const unsigned char *data, int len)
+{
+#ifdef ENABLE_OSCE
+ opus_int ret = SILK_NO_ERROR;
+
+ ret = osce_load_models(&((silk_decoder *)decState)->osce_model, data, len);
+ ((silk_decoder *)decState)->osce_model.loaded = (ret == 0);
+ return ret;
+#else
+ (void) decState;
+ (void) data;
+ (void) len;
+ return SILK_NO_ERROR;
+#endif
+}
+
opus_int silk_Get_Decoder_Size( /* O Returns error code */
opus_int *decSizeBytes /* O Number of bytes in SILK decoder state */
)
@@ -60,12 +86,37 @@ opus_int silk_Get_Decoder_Size( /* O Returns error co
}
/* Reset decoder state */
+opus_int silk_ResetDecoder( /* O Returns error code */
+ void *decState /* I/O State */
+)
+{
+ opus_int n, ret = SILK_NO_ERROR;
+ silk_decoder_state *channel_state = ((silk_decoder *)decState)->channel_state;
+
+ for( n = 0; n < DECODER_NUM_CHANNELS; n++ ) {
+ ret = silk_reset_decoder( &channel_state[ n ] );
+ }
+ silk_memset(&((silk_decoder *)decState)->sStereo, 0, sizeof(((silk_decoder *)decState)->sStereo));
+ /* Not strictly needed, but it's cleaner that way */
+ ((silk_decoder *)decState)->prev_decode_only_middle = 0;
+
+ return ret;
+}
+
+
opus_int silk_InitDecoder( /* O Returns error code */
void *decState /* I/O State */
)
{
opus_int n, ret = SILK_NO_ERROR;
silk_decoder_state *channel_state = ((silk_decoder *)decState)->channel_state;
+#ifdef ENABLE_OSCE
+ ((silk_decoder *)decState)->osce_model.loaded = 0;
+#endif
+#ifndef USE_WEIGHTS_FILE
+ /* load osce models */
+ silk_LoadOSCEModels(decState, NULL, 0);
+#endif
for( n = 0; n < DECODER_NUM_CHANNELS; n++ ) {
ret = silk_init_decoder( &channel_state[ n ] );
@@ -86,6 +137,9 @@ opus_int silk_Decode( /* O Returns error co
ec_dec *psRangeDec, /* I/O Compressor data structure */
opus_int16 *samplesOut, /* O Decoded output speech vector */
opus_int32 *nSamplesOut, /* O Number of samples decoded */
+#ifdef ENABLE_DEEP_PLC
+ LPCNetPLCState *lpcnet,
+#endif
int arch /* I Run-time architecture */
)
{
@@ -278,6 +332,7 @@ opus_int silk_Decode( /* O Returns error co
has_side = !psDec->prev_decode_only_middle
|| (decControl->nChannelsInternal == 2 && lostFlag == FLAG_DECODE_LBRR && channel_state[1].LBRR_flags[ channel_state[1].nFramesDecoded ] == 1 );
}
+ channel_state[ 0 ].sPLC.enable_deep_plc = decControl->enable_deep_plc;
/* Call decoder for one frame */
for( n = 0; n < decControl->nChannelsInternal; n++ ) {
if( n == 0 || has_side ) {
@@ -297,7 +352,19 @@ opus_int silk_Decode( /* O Returns error co
} else {
condCoding = CODE_CONDITIONALLY;
}
- ret += silk_decode_frame( &channel_state[ n ], psRangeDec, &samplesOut1_tmp[ n ][ 2 ], &nSamplesOutDec, lostFlag, condCoding, arch);
+#ifdef ENABLE_OSCE
+ if ( channel_state[n].osce.method != decControl->osce_method ) {
+ osce_reset( &channel_state[n].osce, decControl->osce_method );
+ }
+#endif
+ ret += silk_decode_frame( &channel_state[ n ], psRangeDec, &samplesOut1_tmp[ n ][ 2 ], &nSamplesOutDec, lostFlag, condCoding,
+#ifdef ENABLE_DEEP_PLC
+ n == 0 ? lpcnet : NULL,
+#endif
+#ifdef ENABLE_OSCE
+ &psDec->osce_model,
+#endif
+ arch);
} else {
silk_memset( &samplesOut1_tmp[ n ][ 2 ], 0, nSamplesOutDec * sizeof( opus_int16 ) );
}
diff --git a/silk/decode_frame.c b/silk/decode_frame.c
index 4f36f854..9bc4ca2b 100644
--- a/silk/decode_frame.c
+++ b/silk/decode_frame.c
@@ -33,6 +33,10 @@ POSSIBILITY OF SUCH DAMAGE.
#include "stack_alloc.h"
#include "PLC.h"
+#ifdef ENABLE_OSCE
+#include "osce.h"
+#endif
+
/****************/
/* Decode frame */
/****************/
@@ -43,6 +47,12 @@ opus_int silk_decode_frame(
opus_int32 *pN, /* O Pointer to size of output frame */
opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */
opus_int condCoding, /* I The type of conditional coding to use */
+#ifdef ENABLE_DEEP_PLC
+ LPCNetPLCState *lpcnet,
+#endif
+#ifdef ENABLE_OSCE
+ OSCEModel *osce_model,
+#endif
int arch /* I Run-time architecture */
)
{
@@ -61,6 +71,10 @@ opus_int silk_decode_frame(
( lostFlag == FLAG_DECODE_LBRR && psDec->LBRR_flags[ psDec->nFramesDecoded ] == 1 ) )
{
VARDECL( opus_int16, pulses );
+#ifdef ENABLE_OSCE
+ opus_int32 ec_start;
+ ec_start = ec_tell(psRangeDec);
+#endif
ALLOC( pulses, (L + SHELL_CODEC_FRAME_LENGTH - 1) &
~(SHELL_CODEC_FRAME_LENGTH - 1), opus_int16 );
/*********************************************/
@@ -84,10 +98,29 @@ opus_int silk_decode_frame(
/********************************************************/
silk_decode_core( psDec, psDecCtrl, pOut, pulses, arch );
+ /*************************/
+ /* Update output buffer. */
+ /*************************/
+ celt_assert( psDec->ltp_mem_length >= psDec->frame_length );
+ mv_len = psDec->ltp_mem_length - psDec->frame_length;
+ silk_memmove( psDec->outBuf, &psDec->outBuf[ psDec->frame_length ], mv_len * sizeof(opus_int16) );
+ silk_memcpy( &psDec->outBuf[ mv_len ], pOut, psDec->frame_length * sizeof( opus_int16 ) );
+
+#ifdef ENABLE_OSCE
+ /********************************************************/
+ /* Run SILK enhancer */
+ /********************************************************/
+ osce_enhance_frame( osce_model, psDec, psDecCtrl, pOut, ec_tell(psRangeDec) - ec_start, arch );
+#endif
+
/********************************************************/
/* Update PLC state */
/********************************************************/
- silk_PLC( psDec, psDecCtrl, pOut, 0, arch );
+ silk_PLC( psDec, psDecCtrl, pOut, 0,
+#ifdef ENABLE_DEEP_PLC
+ lpcnet,
+#endif
+ arch );
psDec->lossCnt = 0;
psDec->prevSignalType = psDec->indices.signalType;
@@ -97,16 +130,23 @@ opus_int silk_decode_frame(
psDec->first_frame_after_reset = 0;
} else {
/* Handle packet loss by extrapolation */
- silk_PLC( psDec, psDecCtrl, pOut, 1, arch );
- }
+ silk_PLC( psDec, psDecCtrl, pOut, 1,
+#ifdef ENABLE_DEEP_PLC
+ lpcnet,
+#endif
+ arch );
- /*************************/
- /* Update output buffer. */
- /*************************/
- celt_assert( psDec->ltp_mem_length >= psDec->frame_length );
- mv_len = psDec->ltp_mem_length - psDec->frame_length;
- silk_memmove( psDec->outBuf, &psDec->outBuf[ psDec->frame_length ], mv_len * sizeof(opus_int16) );
- silk_memcpy( &psDec->outBuf[ mv_len ], pOut, psDec->frame_length * sizeof( opus_int16 ) );
+#ifdef ENABLE_OSCE
+ osce_reset( &psDec->osce, psDec->osce.method );
+#endif
+ /*************************/
+ /* Update output buffer. */
+ /*************************/
+ celt_assert( psDec->ltp_mem_length >= psDec->frame_length );
+ mv_len = psDec->ltp_mem_length - psDec->frame_length;
+ silk_memmove( psDec->outBuf, &psDec->outBuf[ psDec->frame_length ], mv_len * sizeof(opus_int16) );
+ silk_memcpy( &psDec->outBuf[ mv_len ], pOut, psDec->frame_length * sizeof( opus_int16 ) );
+ }
/************************************************/
/* Comfort noise generation / estimation */
diff --git a/silk/enc_API.c b/silk/enc_API.c
index 548e0736..369caddd 100644
--- a/silk/enc_API.c
+++ b/silk/enc_API.c
@@ -41,6 +41,10 @@ POSSIBILITY OF SUCH DAMAGE.
#include "main_FLP.h"
#endif
+#ifdef ENABLE_DRED
+#include "dred_encoder.h"
+#endif
+
/***************************************/
/* Read control structure from encoder */
/***************************************/
diff --git a/silk/fixed/encode_frame_FIX.c b/silk/fixed/encode_frame_FIX.c
index a02bf87d..7c83360b 100644
--- a/silk/fixed/encode_frame_FIX.c
+++ b/silk/fixed/encode_frame_FIX.c
@@ -105,8 +105,11 @@ opus_int silk_encode_frame_FIX(
opus_int gain_lock[ MAX_NB_SUBFR ] = {0};
opus_int16 best_gain_mult[ MAX_NB_SUBFR ];
opus_int best_sum[ MAX_NB_SUBFR ];
+ opus_int bits_margin;
SAVE_STACK;
+ /* For CBR, 5 bits below budget is close enough. For VBR, allow up to 25% below the cap if we initially busted the budget. */
+ bits_margin = useCBR ? 5 : maxBits/4;
/* This is totally unnecessary but many compilers (including gcc) are too dumb to realise it */
LastGainIndex_copy2 = nBits_lower = nBits_upper = gainMult_lower = gainMult_upper = 0;
@@ -282,7 +285,7 @@ opus_int silk_encode_frame_FIX(
gainMult_upper = gainMult_Q8;
gainsID_upper = gainsID;
}
- } else if( nBits < maxBits - 5 ) {
+ } else if( nBits < maxBits - bits_margin ) {
found_lower = 1;
nBits_lower = nBits;
gainMult_lower = gainMult_Q8;
@@ -296,7 +299,7 @@ opus_int silk_encode_frame_FIX(
LastGainIndex_copy2 = psEnc->sShape.LastGainIndex;
}
} else {
- /* Within 5 bits of budget: close enough */
+ /* Close enough */
break;
}
@@ -318,17 +321,10 @@ opus_int silk_encode_frame_FIX(
if( ( found_lower & found_upper ) == 0 ) {
/* Adjust gain according to high-rate rate/distortion curve */
if( nBits > maxBits ) {
- if (gainMult_Q8 < 16384) {
- gainMult_Q8 *= 2;
- } else {
- gainMult_Q8 = 32767;
- }
+ gainMult_Q8 = silk_min_32( 1024, gainMult_Q8*3/2 );
} else {
- opus_int32 gain_factor_Q16;
- gain_factor_Q16 = silk_log2lin( silk_LSHIFT( nBits - maxBits, 7 ) / psEnc->sCmn.frame_length + SILK_FIX_CONST( 16, 7 ) );
- gainMult_Q8 = silk_SMULWB( gain_factor_Q16, gainMult_Q8 );
+ gainMult_Q8 = silk_max_32( 64, gainMult_Q8*4/5 );
}
-
} else {
/* Adjust gain by interpolating */
gainMult_Q8 = gainMult_lower + silk_DIV32_16( silk_MUL( gainMult_upper - gainMult_lower, maxBits - nBits_lower ), nBits_upper - nBits_lower );
diff --git a/silk/float/SigProc_FLP.h b/silk/float/SigProc_FLP.h
index 953de8b0..ff9281b8 100644
--- a/silk/float/SigProc_FLP.h
+++ b/silk/float/SigProc_FLP.h
@@ -30,6 +30,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "SigProc_FIX.h"
#include "float_cast.h"
+#include "main.h"
#include <math.h>
#ifdef __cplusplus
@@ -73,7 +74,8 @@ void silk_autocorrelation_FLP(
silk_float *results, /* O result (length correlationCount) */
const silk_float *inputData, /* I input data to correlate */
opus_int inputDataSize, /* I length of input */
- opus_int correlationCount /* I number of correlation taps to compute */
+ opus_int correlationCount, /* I number of correlation taps to compute */
+ int arch
);
opus_int silk_pitch_analysis_core_FLP( /* O Voicing estimate: 0 voiced, 1 unvoiced */
@@ -105,7 +107,8 @@ silk_float silk_burg_modified_FLP( /* O returns residual energy
const silk_float minInvGain, /* I minimum inverse prediction gain */
const opus_int subfr_length, /* I input signal subframe length (incl. D preceding samples) */
const opus_int nb_subfr, /* I number of subframes stacked in x */
- const opus_int D /* I order */
+ const opus_int D, /* I order */
+ int arch
);
/* multiply a vector by a constant */
@@ -124,12 +127,17 @@ void silk_scale_copy_vector_FLP(
);
/* inner product of two silk_float arrays, with result as double */
-double silk_inner_product_FLP(
+double silk_inner_product_FLP_c(
const silk_float *data1,
const silk_float *data2,
opus_int dataSize
);
+#ifndef OVERRIDE_inner_product_FLP
+#define silk_inner_product_FLP(data1, data2, dataSize, arch) ((void)arch,silk_inner_product_FLP_c(data1, data2, dataSize))
+#endif
+
+
/* sum of squares of a silk_float array, with result as double */
double silk_energy_FLP(
const silk_float *data,
diff --git a/silk/float/autocorrelation_FLP.c b/silk/float/autocorrelation_FLP.c
index 8b8a9e65..4253b26e 100644
--- a/silk/float/autocorrelation_FLP.c
+++ b/silk/float/autocorrelation_FLP.c
@@ -37,7 +37,8 @@ void silk_autocorrelation_FLP(
silk_float *results, /* O result (length correlationCount) */
const silk_float *inputData, /* I input data to correlate */
opus_int inputDataSize, /* I length of input */
- opus_int correlationCount /* I number of correlation taps to compute */
+ opus_int correlationCount, /* I number of correlation taps to compute */
+ int arch
)
{
opus_int i;
@@ -47,6 +48,6 @@ void silk_autocorrelation_FLP(
}
for( i = 0; i < correlationCount; i++ ) {
- results[ i ] = (silk_float)silk_inner_product_FLP( inputData, inputData + i, inputDataSize - i );
+ results[ i ] = (silk_float)silk_inner_product_FLP( inputData, inputData + i, inputDataSize - i, arch );
}
}
diff --git a/silk/float/burg_modified_FLP.c b/silk/float/burg_modified_FLP.c
index 756b76a3..f5bef5dd 100644
--- a/silk/float/burg_modified_FLP.c
+++ b/silk/float/burg_modified_FLP.c
@@ -42,7 +42,8 @@ silk_float silk_burg_modified_FLP( /* O returns residual energy
const silk_float minInvGain, /* I minimum inverse prediction gain */
const opus_int subfr_length, /* I input signal subframe length (incl. D preceding samples) */
const opus_int nb_subfr, /* I number of subframes stacked in x */
- const opus_int D /* I order */
+ const opus_int D, /* I order */
+ int arch
)
{
opus_int k, n, s, reached_max_gain;
@@ -60,7 +61,7 @@ silk_float silk_burg_modified_FLP( /* O returns residual energy
for( s = 0; s < nb_subfr; s++ ) {
x_ptr = x + s * subfr_length;
for( n = 1; n < D + 1; n++ ) {
- C_first_row[ n - 1 ] += silk_inner_product_FLP( x_ptr, x_ptr + n, subfr_length - n );
+ C_first_row[ n - 1 ] += silk_inner_product_FLP( x_ptr, x_ptr + n, subfr_length - n, arch );
}
}
silk_memcpy( C_last_row, C_first_row, SILK_MAX_ORDER_LPC * sizeof( double ) );
diff --git a/silk/float/corrMatrix_FLP.c b/silk/float/corrMatrix_FLP.c
index eae6a1cf..eef6e8aa 100644
--- a/silk/float/corrMatrix_FLP.c
+++ b/silk/float/corrMatrix_FLP.c
@@ -41,7 +41,8 @@ void silk_corrVector_FLP(
const silk_float *t, /* I Target vector [L] */
const opus_int L, /* I Length of vecors */
const opus_int Order, /* I Max lag for correlation */
- silk_float *Xt /* O X'*t correlation vector [order] */
+ silk_float *Xt, /* O X'*t correlation vector [order] */
+ int arch
)
{
opus_int lag;
@@ -50,7 +51,7 @@ void silk_corrVector_FLP(
ptr1 = &x[ Order - 1 ]; /* Points to first sample of column 0 of X: X[:,0] */
for( lag = 0; lag < Order; lag++ ) {
/* Calculate X[:,lag]'*t */
- Xt[ lag ] = (silk_float)silk_inner_product_FLP( ptr1, t, L );
+ Xt[ lag ] = (silk_float)silk_inner_product_FLP( ptr1, t, L, arch );
ptr1--; /* Next column of X */
}
}
@@ -60,7 +61,8 @@ void silk_corrMatrix_FLP(
const silk_float *x, /* I x vector [ L+order-1 ] used to create X */
const opus_int L, /* I Length of vectors */
const opus_int Order, /* I Max lag for correlation */
- silk_float *XX /* O X'*X correlation matrix [order x order] */
+ silk_float *XX, /* O X'*X correlation matrix [order x order] */
+ int arch
)
{
opus_int j, lag;
@@ -79,7 +81,7 @@ void silk_corrMatrix_FLP(
ptr2 = &x[ Order - 2 ]; /* First sample of column 1 of X */
for( lag = 1; lag < Order; lag++ ) {
/* Calculate X[:,0]'*X[:,lag] */
- energy = silk_inner_product_FLP( ptr1, ptr2, L );
+ energy = silk_inner_product_FLP( ptr1, ptr2, L, arch );
matrix_ptr( XX, lag, 0, Order ) = ( silk_float )energy;
matrix_ptr( XX, 0, lag, Order ) = ( silk_float )energy;
/* Calculate X[:,j]'*X[:,j + lag] */
diff --git a/silk/float/encode_frame_FLP.c b/silk/float/encode_frame_FLP.c
index b029c3f5..8a327c56 100644
--- a/silk/float/encode_frame_FLP.c
+++ b/silk/float/encode_frame_FLP.c
@@ -107,7 +107,10 @@ opus_int silk_encode_frame_FLP(
opus_int gain_lock[ MAX_NB_SUBFR ] = {0};
opus_int16 best_gain_mult[ MAX_NB_SUBFR ];
opus_int best_sum[ MAX_NB_SUBFR ];
+ opus_int bits_margin;
+ /* For CBR, 5 bits below budget is close enough. For VBR, allow up to 25% below the cap if we initially busted the budget. */
+ bits_margin = useCBR ? 5 : maxBits/4;
/* This is totally unnecessary but many compilers (including gcc) are too dumb to realise it */
LastGainIndex_copy2 = nBits_lower = nBits_upper = gainMult_lower = gainMult_upper = 0;
@@ -270,7 +273,7 @@ opus_int silk_encode_frame_FLP(
gainMult_upper = gainMult_Q8;
gainsID_upper = gainsID;
}
- } else if( nBits < maxBits - 5 ) {
+ } else if( nBits < maxBits - bits_margin ) {
found_lower = 1;
nBits_lower = nBits;
gainMult_lower = gainMult_Q8;
@@ -284,7 +287,7 @@ opus_int silk_encode_frame_FLP(
LastGainIndex_copy2 = psEnc->sShape.LastGainIndex;
}
} else {
- /* Within 5 bits of budget: close enough */
+ /* Close enough */
break;
}
@@ -306,15 +309,9 @@ opus_int silk_encode_frame_FLP(
if( ( found_lower & found_upper ) == 0 ) {
/* Adjust gain according to high-rate rate/distortion curve */
if( nBits > maxBits ) {
- if (gainMult_Q8 < 16384) {
- gainMult_Q8 *= 2;
- } else {
- gainMult_Q8 = 32767;
- }
+ gainMult_Q8 = silk_min_32( 1024, gainMult_Q8*3/2 );
} else {
- opus_int32 gain_factor_Q16;
- gain_factor_Q16 = silk_log2lin( silk_LSHIFT( nBits - maxBits, 7 ) / psEnc->sCmn.frame_length + SILK_FIX_CONST( 16, 7 ) );
- gainMult_Q8 = silk_SMULWB( gain_factor_Q16, gainMult_Q8 );
+ gainMult_Q8 = silk_max_32( 64, gainMult_Q8*4/5 );
}
} else {
/* Adjust gain by interpolating */
diff --git a/silk/float/find_LPC_FLP.c b/silk/float/find_LPC_FLP.c
index fa3ffe7f..6ccd711d 100644
--- a/silk/float/find_LPC_FLP.c
+++ b/silk/float/find_LPC_FLP.c
@@ -38,7 +38,8 @@ void silk_find_LPC_FLP(
silk_encoder_state *psEncC, /* I/O Encoder state */
opus_int16 NLSF_Q15[], /* O NLSFs */
const silk_float x[], /* I Input signal */
- const silk_float minInvGain /* I Inverse of max prediction gain */
+ const silk_float minInvGain, /* I Inverse of max prediction gain */
+ int arch
)
{
opus_int k, subfr_length;
@@ -56,12 +57,12 @@ void silk_find_LPC_FLP(
psEncC->indices.NLSFInterpCoef_Q2 = 4;
/* Burg AR analysis for the full frame */
- res_nrg = silk_burg_modified_FLP( a, x, minInvGain, subfr_length, psEncC->nb_subfr, psEncC->predictLPCOrder );
+ res_nrg = silk_burg_modified_FLP( a, x, minInvGain, subfr_length, psEncC->nb_subfr, psEncC->predictLPCOrder, arch );
if( psEncC->useInterpolatedNLSFs && !psEncC->first_frame_after_reset && psEncC->nb_subfr == MAX_NB_SUBFR ) {
/* Optimal solution for last 10 ms; subtract residual energy here, as that's easier than */
/* adding it to the residual energy of the first 10 ms in each iteration of the search below */
- res_nrg -= silk_burg_modified_FLP( a_tmp, x + ( MAX_NB_SUBFR / 2 ) * subfr_length, minInvGain, subfr_length, MAX_NB_SUBFR / 2, psEncC->predictLPCOrder );
+ res_nrg -= silk_burg_modified_FLP( a_tmp, x + ( MAX_NB_SUBFR / 2 ) * subfr_length, minInvGain, subfr_length, MAX_NB_SUBFR / 2, psEncC->predictLPCOrder, arch );
/* Convert to NLSFs */
silk_A2NLSF_FLP( NLSF_Q15, a_tmp, psEncC->predictLPCOrder );
diff --git a/silk/float/find_LTP_FLP.c b/silk/float/find_LTP_FLP.c
index f9706493..90aeeac0 100644
--- a/silk/float/find_LTP_FLP.c
+++ b/silk/float/find_LTP_FLP.c
@@ -38,7 +38,8 @@ void silk_find_LTP_FLP(
const silk_float r_ptr[], /* I LPC residual */
const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */
const opus_int subfr_length, /* I Subframe length */
- const opus_int nb_subfr /* I number of subframes */
+ const opus_int nb_subfr, /* I number of subframes */
+ int arch
)
{
opus_int k;
@@ -50,8 +51,8 @@ void silk_find_LTP_FLP(
XX_ptr = XX;
for( k = 0; k < nb_subfr; k++ ) {
lag_ptr = r_ptr - ( lag[ k ] + LTP_ORDER / 2 );
- silk_corrMatrix_FLP( lag_ptr, subfr_length, LTP_ORDER, XX_ptr );
- silk_corrVector_FLP( lag_ptr, r_ptr, subfr_length, LTP_ORDER, xX_ptr );
+ silk_corrMatrix_FLP( lag_ptr, subfr_length, LTP_ORDER, XX_ptr, arch );
+ silk_corrVector_FLP( lag_ptr, r_ptr, subfr_length, LTP_ORDER, xX_ptr, arch );
xx = ( silk_float )silk_energy_FLP( r_ptr, subfr_length + LTP_ORDER );
temp = 1.0f / silk_max( xx, LTP_CORR_INV_MAX * 0.5f * ( XX_ptr[ 0 ] + XX_ptr[ 24 ] ) + 1.0f );
silk_scale_vector_FLP( XX_ptr, temp, LTP_ORDER * LTP_ORDER );
diff --git a/silk/float/find_pitch_lags_FLP.c b/silk/float/find_pitch_lags_FLP.c
index dedbcd28..1f6bd599 100644
--- a/silk/float/find_pitch_lags_FLP.c
+++ b/silk/float/find_pitch_lags_FLP.c
@@ -82,7 +82,7 @@ void silk_find_pitch_lags_FLP(
silk_apply_sine_window_FLP( Wsig_ptr, x_buf_ptr, 2, psEnc->sCmn.la_pitch );
/* Calculate autocorrelation sequence */
- silk_autocorrelation_FLP( auto_corr, Wsig, psEnc->sCmn.pitch_LPC_win_length, psEnc->sCmn.pitchEstimationLPCOrder + 1 );
+ silk_autocorrelation_FLP( auto_corr, Wsig, psEnc->sCmn.pitch_LPC_win_length, psEnc->sCmn.pitchEstimationLPCOrder + 1, arch );
/* Add white noise, as a fraction of the energy */
auto_corr[ 0 ] += auto_corr[ 0 ] * FIND_PITCH_WHITE_NOISE_FRACTION + 1;
diff --git a/silk/float/find_pred_coefs_FLP.c b/silk/float/find_pred_coefs_FLP.c
index 6f790788..f3c54cf4 100644
--- a/silk/float/find_pred_coefs_FLP.c
+++ b/silk/float/find_pred_coefs_FLP.c
@@ -63,7 +63,7 @@ void silk_find_pred_coefs_FLP(
celt_assert( psEnc->sCmn.ltp_mem_length - psEnc->sCmn.predictLPCOrder >= psEncCtrl->pitchL[ 0 ] + LTP_ORDER / 2 );
/* LTP analysis */
- silk_find_LTP_FLP( XXLTP, xXLTP, res_pitch, psEncCtrl->pitchL, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr );
+ silk_find_LTP_FLP( XXLTP, xXLTP, res_pitch, psEncCtrl->pitchL, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.arch );
/* Quantize LTP gain parameters */
silk_quant_LTP_gains_FLP( psEncCtrl->LTPCoef, psEnc->sCmn.indices.LTPIndex, &psEnc->sCmn.indices.PERIndex,
@@ -102,7 +102,7 @@ void silk_find_pred_coefs_FLP(
}
/* LPC_in_pre contains the LTP-filtered input for voiced, and the unfiltered input for unvoiced */
- silk_find_LPC_FLP( &psEnc->sCmn, NLSF_Q15, LPC_in_pre, minInvGain );
+ silk_find_LPC_FLP( &psEnc->sCmn, NLSF_Q15, LPC_in_pre, minInvGain, psEnc->sCmn.arch );
/* Quantize LSFs */
silk_process_NLSFs_FLP( &psEnc->sCmn, psEncCtrl->PredCoef, NLSF_Q15, psEnc->sCmn.prev_NLSFq_Q15 );
diff --git a/silk/float/inner_product_FLP.c b/silk/float/inner_product_FLP.c
index cdd39d24..88b160ab 100644
--- a/silk/float/inner_product_FLP.c
+++ b/silk/float/inner_product_FLP.c
@@ -32,7 +32,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "SigProc_FLP.h"
/* inner product of two silk_float arrays, with result as double */
-double silk_inner_product_FLP(
+double silk_inner_product_FLP_c(
const silk_float *data1,
const silk_float *data2,
opus_int dataSize
diff --git a/silk/float/main_FLP.h b/silk/float/main_FLP.h
index 5dc0ccf4..2e4435cc 100644
--- a/silk/float/main_FLP.h
+++ b/silk/float/main_FLP.h
@@ -138,7 +138,8 @@ void silk_find_LPC_FLP(
silk_encoder_state *psEncC, /* I/O Encoder state */
opus_int16 NLSF_Q15[], /* O NLSFs */
const silk_float x[], /* I Input signal */
- const silk_float minInvGain /* I Prediction gain from LTP (dB) */
+ const silk_float minInvGain, /* I Prediction gain from LTP (dB) */
+ int arch
);
/* LTP analysis */
@@ -148,7 +149,8 @@ void silk_find_LTP_FLP(
const silk_float r_ptr[], /* I LPC residual */
const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */
const opus_int subfr_length, /* I Subframe length */
- const opus_int nb_subfr /* I number of subframes */
+ const opus_int nb_subfr, /* I number of subframes */
+ int arch
);
void silk_LTP_analysis_filter_FLP(
@@ -221,7 +223,8 @@ void silk_corrMatrix_FLP(
const silk_float *x, /* I x vector [ L+order-1 ] used to create X */
const opus_int L, /* I Length of vectors */
const opus_int Order, /* I Max lag for correlation */
- silk_float *XX /* O X'*X correlation matrix [order x order] */
+ silk_float *XX, /* O X'*X correlation matrix [order x order] */
+ int arch
);
/* Calculates correlation vector X'*t */
@@ -230,7 +233,8 @@ void silk_corrVector_FLP(
const silk_float *t, /* I Target vector [L] */
const opus_int L, /* I Length of vecors */
const opus_int Order, /* I Max lag for correlation */
- silk_float *Xt /* O X'*t correlation vector [order] */
+ silk_float *Xt, /* O X'*t correlation vector [order] */
+ int arch
);
/* Apply sine window to signal vector. */
diff --git a/silk/float/noise_shape_analysis_FLP.c b/silk/float/noise_shape_analysis_FLP.c
index cb3d8a50..0b5ea952 100644
--- a/silk/float/noise_shape_analysis_FLP.c
+++ b/silk/float/noise_shape_analysis_FLP.c
@@ -255,7 +255,7 @@ void silk_noise_shape_analysis_FLP(
psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder );
} else {
/* Calculate regular auto correlation */
- silk_autocorrelation_FLP( auto_corr, x_windowed, psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder + 1 );
+ silk_autocorrelation_FLP( auto_corr, x_windowed, psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder + 1, psEnc->sCmn.arch );
}
/* Add white noise, as a fraction of energy */
diff --git a/silk/float/pitch_analysis_core_FLP.c b/silk/float/pitch_analysis_core_FLP.c
index f351bc37..0530a883 100644
--- a/silk/float/pitch_analysis_core_FLP.c
+++ b/silk/float/pitch_analysis_core_FLP.c
@@ -291,7 +291,7 @@ opus_int silk_pitch_analysis_core_FLP( /* O Voicing estimate: 0 voiced,
for( j = 0; j < length_d_comp; j++ ) {
d = d_comp[ j ];
basis_ptr = target_ptr - d;
- cross_corr = silk_inner_product_FLP( basis_ptr, target_ptr, sf_length_8kHz );
+ cross_corr = silk_inner_product_FLP( basis_ptr, target_ptr, sf_length_8kHz, arch );
if( cross_corr > 0.0f ) {
energy = silk_energy_FLP( basis_ptr, sf_length_8kHz );
C[ k ][ d ] = (silk_float)( 2 * cross_corr / ( energy + energy_tmp ) );
diff --git a/silk/float/warped_autocorrelation_FLP.c b/silk/float/warped_autocorrelation_FLP.c
index 09186e73..116dab92 100644
--- a/silk/float/warped_autocorrelation_FLP.c
+++ b/silk/float/warped_autocorrelation_FLP.c
@@ -54,11 +54,13 @@ void silk_warped_autocorrelation_FLP(
/* Loop over allpass sections */
for( i = 0; i < order; i += 2 ) {
/* Output of allpass section */
- tmp2 = state[ i ] + warping * ( state[ i + 1 ] - tmp1 );
+ /* We voluntarily use two multiples instead of factoring the expression to
+ reduce the length of the dependency chain (tmp1->tmp2->tmp1... ). */
+ tmp2 = state[ i ] + warping * state[ i + 1 ] - warping * tmp1;
state[ i ] = tmp1;
C[ i ] += state[ 0 ] * tmp1;
/* Output of allpass section */
- tmp1 = state[ i + 1 ] + warping * ( state[ i + 2 ] - tmp2 );
+ tmp1 = state[ i + 1 ] + warping * state[ i + 2 ] - warping * tmp2;
state[ i + 1 ] = tmp2;
C[ i + 1 ] += state[ 0 ] * tmp2;
}
diff --git a/silk/float/x86/inner_product_FLP_avx2.c b/silk/float/x86/inner_product_FLP_avx2.c
new file mode 100644
index 00000000..4a2daaf5
--- /dev/null
+++ b/silk/float/x86/inner_product_FLP_avx2.c
@@ -0,0 +1,85 @@
+/***********************************************************************
+Copyright (c) 2006-2011, Skype Limited. All rights reserved.
+ 2023 Amazon
+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 Internet Society, IETF or IETF Trust, nor the
+names of specific 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 THE COPYRIGHT OWNER 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.
+***********************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "SigProc_FLP.h"
+#include <immintrin.h>
+
+
+/* inner product of two silk_float arrays, with result as double */
+double silk_inner_product_FLP_avx2(
+ const silk_float *data1,
+ const silk_float *data2,
+ opus_int dataSize
+)
+{
+ opus_int i;
+ __m256d accum1, accum2;
+ double result;
+
+ /* 4x unrolled loop */
+ result = 0.0;
+ accum1 = accum2 = _mm256_setzero_pd();
+ for( i = 0; i < dataSize - 7; i += 8 ) {
+ __m128 x1f, x2f;
+ __m256d x1d, x2d;
+ x1f = _mm_loadu_ps( &data1[ i ] );
+ x2f = _mm_loadu_ps( &data2[ i ] );
+ x1d = _mm256_cvtps_pd( x1f );
+ x2d = _mm256_cvtps_pd( x2f );
+ accum1 = _mm256_fmadd_pd( x1d, x2d, accum1 );
+ x1f = _mm_loadu_ps( &data1[ i + 4 ] );
+ x2f = _mm_loadu_ps( &data2[ i + 4 ] );
+ x1d = _mm256_cvtps_pd( x1f );
+ x2d = _mm256_cvtps_pd( x2f );
+ accum2 = _mm256_fmadd_pd( x1d, x2d, accum2 );
+ }
+ for( ; i < dataSize - 3; i += 4 ) {
+ __m128 x1f, x2f;
+ __m256d x1d, x2d;
+ x1f = _mm_loadu_ps( &data1[ i ] );
+ x2f = _mm_loadu_ps( &data2[ i ] );
+ x1d = _mm256_cvtps_pd( x1f );
+ x2d = _mm256_cvtps_pd( x2f );
+ accum1 = _mm256_fmadd_pd( x1d, x2d, accum1 );
+ }
+ accum1 = _mm256_add_pd(accum1, accum2);
+ accum1 = _mm256_add_pd(accum1, _mm256_permute2f128_pd(accum1, accum1, 1));
+ accum1 = _mm256_hadd_pd(accum1,accum1);
+ result = _mm256_cvtsd_f64(accum1);
+
+ /* add any remaining products */
+ for( ; i < dataSize; i++ ) {
+ result += data1[ i ] * (double)data2[ i ];
+ }
+
+ return result;
+}
diff --git a/silk/init_decoder.c b/silk/init_decoder.c
index 16c03dcd..01bc4b7a 100644
--- a/silk/init_decoder.c
+++ b/silk/init_decoder.c
@@ -31,15 +31,21 @@ POSSIBILITY OF SUCH DAMAGE.
#include "main.h"
+#ifdef ENABLE_OSCE
+#include "osce.h"
+#endif
+
+#include "structs.h"
+
/************************/
-/* Init Decoder State */
+/* Reset Decoder State */
/************************/
-opus_int silk_init_decoder(
+opus_int silk_reset_decoder(
silk_decoder_state *psDec /* I/O Decoder state pointer */
)
{
/* Clear the entire encoder state, except anything copied */
- silk_memset( psDec, 0, sizeof( silk_decoder_state ) );
+ silk_memset( &psDec->SILK_DECODER_STATE_RESET_START, 0, sizeof( silk_decoder_state ) - ((char*) &psDec->SILK_DECODER_STATE_RESET_START - (char*)psDec) );
/* Used to deactivate LSF interpolation */
psDec->first_frame_after_reset = 1;
@@ -52,6 +58,27 @@ opus_int silk_init_decoder(
/* Reset PLC state */
silk_PLC_Reset( psDec );
+#ifdef ENABLE_OSCE
+ /* Reset OSCE state and method */
+ osce_reset(&psDec->osce, OSCE_DEFAULT_METHOD);
+#endif
+
+ return 0;
+}
+
+
+/************************/
+/* Init Decoder State */
+/************************/
+opus_int silk_init_decoder(
+ silk_decoder_state *psDec /* I/O Decoder state pointer */
+)
+{
+ /* Clear the entire encoder state, except anything copied */
+ silk_memset( psDec, 0, sizeof( silk_decoder_state ) );
+
+ silk_reset_decoder( psDec );
+
return(0);
}
diff --git a/silk/init_encoder.c b/silk/init_encoder.c
index 65995c33..10d41287 100644
--- a/silk/init_encoder.c
+++ b/silk/init_encoder.c
@@ -36,6 +36,10 @@ POSSIBILITY OF SUCH DAMAGE.
#include "tuning_parameters.h"
#include "cpu_support.h"
+#ifdef ENABLE_DRED
+#include "dred_encoder.h"
+#endif
+
/*********************************/
/* Initialize Silk Encoder state */
/*********************************/
diff --git a/silk/main.h b/silk/main.h
index a5f56875..cd576d8c 100644
--- a/silk/main.h
+++ b/silk/main.h
@@ -252,7 +252,7 @@ void silk_NSQ_c(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -278,7 +278,7 @@ void silk_NSQ_del_dec_c(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -389,6 +389,10 @@ void silk_NLSF_decode(
/****************************************************/
/* Decoder Functions */
/****************************************************/
+opus_int silk_reset_decoder(
+ silk_decoder_state *psDec /* I/O Decoder state pointer */
+);
+
opus_int silk_init_decoder(
silk_decoder_state *psDec /* I/O Decoder state pointer */
);
@@ -410,6 +414,12 @@ opus_int silk_decode_frame(
opus_int32 *pN, /* O Pointer to size of output frame */
opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */
opus_int condCoding, /* I The type of conditional coding to use */
+#ifdef ENABLE_DEEP_PLC
+ LPCNetPLCState *lpcnet,
+#endif
+#ifdef ENABLE_OSCE
+ OSCEModel *osce_model,
+#endif
int arch /* I Run-time architecture */
);
diff --git a/silk/meson.build b/silk/meson.build
index 917048b2..80a59b05 100644
--- a/silk/meson.build
+++ b/silk/meson.build
@@ -2,6 +2,8 @@ silk_sources = sources['SILK_SOURCES']
silk_sources_sse4_1 = sources['SILK_SOURCES_SSE4_1']
+silk_sources_avx2 = sources['SILK_SOURCES_AVX2']
+
silk_sources_neon_intr = sources['SILK_SOURCES_ARM_NEON_INTR']
silk_sources_fixed_neon_intr = sources['SILK_SOURCES_FIXED_ARM_NEON_INTR']
@@ -10,6 +12,10 @@ silk_sources_fixed = sources['SILK_SOURCES_FIXED']
silk_sources_fixed_sse4_1 = sources['SILK_SOURCES_FIXED_SSE4_1']
+silk_sources_float_sse4_1 = []
+silk_sources_float_neon_intr = []
+silk_sources_float_avx2 = sources['SILK_SOURCES_FLOAT_AVX2']
+
silk_sources_float = sources['SILK_SOURCES_FLOAT']
if opt_fixed_point
@@ -31,7 +37,7 @@ if host_cpu_family in ['arm', 'aarch64'] and have_arm_intrinsics_or_asm
endif
endif
-foreach intr_name : ['sse4_1', 'neon_intr']
+foreach intr_name : ['sse4_1', 'avx2', 'neon_intr']
have_intr = get_variable('have_' + intr_name)
if not have_intr
continue
@@ -40,6 +46,8 @@ foreach intr_name : ['sse4_1', 'neon_intr']
intr_sources = get_variable('silk_sources_' + intr_name)
if opt_fixed_point
intr_sources += get_variable('silk_sources_fixed_' + intr_name)
+ else
+ intr_sources += get_variable('silk_sources_float_' + intr_name)
endif
intr_args = get_variable('opus_@0@_args'.format(intr_name), [])
@@ -58,6 +66,6 @@ silk_lib = static_library('opus-silk',
silk_sources,
c_args: silk_c_args,
include_directories: silk_includes,
- link_whole: silk_static_libs,
+ link_whole: [silk_static_libs],
dependencies: libm,
install: false)
diff --git a/silk/mips/NSQ_del_dec_mipsr1.h b/silk/mips/NSQ_del_dec_mipsr1.h
index cd70713a..85bfb637 100644
--- a/silk/mips/NSQ_del_dec_mipsr1.h
+++ b/silk/mips/NSQ_del_dec_mipsr1.h
@@ -25,8 +25,8 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
***********************************************************************/
-#ifndef __NSQ_DEL_DEC_MIPSR1_H__
-#define __NSQ_DEL_DEC_MIPSR1_H__
+#ifndef NSQ_DEL_DEC_MIPSR1_H__
+#define NSQ_DEL_DEC_MIPSR1_H__
#ifdef HAVE_CONFIG_H
#include "config.h"
@@ -407,4 +407,4 @@ static inline void silk_noise_shape_quantizer_del_dec(
}
}
-#endif /* __NSQ_DEL_DEC_MIPSR1_H__ */
+#endif /* NSQ_DEL_DEC_MIPSR1_H__ */
diff --git a/silk/mips/macros_mipsr1.h b/silk/mips/macros_mipsr1.h
index 12ed981a..af408802 100644
--- a/silk/mips/macros_mipsr1.h
+++ b/silk/mips/macros_mipsr1.h
@@ -26,8 +26,8 @@ POSSIBILITY OF SUCH DAMAGE.
***********************************************************************/
-#ifndef __SILK_MACROS_MIPSR1_H__
-#define __SILK_MACROS_MIPSR1_H__
+#ifndef SILK_MACROS_MIPSR1_H__
+#define SILK_MACROS_MIPSR1_H__
#define mips_clz(x) __builtin_clz(x)
@@ -89,4 +89,4 @@ static inline opus_int32 silk_CLZ32(opus_int32 in32)
return re32;
}
-#endif /* __SILK_MACROS_MIPSR1_H__ */
+#endif /* SILK_MACROS_MIPSR1_H__ */
diff --git a/silk/structs.h b/silk/structs.h
index 3380c757..38243be1 100644
--- a/silk/structs.h
+++ b/silk/structs.h
@@ -34,6 +34,21 @@ POSSIBILITY OF SUCH DAMAGE.
#include "entenc.h"
#include "entdec.h"
+#ifdef ENABLE_DEEP_PLC
+#include "lpcnet.h"
+#include "lpcnet_private.h"
+#endif
+
+#ifdef ENABLE_DRED
+#include "dred_encoder.h"
+#include "dred_decoder.h"
+#endif
+
+#ifdef ENABLE_OSCE
+#include "osce_config.h"
+#include "osce_structs.h"
+#endif
+
#ifdef __cplusplus
extern "C"
{
@@ -228,6 +243,14 @@ typedef struct {
} silk_encoder_state;
+#ifdef ENABLE_OSCE
+typedef struct {
+ OSCEFeatureState features;
+ OSCEState state;
+ int method;
+} silk_OSCE_struct;
+#endif
+
/* Struct for Packet Loss Concealment */
typedef struct {
opus_int32 pitchL_Q8; /* Pitch lag to use for voiced concealment */
@@ -243,6 +266,7 @@ typedef struct {
opus_int fs_kHz;
opus_int nb_subfr;
opus_int subfr_length;
+ opus_int enable_deep_plc;
} silk_PLC_struct;
/* Struct for CNG */
@@ -259,6 +283,10 @@ typedef struct {
/* Decoder state */
/********************************/
typedef struct {
+#ifdef ENABLE_OSCE
+ silk_OSCE_struct osce;
+#endif
+#define SILK_DECODER_STATE_RESET_START prev_gain_Q16
opus_int32 prev_gain_Q16;
opus_int32 exc_Q14[ MAX_FRAME_LENGTH ];
opus_int32 sLPC_Q14_buf[ MAX_LPC_ORDER ];
diff --git a/silk/tests/test_unit_LPC_inv_pred_gain.c b/silk/tests/test_unit_LPC_inv_pred_gain.c
index 7ca902ad..acdd31af 100644
--- a/silk/tests/test_unit_LPC_inv_pred_gain.c
+++ b/silk/tests/test_unit_LPC_inv_pred_gain.c
@@ -125,5 +125,6 @@ int main(void) {
}
}
printf("silk_LPC_inverse_pred_gain() optimization passed\n");
+ RESTORE_STACK;
return 0;
}
diff --git a/silk/x86/NSQ_del_dec_avx2.c b/silk/x86/NSQ_del_dec_avx2.c
new file mode 100644
index 00000000..43485871
--- /dev/null
+++ b/silk/x86/NSQ_del_dec_avx2.c
@@ -0,0 +1,1075 @@
+/***********************************************************************
+Copyright (c) 2021 Google Inc.
+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 Internet Society, IETF or IETF Trust, nor the
+names of specific 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 THE COPYRIGHT OWNER 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.
+***********************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef OPUS_CHECK_ASM
+#include <string.h>
+#endif
+
+#include "opus_defines.h"
+#include <immintrin.h>
+
+#include "main.h"
+#include "stack_alloc.h"
+#include "NSQ.h"
+#include "celt/x86/x86cpu.h"
+
+/* Returns TRUE if all assumptions met */
+static OPUS_INLINE int verify_assumptions(const silk_encoder_state *psEncC)
+{
+ /* This optimization is based on these assumptions */
+ /* These assumptions are fundamental and hence assert are */
+ /* used. Should any assert triggers, we have to re-visit */
+ /* all related code to make sure it still functions the */
+ /* same as the C implementation. */
+ silk_assert(MAX_DEL_DEC_STATES <= 4 &&
+ MAX_FRAME_LENGTH % 4 == 0 &&
+ MAX_SUB_FRAME_LENGTH % 4 == 0 &&
+ LTP_MEM_LENGTH_MS % 4 == 0 );
+ silk_assert(psEncC->fs_kHz == 8 ||
+ psEncC->fs_kHz == 12 ||
+ psEncC->fs_kHz == 16 );
+ silk_assert(psEncC->nb_subfr <= MAX_NB_SUBFR &&
+ psEncC->nb_subfr > 0 );
+ silk_assert(psEncC->nStatesDelayedDecision <= MAX_DEL_DEC_STATES &&
+ psEncC->nStatesDelayedDecision > 0 );
+ silk_assert(psEncC->ltp_mem_length == psEncC->fs_kHz * LTP_MEM_LENGTH_MS);
+
+ /* Regressions were observed on certain AMD Zen CPUs when */
+ /* nStatesDelayedDecision is 1 or 2. Ideally we should detect */
+ /* these CPUs and enable this optimization on others; however, */
+ /* there is no good way to do so under current OPUS framework. */
+ return psEncC->nStatesDelayedDecision == 3 ||
+ psEncC->nStatesDelayedDecision == 4;
+}
+
+/* Intrinsics not defined on MSVC */
+#ifdef _MSC_VER
+#include <Intsafe.h>
+#define __m128i_u __m128i
+static inline int __builtin_sadd_overflow(opus_int32 a, opus_int32 b, opus_int32* res)
+{
+ *res = a+b;
+ return (*res ^ a) & (*res ^ b) & 0x80000000;
+}
+static inline int __builtin_ctz(unsigned int x)
+{
+ DWORD res = 0;
+ return _BitScanForward(&res, x) ? res : 32;
+}
+#endif
+
+static OPUS_INLINE __m128i silk_cvtepi64_epi32_high(__m256i num)
+{
+ return _mm256_castsi256_si128(_mm256_permutevar8x32_epi32(num, _mm256_set_epi32(0, 0, 0, 0, 7, 5, 3, 1)));
+}
+
+static OPUS_INLINE opus_int16 silk_sat16(opus_int32 num)
+{
+ num = num > silk_int16_MAX ? silk_int16_MAX : num;
+ num = num < silk_int16_MIN ? silk_int16_MIN : num;
+ return num;
+}
+
+static OPUS_INLINE opus_int32 silk_sar_round_32(opus_int32 a, int bits)
+{
+ silk_assert(bits > 0 && bits < 31);
+ a += 1 << (bits-1);
+ return a >> bits;
+}
+
+static OPUS_INLINE opus_int64 silk_sar_round_smulww(opus_int32 a, opus_int32 b, int bits)
+{
+ silk_assert(bits > 0 && bits < 63);
+#ifdef OPUS_CHECK_ASM
+ return silk_RSHIFT_ROUND(silk_SMULWW(a, b), bits);
+#else
+ /* This code is more correct, but it won't overflow like the C code in some rare cases. */
+ silk_assert(bits > 0 && bits < 63);
+ opus_int64 t = ((opus_int64)a) * ((opus_int64)b);
+ bits += 16;
+ t += 1ull << (bits-1);
+ return t >> bits;
+#endif
+}
+
+static OPUS_INLINE opus_int32 silk_add_sat32(opus_int32 a, opus_int32 b)
+{
+ opus_int32 sum;
+ if (__builtin_sadd_overflow(a, b, &sum))
+ {
+ return a >= 0 ? silk_int32_MAX : silk_int32_MIN;
+ }
+ return sum;
+}
+
+static OPUS_INLINE __m128i silk_mm_srai_round_epi32(__m128i a, int bits)
+{
+ silk_assert(bits > 0 && bits < 31);
+ return _mm_srai_epi32(_mm_add_epi32(a, _mm_set1_epi32(1 << (bits - 1))), bits);
+}
+
+/* add/subtract with output saturated */
+static OPUS_INLINE __m128i silk_mm_add_sat_epi32(__m128i a, __m128i b)
+{
+ __m128i r = _mm_add_epi32(a, b);
+ __m128i OF = _mm_and_si128(_mm_xor_si128(a, r), _mm_xor_si128(b, r)); /* OF = (sum ^ a) & (sum ^ b) */
+ __m128i SAT = _mm_add_epi32(_mm_srli_epi32(a, 31), _mm_set1_epi32(0x7FFFFFFF)); /* SAT = (a >> 31) + 0x7FFFFFFF */
+ return _mm_blendv_epi8(r, SAT, _mm_srai_epi32(OF, 31));
+}
+static OPUS_INLINE __m128i silk_mm_sub_sat_epi32(__m128i a, __m128i b)
+{
+ __m128i r = _mm_sub_epi32(a, b);
+ __m128i OF = _mm_andnot_si128(_mm_xor_si128(b, r), _mm_xor_si128(a, r)); /* OF = (sum ^ a) & (sum ^ ~b) = (sum ^ a) & ~(sum ^ b) */
+ __m128i SAT = _mm_add_epi32(_mm_srli_epi32(a, 31), _mm_set1_epi32(0x7FFFFFFF)); /* SAT = (a >> 31) + 0x7FFFFFFF */
+ return _mm_blendv_epi8(r, SAT, _mm_srai_epi32(OF, 31));
+}
+static OPUS_INLINE __m256i silk_mm256_sub_sat_epi32(__m256i a, __m256i b)
+{
+ __m256i r = _mm256_sub_epi32(a, b);
+ __m256i OF = _mm256_andnot_si256(_mm256_xor_si256(b, r), _mm256_xor_si256(a, r)); /* OF = (sum ^ a) & (sum ^ ~b) = (sum ^ a) & ~(sum ^ b) */
+ __m256i SAT = _mm256_add_epi32(_mm256_srli_epi32(a, 31), _mm256_set1_epi32(0x7FFFFFFF)); /* SAT = (a >> 31) + 0x7FFFFFFF */
+ return _mm256_blendv_epi8(r, SAT, _mm256_srai_epi32(OF, 31));
+}
+
+static OPUS_INLINE __m128i silk_mm_limit_epi32(__m128i num, opus_int32 limit1, opus_int32 limit2)
+{
+ opus_int32 lo = limit1 < limit2 ? limit1 : limit2;
+ opus_int32 hi = limit1 > limit2 ? limit1 : limit2;
+
+ num = _mm_min_epi32(num, _mm_set1_epi32(hi));
+ num = _mm_max_epi32(num, _mm_set1_epi32(lo));
+ return num;
+}
+
+/* cond < 0 ? -num : num */
+static OPUS_INLINE __m128i silk_mm_sign_epi32(__m128i num, __m128i cond)
+{
+ return _mm_sign_epi32(num, _mm_or_si128(cond, _mm_set1_epi32(1)));
+}
+static OPUS_INLINE __m256i silk_mm256_sign_epi32(__m256i num, __m256i cond)
+{
+ return _mm256_sign_epi32(num, _mm256_or_si256(cond, _mm256_set1_epi32(1)));
+}
+
+/* (a32 * b32) >> 16 */
+static OPUS_INLINE __m128i silk_mm_smulww_epi32(__m128i a, opus_int32 b)
+{
+ return silk_cvtepi64_epi32_high(_mm256_slli_epi64(_mm256_mul_epi32(_mm256_cvtepi32_epi64(a), _mm256_set1_epi32(b)), 16));
+}
+
+/* (a32 * (opus_int32)((opus_int16)(b32))) >> 16 output have to be 32bit int */
+static OPUS_INLINE __m128i silk_mm_smulwb_epi32(__m128i a, opus_int32 b)
+{
+ return silk_cvtepi64_epi32_high(_mm256_mul_epi32(_mm256_cvtepi32_epi64(a), _mm256_set1_epi32(silk_LSHIFT(b, 16))));
+}
+
+/* (opus_int32)((opus_int16)(a3))) * (opus_int32)((opus_int16)(b32)) output have to be 32bit int */
+static OPUS_INLINE __m256i silk_mm256_smulbb_epi32(__m256i a, __m256i b)
+{
+ const char FF = (char)0xFF;
+ __m256i msk = _mm256_set_epi8(
+ FF, FF, FF, FF, FF, FF, FF, FF, 13, 12, 9, 8, 5, 4, 1, 0,
+ FF, FF, FF, FF, FF, FF, FF, FF, 13, 12, 9, 8, 5, 4, 1, 0);
+ __m256i lo = _mm256_mullo_epi16(a, b);
+ __m256i hi = _mm256_mulhi_epi16(a, b);
+ lo = _mm256_shuffle_epi8(lo, msk);
+ hi = _mm256_shuffle_epi8(hi, msk);
+ return _mm256_unpacklo_epi16(lo, hi);
+}
+
+static OPUS_INLINE __m256i silk_mm256_reverse_epi32(__m256i v)
+{
+ v = _mm256_shuffle_epi32(v, 0x1B);
+ v = _mm256_permute4x64_epi64(v, 0x4E);
+ return v;
+}
+
+static OPUS_INLINE opus_int32 silk_mm256_hsum_epi32(__m256i v)
+{
+ __m128i sum = _mm_add_epi32(_mm256_extracti128_si256(v, 1), _mm256_extracti128_si256(v, 0));
+ sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E));
+ sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1));
+ return _mm_cvtsi128_si32(sum);
+}
+
+static OPUS_INLINE __m128i silk_mm_hmin_epi32(__m128i num)
+{
+ num = _mm_min_epi32(num, _mm_shuffle_epi32(num, 0x4E)); /* 0123 -> 2301 */
+ num = _mm_min_epi32(num, _mm_shuffle_epi32(num, 0xB1)); /* 0123 -> 1032 */
+ return num;
+}
+
+static OPUS_INLINE __m128i silk_mm_hmax_epi32(__m128i num)
+{
+ num = _mm_max_epi32(num, _mm_shuffle_epi32(num, 0x4E)); /* 0123 -> 2310 */
+ num = _mm_max_epi32(num, _mm_shuffle_epi32(num, 0xB1)); /* 0123 -> 1032 */
+ return num;
+}
+
+static OPUS_INLINE __m128i silk_mm_mask_hmin_epi32(__m128i num, __m128i mask)
+{
+ num = _mm_blendv_epi8(num, _mm_set1_epi32(silk_int32_MAX), mask);
+ return silk_mm_hmin_epi32(num);
+}
+
+static OPUS_INLINE __m128i silk_mm_mask_hmax_epi32(__m128i num, __m128i mask)
+{
+ num = _mm_blendv_epi8(num, _mm_set1_epi32(silk_int32_MIN), mask);
+ return silk_mm_hmax_epi32(num);
+}
+
+static OPUS_INLINE __m128i silk_mm256_rand_epi32(__m128i seed)
+{
+ seed = _mm_mullo_epi32(seed, _mm_set1_epi32(RAND_MULTIPLIER));
+ seed = _mm_add_epi32(seed, _mm_set1_epi32(RAND_INCREMENT));
+ return seed;
+}
+
+static OPUS_INLINE opus_int32 silk_index_of_first_equal_epi32(__m128i a, __m128i b)
+{
+ unsigned int mask = _mm_movemask_epi8(_mm_cmpeq_epi32(a, b)) & 0x1111;
+ silk_assert(mask != 0);
+ return __builtin_ctz(mask) >> 2;
+}
+
+static __m128i silk_index_to_selector(opus_int32 index)
+{
+ silk_assert(index < 4);
+ index <<= 2;
+ return _mm_set_epi8(
+ index + 3, index + 2, index + 1, index + 0,
+ index + 3, index + 2, index + 1, index + 0,
+ index + 3, index + 2, index + 1, index + 0,
+ index + 3, index + 2, index + 1, index + 0);
+}
+
+static opus_int32 silk_select_winner(__m128i num, __m128i selector)
+{
+ return _mm_cvtsi128_si32(_mm_shuffle_epi8(num, selector));
+}
+
+typedef struct
+{
+ __m128i RandState;
+ __m128i Q_Q10;
+ __m128i Xq_Q14;
+ __m128i Pred_Q15;
+ __m128i Shape_Q14;
+} NSQ_del_dec_sample_struct;
+
+typedef struct
+{
+ __m128i sLPC_Q14[MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH];
+ __m128i LF_AR_Q14;
+ __m128i Seed;
+ __m128i SeedInit;
+ __m128i RD_Q10;
+ __m128i Diff_Q14;
+ __m128i sAR2_Q14[MAX_SHAPE_LPC_ORDER];
+ NSQ_del_dec_sample_struct Samples[DECISION_DELAY];
+} NSQ_del_dec_struct;
+
+static OPUS_INLINE void silk_nsq_del_dec_scale_states_avx2(
+ const silk_encoder_state *psEncC, /* I Encoder State */
+ silk_nsq_state *NSQ, /* I/O NSQ state */
+ NSQ_del_dec_struct *psDelDec, /* I/O Delayed decision states */
+ const opus_int16 x16[], /* I Input */
+ opus_int32 x_sc_Q10[MAX_SUB_FRAME_LENGTH], /* O Input scaled with 1/Gain in Q10 */
+ const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */
+ opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */
+ opus_int subfr, /* I Subframe number */
+ const opus_int LTP_scale_Q14, /* I LTP state scaling */
+ const opus_int32 Gains_Q16[MAX_NB_SUBFR], /* I */
+ const opus_int pitchL[MAX_NB_SUBFR], /* I Pitch lag */
+ const opus_int signal_type, /* I Signal type */
+ const opus_int decisionDelay /* I Decision delay */
+);
+
+/*******************************************/
+/* LPC analysis filter */
+/* NB! State is kept internally and the */
+/* filter always starts with zero state */
+/* first d output samples are set to zero */
+/*******************************************/
+static OPUS_INLINE void silk_LPC_analysis_filter_avx2(
+ opus_int16 *out, /* O Output signal */
+ const opus_int16 *in, /* I Input signal */
+ const opus_int16 *B, /* I MA prediction coefficients, Q12 [order] */
+ const opus_int32 len, /* I Signal length */
+ const opus_int32 order /* I Filter order */
+);
+
+/******************************************/
+/* Noise shape quantizer for one subframe */
+/******************************************/
+static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_avx2(
+ silk_nsq_state *NSQ, /* I/O NSQ state */
+ NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */
+ opus_int signalType, /* I Signal type */
+ const opus_int32 x_Q10[], /* I */
+ opus_int8 pulses[], /* O */
+ opus_int16 xq[], /* O */
+ opus_int32 sLTP_Q15[], /* I/O LTP filter state */
+ opus_int32 delayedGain_Q10[DECISION_DELAY], /* I/O Gain delay buffer */
+ const opus_int16 a_Q12[], /* I Short term prediction coefs */
+ const opus_int16 b_Q14[], /* I Long term prediction coefs */
+ const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */
+ opus_int lag, /* I Pitch lag */
+ opus_int32 HarmShapeFIRPacked_Q14, /* I */
+ opus_int Tilt_Q14, /* I Spectral tilt */
+ opus_int32 LF_shp_Q14, /* I */
+ opus_int32 Gain_Q16, /* I */
+ opus_int Lambda_Q10, /* I */
+ opus_int offset_Q10, /* I */
+ opus_int length, /* I Input length */
+ opus_int subfr, /* I Subframe number */
+ opus_int shapingLPCOrder, /* I Shaping LPC filter order */
+ opus_int predictLPCOrder, /* I Prediction filter order */
+ opus_int warping_Q16, /* I */
+ __m128i MaskDelDec, /* I Mask of states in decision tree */
+ opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */
+ opus_int decisionDelay /* I */
+);
+
+void silk_NSQ_del_dec_avx2(
+ const silk_encoder_state *psEncC, /* I Encoder State */
+ silk_nsq_state *NSQ, /* I/O NSQ state */
+ SideInfoIndices *psIndices, /* I/O Quantization Indices */
+ const opus_int16 x16[], /* I Input */
+ opus_int8 pulses[], /* O Quantized pulse signal */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
+ const opus_int16 LTPCoef_Q14[LTP_ORDER * MAX_NB_SUBFR], /* I Long term prediction coefs */
+ const opus_int16 AR_Q13[MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER], /* I Noise shaping coefs */
+ const opus_int HarmShapeGain_Q14[MAX_NB_SUBFR], /* I Long term shaping coefs */
+ const opus_int Tilt_Q14[MAX_NB_SUBFR], /* I Spectral tilt */
+ const opus_int32 LF_shp_Q14[MAX_NB_SUBFR], /* I Low frequency shaping coefs */
+ const opus_int32 Gains_Q16[MAX_NB_SUBFR], /* I Quantization step sizes */
+ const opus_int32 pitchL[MAX_NB_SUBFR], /* I Pitch lags */
+ const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */
+ const opus_int LTP_scale_Q14 /* I LTP state scaling */
+)
+{
+#ifdef OPUS_CHECK_ASM
+ silk_nsq_state NSQ_c;
+ SideInfoIndices psIndices_c;
+ opus_int8 pulses_c[MAX_FRAME_LENGTH];
+ const opus_int8 *const pulses_a = pulses;
+
+ silk_memcpy(&NSQ_c, NSQ, sizeof(NSQ_c));
+ silk_memcpy(&psIndices_c, psIndices, sizeof(psIndices_c));
+ silk_memcpy(pulses_c, pulses, sizeof(pulses_c));
+ silk_NSQ_del_dec_c(psEncC, &NSQ_c, &psIndices_c, x16, pulses_c, PredCoef_Q12, LTPCoef_Q14, AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16,
+ pitchL, Lambda_Q10, LTP_scale_Q14);
+#endif
+
+ if (!verify_assumptions(psEncC))
+ {
+ silk_NSQ_del_dec_c(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14);
+ return;
+ }
+
+ opus_int i, k, lag, start_idx, LSF_interpolation_flag, Winner_ind, subfr;
+ opus_int last_smple_idx, smpl_buf_idx, decisionDelay;
+ const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13;
+ opus_int16 *pxq;
+ VARDECL(opus_int32, sLTP_Q15);
+ VARDECL(opus_int16, sLTP);
+ opus_int32 HarmShapeFIRPacked_Q14;
+ opus_int offset_Q10;
+ opus_int32 Gain_Q10;
+ opus_int32 x_sc_Q10[MAX_SUB_FRAME_LENGTH];
+ opus_int32 delayedGain_Q10[DECISION_DELAY];
+ NSQ_del_dec_struct psDelDec = {0};
+ NSQ_del_dec_sample_struct *psSample;
+ __m128i RDmin_Q10, MaskDelDec, Winner_selector;
+ SAVE_STACK;
+
+ MaskDelDec = _mm_cvtepi8_epi32(_mm_cvtsi32_si128(0xFFFFFF00ul << ((psEncC->nStatesDelayedDecision - 1) << 3)));
+
+ /* Set unvoiced lag to the previous one, overwrite later for voiced */
+ lag = NSQ->lagPrev;
+
+ silk_assert(NSQ->prev_gain_Q16 != 0);
+ psDelDec.Seed = _mm_and_si128(
+ _mm_add_epi32(_mm_set_epi32(3, 2, 1, 0), _mm_set1_epi32(psIndices->Seed)),
+ _mm_set1_epi32(3));
+ psDelDec.SeedInit = psDelDec.Seed;
+ psDelDec.RD_Q10 = _mm_setzero_si128();
+ psDelDec.LF_AR_Q14 = _mm_set1_epi32(NSQ->sLF_AR_shp_Q14);
+ psDelDec.Diff_Q14 = _mm_set1_epi32(NSQ->sDiff_shp_Q14);
+ psDelDec.Samples[0].Shape_Q14 = _mm_set1_epi32(NSQ->sLTP_shp_Q14[psEncC->ltp_mem_length - 1]);
+ for (i = 0; i < NSQ_LPC_BUF_LENGTH; i++)
+ {
+ psDelDec.sLPC_Q14[i] = _mm_set1_epi32(NSQ->sLPC_Q14[i]);
+ }
+ for (i = 0; i < MAX_SHAPE_LPC_ORDER; i++)
+ {
+ psDelDec.sAR2_Q14[i] = _mm_set1_epi32(NSQ->sAR2_Q14[i]);
+ }
+
+ offset_Q10 = silk_Quantization_Offsets_Q10[psIndices->signalType >> 1][psIndices->quantOffsetType];
+ smpl_buf_idx = 0; /* index of oldest samples */
+
+ decisionDelay = silk_min_int(DECISION_DELAY, psEncC->subfr_length);
+
+ /* For voiced frames limit the decision delay to lower than the pitch lag */
+ if (psIndices->signalType == TYPE_VOICED)
+ {
+ for (k = 0; k < psEncC->nb_subfr; k++)
+ {
+ decisionDelay = silk_min_int(decisionDelay, pitchL[k] - LTP_ORDER / 2 - 1);
+ }
+ }
+ else
+ {
+ if (lag > 0)
+ {
+ decisionDelay = silk_min_int(decisionDelay, lag - LTP_ORDER / 2 - 1);
+ }
+ }
+
+ if (psIndices->NLSFInterpCoef_Q2 == 4)
+ {
+ LSF_interpolation_flag = 0;
+ }
+ else
+ {
+ LSF_interpolation_flag = 1;
+ }
+
+ ALLOC(sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32);
+ ALLOC(sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16);
+ /* Set up pointers to start of sub frame */
+ pxq = &NSQ->xq[psEncC->ltp_mem_length];
+ NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length;
+ NSQ->sLTP_buf_idx = psEncC->ltp_mem_length;
+ subfr = 0;
+ for (k = 0; k < psEncC->nb_subfr; k++)
+ {
+ A_Q12 = &PredCoef_Q12[((k >> 1) | (1 ^ LSF_interpolation_flag)) * MAX_LPC_ORDER];
+ B_Q14 = &LTPCoef_Q14[k * LTP_ORDER];
+ AR_shp_Q13 = &AR_Q13[k * MAX_SHAPE_LPC_ORDER];
+
+ /* Noise shape parameters */
+ silk_assert(HarmShapeGain_Q14[k] >= 0);
+ HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 );
+ HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 );
+
+ NSQ->rewhite_flag = 0;
+ if (psIndices->signalType == TYPE_VOICED)
+ {
+ /* Voiced */
+ lag = pitchL[k];
+
+ /* Re-whitening */
+ if ((k & (3 ^ (LSF_interpolation_flag << 1))) == 0)
+ {
+ if (k == 2)
+ {
+ /* RESET DELAYED DECISIONS */
+ /* Find winner */
+ RDmin_Q10 = silk_mm_mask_hmin_epi32(psDelDec.RD_Q10, MaskDelDec);
+ Winner_ind = silk_index_of_first_equal_epi32(RDmin_Q10, psDelDec.RD_Q10);
+ Winner_selector = silk_index_to_selector(Winner_ind);
+ psDelDec.RD_Q10 = _mm_add_epi32(
+ psDelDec.RD_Q10,
+ _mm_blendv_epi8(
+ _mm_set1_epi32(silk_int32_MAX >> 4),
+ _mm_setzero_si128(),
+ _mm_cvtepi8_epi32(_mm_cvtsi32_si128(0xFFU << (unsigned)(Winner_ind << 3)))));
+
+ /* Copy final part of signals from winner state to output and long-term filter states */
+ last_smple_idx = smpl_buf_idx + decisionDelay;
+ for (i = 0; i < decisionDelay; i++)
+ {
+ last_smple_idx = (last_smple_idx + DECISION_DELAY - 1) % DECISION_DELAY;
+ psSample = &psDelDec.Samples[last_smple_idx];
+ pulses[i - decisionDelay] =
+ (opus_int8)silk_sar_round_32(silk_select_winner(psSample->Q_Q10, Winner_selector), 10);
+ pxq[i - decisionDelay] =
+ silk_sat16((opus_int32)silk_sar_round_smulww(silk_select_winner(psSample->Xq_Q14, Winner_selector), Gains_Q16[1], 14));
+ NSQ->sLTP_shp_Q14[NSQ->sLTP_shp_buf_idx - decisionDelay + i] =
+ silk_select_winner(psSample->Shape_Q14, Winner_selector);
+ }
+
+ subfr = 0;
+ }
+
+ /* Rewhiten with new A coefs */
+ start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2;
+ silk_assert(start_idx > 0);
+
+ silk_LPC_analysis_filter_avx2(&sLTP[start_idx], &NSQ->xq[start_idx + k * psEncC->subfr_length],
+ A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder);
+
+ NSQ->sLTP_buf_idx = psEncC->ltp_mem_length;
+ NSQ->rewhite_flag = 1;
+ }
+ }
+
+ silk_nsq_del_dec_scale_states_avx2(psEncC, NSQ, &psDelDec, x16, x_sc_Q10, sLTP, sLTP_Q15, k,
+ LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType, decisionDelay);
+
+ silk_noise_shape_quantizer_del_dec_avx2(NSQ, &psDelDec, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15,
+ delayedGain_Q10, A_Q12, B_Q14, AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[k], LF_shp_Q14[k],
+ Gains_Q16[k], Lambda_Q10, offset_Q10, psEncC->subfr_length, subfr++, psEncC->shapingLPCOrder,
+ psEncC->predictLPCOrder, psEncC->warping_Q16, MaskDelDec, &smpl_buf_idx, decisionDelay);
+
+ x16 += psEncC->subfr_length;
+ pulses += psEncC->subfr_length;
+ pxq += psEncC->subfr_length;
+ }
+
+ /* Find winner */
+ RDmin_Q10 = silk_mm_mask_hmin_epi32(psDelDec.RD_Q10, MaskDelDec);
+ Winner_selector = silk_index_to_selector(silk_index_of_first_equal_epi32(RDmin_Q10, psDelDec.RD_Q10));
+
+ /* Copy final part of signals from winner state to output and long-term filter states */
+ psIndices->Seed = silk_select_winner(psDelDec.SeedInit, Winner_selector);
+ last_smple_idx = smpl_buf_idx + decisionDelay;
+ Gain_Q10 = Gains_Q16[psEncC->nb_subfr - 1] >> 6;
+ for (i = 0; i < decisionDelay; i++)
+ {
+ last_smple_idx = (last_smple_idx + DECISION_DELAY - 1) % DECISION_DELAY;
+ psSample = &psDelDec.Samples[last_smple_idx];
+
+ pulses[i - decisionDelay] =
+ (opus_int8)silk_sar_round_32(silk_select_winner(psSample->Q_Q10, Winner_selector), 10);
+ pxq[i - decisionDelay] =
+ silk_sat16((opus_int32)silk_sar_round_smulww(silk_select_winner(psSample->Xq_Q14, Winner_selector), Gain_Q10, 8));
+ NSQ->sLTP_shp_Q14[NSQ->sLTP_shp_buf_idx - decisionDelay + i] =
+ silk_select_winner(psSample->Shape_Q14, Winner_selector);
+ }
+ for (i = 0; i < NSQ_LPC_BUF_LENGTH; i++)
+ {
+ NSQ->sLPC_Q14[i] = silk_select_winner(psDelDec.sLPC_Q14[i], Winner_selector);
+ }
+ for (i = 0; i < MAX_SHAPE_LPC_ORDER; i++)
+ {
+ NSQ->sAR2_Q14[i] = silk_select_winner(psDelDec.sAR2_Q14[i], Winner_selector);
+ }
+
+ /* Update states */
+ NSQ->sLF_AR_shp_Q14 = silk_select_winner(psDelDec.LF_AR_Q14, Winner_selector);
+ NSQ->sDiff_shp_Q14 = silk_select_winner(psDelDec.Diff_Q14, Winner_selector);
+ NSQ->lagPrev = pitchL[psEncC->nb_subfr - 1];
+
+ /* Save quantized speech signal */
+ silk_memmove(NSQ->xq, &NSQ->xq[psEncC->frame_length], psEncC->ltp_mem_length * sizeof(opus_int16));
+ silk_memmove(NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[psEncC->frame_length], psEncC->ltp_mem_length * sizeof(opus_int32));
+
+#ifdef OPUS_CHECK_ASM
+ silk_assert(!memcmp(&NSQ_c, NSQ, sizeof(NSQ_c)));
+ silk_assert(!memcmp(&psIndices_c, psIndices, sizeof(psIndices_c)));
+ silk_assert(!memcmp(pulses_c, pulses_a, sizeof(pulses_c)));
+#endif
+
+ RESTORE_STACK;
+}
+
+static OPUS_INLINE __m128i silk_noise_shape_quantizer_short_prediction_x4(const __m128i *buf32, const opus_int16 *coef16, opus_int order)
+{
+ __m256i out;
+ silk_assert(order == 10 || order == 16);
+
+ /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */
+ out = _mm256_set1_epi32(order >> 1);
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-0]), _mm256_set1_epi32(silk_LSHIFT(coef16[0], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-1]), _mm256_set1_epi32(silk_LSHIFT(coef16[1], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-2]), _mm256_set1_epi32(silk_LSHIFT(coef16[2], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-3]), _mm256_set1_epi32(silk_LSHIFT(coef16[3], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-4]), _mm256_set1_epi32(silk_LSHIFT(coef16[4], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-5]), _mm256_set1_epi32(silk_LSHIFT(coef16[5], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-6]), _mm256_set1_epi32(silk_LSHIFT(coef16[6], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-7]), _mm256_set1_epi32(silk_LSHIFT(coef16[7], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-8]), _mm256_set1_epi32(silk_LSHIFT(coef16[8], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-9]), _mm256_set1_epi32(silk_LSHIFT(coef16[9], 16)))); /* High DWORD */
+
+ if (order == 16)
+ {
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-10]), _mm256_set1_epi32(silk_LSHIFT(coef16[10], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-11]), _mm256_set1_epi32(silk_LSHIFT(coef16[11], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-12]), _mm256_set1_epi32(silk_LSHIFT(coef16[12], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-13]), _mm256_set1_epi32(silk_LSHIFT(coef16[13], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-14]), _mm256_set1_epi32(silk_LSHIFT(coef16[14], 16)))); /* High DWORD */
+ out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-15]), _mm256_set1_epi32(silk_LSHIFT(coef16[15], 16)))); /* High DWORD */
+ }
+ return silk_cvtepi64_epi32_high(out);
+}
+
+/******************************************/
+/* Noise shape quantizer for one subframe */
+/******************************************/
+static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_avx2(
+ silk_nsq_state *NSQ, /* I/O NSQ state */
+ NSQ_del_dec_struct *psDelDec, /* I/O Delayed decision states */
+ opus_int signalType, /* I Signal type */
+ const opus_int32 x_Q10[], /* I */
+ opus_int8 pulses[], /* O */
+ opus_int16 xq[], /* O */
+ opus_int32 sLTP_Q15[], /* I/O LTP filter state */
+ opus_int32 delayedGain_Q10[DECISION_DELAY], /* I/O Gain delay buffer */
+ const opus_int16 a_Q12[], /* I Short term prediction coefs */
+ const opus_int16 b_Q14[], /* I Long term prediction coefs */
+ const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */
+ opus_int lag, /* I Pitch lag */
+ opus_int32 HarmShapeFIRPacked_Q14, /* I */
+ opus_int Tilt_Q14, /* I Spectral tilt */
+ opus_int32 LF_shp_Q14, /* I */
+ opus_int32 Gain_Q16, /* I */
+ opus_int Lambda_Q10, /* I */
+ opus_int offset_Q10, /* I */
+ opus_int length, /* I Input length */
+ opus_int subfr, /* I Subframe number */
+ opus_int shapingLPCOrder, /* I Shaping LPC filter order */
+ opus_int predictLPCOrder, /* I Prediction filter order */
+ opus_int warping_Q16, /* I */
+ __m128i MaskDelDec, /* I Mask of states in decision tree */
+ opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */
+ opus_int decisionDelay /* I */
+)
+{
+ int i;
+ opus_int32 *shp_lag_ptr = &NSQ->sLTP_shp_Q14[NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2];
+ opus_int32 *pred_lag_ptr = &sLTP_Q15[NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2];
+ opus_int32 Gain_Q10 = Gain_Q16 >> 6;
+
+ for (i = 0; i < length; i++)
+ {
+ /* Perform common calculations used in all states */
+ /* NSQ_sample_struct */
+ /* Low 128 bits => 1st set */
+ /* High 128 bits => 2nd set */
+ int j;
+ __m256i SS_Q_Q10;
+ __m256i SS_RD_Q10;
+ __m256i SS_xq_Q14;
+ __m256i SS_LF_AR_Q14;
+ __m256i SS_Diff_Q14;
+ __m256i SS_sLTP_shp_Q14;
+ __m256i SS_LPC_exc_Q14;
+ __m256i exc_Q14;
+ __m256i q_Q10, rr_Q10, rd_Q10;
+ __m256i mask;
+ __m128i LPC_pred_Q14, n_AR_Q14;
+ __m128i RDmin_Q10, RDmax_Q10;
+ __m128i n_LF_Q14;
+ __m128i r_Q10, q1_Q0, q1_Q10, q2_Q10;
+ __m128i Winner_rand_state, Winner_selector;
+ __m128i tmp0, tmp1;
+ NSQ_del_dec_sample_struct *psLastSample, *psSample;
+ opus_int32 RDmin_ind, RDmax_ind, last_smple_idx;
+ opus_int32 LTP_pred_Q14, n_LTP_Q14;
+
+ /* Long-term prediction */
+ if (signalType == TYPE_VOICED)
+ {
+ /* Unrolled loop */
+ /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */
+ LTP_pred_Q14 = 2;
+ LTP_pred_Q14 += silk_SMULWB(pred_lag_ptr[-0], b_Q14[0]);
+ LTP_pred_Q14 += silk_SMULWB(pred_lag_ptr[-1], b_Q14[1]);
+ LTP_pred_Q14 += silk_SMULWB(pred_lag_ptr[-2], b_Q14[2]);
+ LTP_pred_Q14 += silk_SMULWB(pred_lag_ptr[-3], b_Q14[3]);
+ LTP_pred_Q14 += silk_SMULWB(pred_lag_ptr[-4], b_Q14[4]);
+ LTP_pred_Q14 = silk_LSHIFT(LTP_pred_Q14, 1); /* Q13 -> Q14 */
+ pred_lag_ptr++;
+ }
+ else
+ {
+ LTP_pred_Q14 = 0;
+ }
+
+ /* Long-term shaping */
+ if (lag > 0)
+ {
+ /* Symmetric, packed FIR coefficients */
+ n_LTP_Q14 = silk_add_sat32(shp_lag_ptr[0], shp_lag_ptr[-2]);
+ n_LTP_Q14 = silk_SMULWB(n_LTP_Q14, HarmShapeFIRPacked_Q14);
+ n_LTP_Q14 = n_LTP_Q14 + silk_SMULWT(shp_lag_ptr[-1], HarmShapeFIRPacked_Q14);
+ n_LTP_Q14 = LTP_pred_Q14 - (silk_LSHIFT(n_LTP_Q14, 2)); /* Q12 -> Q14 */
+ shp_lag_ptr++;
+ }
+ else
+ {
+ n_LTP_Q14 = 0;
+ }
+
+ /* BEGIN Updating Delayed Decision States */
+
+ /* Generate dither */
+ psDelDec->Seed = silk_mm256_rand_epi32(psDelDec->Seed);
+
+ /* Short-term prediction */
+ LPC_pred_Q14 = silk_noise_shape_quantizer_short_prediction_x4(&psDelDec->sLPC_Q14[NSQ_LPC_BUF_LENGTH - 1 + i], a_Q12, predictLPCOrder);
+ LPC_pred_Q14 = _mm_slli_epi32(LPC_pred_Q14, 4); /* Q10 -> Q14 */
+
+ /* Noise shape feedback */
+ silk_assert(shapingLPCOrder > 0);
+ silk_assert((shapingLPCOrder & 1) == 0); /* check that order is even */
+ /* Output of lowpass section */
+ tmp0 = _mm_add_epi32(psDelDec->Diff_Q14, silk_mm_smulwb_epi32(psDelDec->sAR2_Q14[0], warping_Q16));
+ n_AR_Q14 = _mm_set1_epi32(shapingLPCOrder >> 1);
+ for (j = 0; j < shapingLPCOrder - 1; j++)
+ {
+ /* Output of allpass section */
+ tmp1 = psDelDec->sAR2_Q14[j];
+ psDelDec->sAR2_Q14[j] = tmp0;
+ n_AR_Q14 = _mm_add_epi32(n_AR_Q14, silk_mm_smulwb_epi32(tmp0, AR_shp_Q13[j]));
+ tmp0 = _mm_add_epi32(tmp1, silk_mm_smulwb_epi32(_mm_sub_epi32(psDelDec->sAR2_Q14[j + 1], tmp0), warping_Q16));
+ }
+ psDelDec->sAR2_Q14[shapingLPCOrder - 1] = tmp0;
+ n_AR_Q14 = _mm_add_epi32(n_AR_Q14, silk_mm_smulwb_epi32(tmp0, AR_shp_Q13[shapingLPCOrder - 1]));
+
+ n_AR_Q14 = _mm_slli_epi32(n_AR_Q14, 1); /* Q11 -> Q12 */
+ n_AR_Q14 = _mm_add_epi32(n_AR_Q14, silk_mm_smulwb_epi32(psDelDec->LF_AR_Q14, Tilt_Q14)); /* Q12 */
+ n_AR_Q14 = _mm_slli_epi32(n_AR_Q14, 2); /* Q12 -> Q14 */
+
+ tmp0 = silk_mm_smulwb_epi32(psDelDec->Samples[*smpl_buf_idx].Shape_Q14, LF_shp_Q14); /* Q12 */
+ tmp1 = silk_mm_smulwb_epi32(psDelDec->LF_AR_Q14, LF_shp_Q14 >> 16); /* Q12 */
+ n_LF_Q14 = _mm_add_epi32(tmp0, tmp1); /* Q12 */
+ n_LF_Q14 = _mm_slli_epi32(n_LF_Q14, 2); /* Q12 -> Q14 */
+
+ /* Input minus prediction plus noise feedback */
+ /* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */
+ tmp0 = silk_mm_add_sat_epi32(n_AR_Q14, n_LF_Q14); /* Q14 */
+ tmp1 = _mm_add_epi32(_mm_set1_epi32(n_LTP_Q14), LPC_pred_Q14); /* Q13 */
+ tmp0 = silk_mm_sub_sat_epi32(tmp1, tmp0); /* Q13 */
+ tmp0 = silk_mm_srai_round_epi32(tmp0, 4); /* Q10 */
+
+ r_Q10 = _mm_sub_epi32(_mm_set1_epi32(x_Q10[i]), tmp0); /* residual error Q10 */
+
+ /* Flip sign depending on dither */
+ r_Q10 = silk_mm_sign_epi32(r_Q10, psDelDec->Seed);
+ r_Q10 = silk_mm_limit_epi32(r_Q10, -(31 << 10), 30 << 10);
+
+ /* Find two quantization level candidates and measure their rate-distortion */
+ q1_Q10 = _mm_sub_epi32(r_Q10, _mm_set1_epi32(offset_Q10));
+ q1_Q0 = _mm_srai_epi32(q1_Q10, 10);
+ if (Lambda_Q10 > 2048)
+ {
+ /* For aggressive RDO, the bias becomes more than one pulse. */
+ tmp0 = _mm_sub_epi32(_mm_abs_epi32(q1_Q10), _mm_set1_epi32(Lambda_Q10 / 2 - 512)); /* rdo_offset */
+ q1_Q0 = _mm_srai_epi32(q1_Q10, 31);
+ tmp1 = _mm_cmpgt_epi32(tmp0, _mm_setzero_si128());
+ tmp0 = _mm_srai_epi32(silk_mm_sign_epi32(tmp0, q1_Q10), 10);
+ q1_Q0 = _mm_blendv_epi8(q1_Q0, tmp0, tmp1);
+ }
+
+ tmp0 = _mm_sign_epi32(_mm_set1_epi32(QUANT_LEVEL_ADJUST_Q10), q1_Q0);
+ q1_Q10 = _mm_sub_epi32(_mm_slli_epi32(q1_Q0, 10), tmp0);
+ q1_Q10 = _mm_add_epi32(q1_Q10, _mm_set1_epi32(offset_Q10));
+
+ /* check if q1_Q0 is 0 or -1 */
+ tmp0 = _mm_add_epi32(_mm_srli_epi32(q1_Q0, 31), q1_Q0);
+ tmp1 = _mm_cmpeq_epi32(tmp0, _mm_setzero_si128());
+ tmp0 = _mm_blendv_epi8(_mm_set1_epi32(1024), _mm_set1_epi32(1024 - QUANT_LEVEL_ADJUST_Q10), tmp1);
+ q2_Q10 = _mm_add_epi32(q1_Q10, tmp0);
+ q_Q10 = _mm256_set_m128i(q2_Q10, q1_Q10);
+
+ rr_Q10 = _mm256_sub_epi32(_mm256_broadcastsi128_si256(r_Q10), q_Q10);
+ rd_Q10 = _mm256_abs_epi32(q_Q10);
+ rr_Q10 = silk_mm256_smulbb_epi32(rr_Q10, rr_Q10);
+ rd_Q10 = silk_mm256_smulbb_epi32(rd_Q10, _mm256_set1_epi32(Lambda_Q10));
+ rd_Q10 = _mm256_add_epi32(rd_Q10, rr_Q10);
+ rd_Q10 = _mm256_srai_epi32(rd_Q10, 10);
+
+ mask = _mm256_broadcastsi128_si256(_mm_cmplt_epi32(_mm256_extracti128_si256(rd_Q10, 0), _mm256_extracti128_si256(rd_Q10, 1)));
+ SS_RD_Q10 = _mm256_add_epi32(
+ _mm256_broadcastsi128_si256(psDelDec->RD_Q10),
+ _mm256_blendv_epi8(
+ _mm256_permute2x128_si256(rd_Q10, rd_Q10, 0x1),
+ rd_Q10,
+ mask));
+ SS_Q_Q10 = _mm256_blendv_epi8(
+ _mm256_permute2x128_si256(q_Q10, q_Q10, 0x1),
+ q_Q10,
+ mask);
+
+ /* Update states for best and second best quantization */
+
+ /* Quantized excitation */
+ exc_Q14 = silk_mm256_sign_epi32(_mm256_slli_epi32(SS_Q_Q10, 4), _mm256_broadcastsi128_si256(psDelDec->Seed));
+
+ /* Add predictions */
+ exc_Q14 = _mm256_add_epi32(exc_Q14, _mm256_set1_epi32(LTP_pred_Q14));
+ SS_LPC_exc_Q14 = _mm256_slli_epi32(exc_Q14, 1);
+ SS_xq_Q14 = _mm256_add_epi32(exc_Q14, _mm256_broadcastsi128_si256(LPC_pred_Q14));
+
+ /* Update states */
+ SS_Diff_Q14 = _mm256_sub_epi32(SS_xq_Q14, _mm256_set1_epi32(silk_LSHIFT(x_Q10[i], 4)));
+ SS_LF_AR_Q14 = _mm256_sub_epi32(SS_Diff_Q14, _mm256_broadcastsi128_si256(n_AR_Q14));
+ SS_sLTP_shp_Q14 = silk_mm256_sub_sat_epi32(SS_LF_AR_Q14, _mm256_broadcastsi128_si256(n_LF_Q14));
+
+ /* END Updating Delayed Decision States */
+
+ *smpl_buf_idx = (*smpl_buf_idx + DECISION_DELAY - 1) % DECISION_DELAY;
+ last_smple_idx = (*smpl_buf_idx + decisionDelay) % DECISION_DELAY;
+ psLastSample = &psDelDec->Samples[last_smple_idx];
+
+ /* Find winner */
+ RDmin_Q10 = silk_mm_mask_hmin_epi32(_mm256_castsi256_si128(SS_RD_Q10), MaskDelDec);
+ Winner_selector = silk_index_to_selector(silk_index_of_first_equal_epi32(RDmin_Q10, _mm256_castsi256_si128(SS_RD_Q10)));
+
+ /* Increase RD values of expired states */
+ Winner_rand_state = _mm_shuffle_epi8(psLastSample->RandState, Winner_selector);
+
+ SS_RD_Q10 = _mm256_blendv_epi8(
+ _mm256_add_epi32(SS_RD_Q10, _mm256_set1_epi32(silk_int32_MAX >> 4)),
+ SS_RD_Q10,
+ _mm256_broadcastsi128_si256(_mm_cmpeq_epi32(psLastSample->RandState, Winner_rand_state)));
+
+ /* find worst in first set */
+ RDmax_Q10 = silk_mm_mask_hmax_epi32(_mm256_extracti128_si256(SS_RD_Q10, 0), MaskDelDec);
+ /* find best in second set */
+ RDmin_Q10 = silk_mm_mask_hmin_epi32(_mm256_extracti128_si256(SS_RD_Q10, 1), MaskDelDec);
+
+ /* Replace a state if best from second set outperforms worst in first set */
+ tmp0 = _mm_cmplt_epi32(RDmin_Q10, RDmax_Q10);
+ if (!_mm_test_all_zeros(tmp0, tmp0))
+ {
+ int t;
+ RDmax_ind = silk_index_of_first_equal_epi32(RDmax_Q10, _mm256_extracti128_si256(SS_RD_Q10, 0));
+ RDmin_ind = silk_index_of_first_equal_epi32(RDmin_Q10, _mm256_extracti128_si256(SS_RD_Q10, 1));
+ tmp1 = _mm_cvtepi8_epi32(_mm_cvtsi32_si128(0xFFU << (unsigned)(RDmax_ind << 3)));
+ tmp0 = _mm_blendv_epi8(
+ _mm_set_epi8(0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0),
+ silk_index_to_selector(RDmin_ind),
+ tmp1);
+ for (t = i; t < MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH; t++)
+ {
+ psDelDec->sLPC_Q14[t] = _mm_shuffle_epi8(psDelDec->sLPC_Q14[t], tmp0);
+ }
+ psDelDec->Seed = _mm_shuffle_epi8(psDelDec->Seed, tmp0);
+ psDelDec->SeedInit = _mm_shuffle_epi8(psDelDec->SeedInit, tmp0);
+ for (t = 0; t < MAX_SHAPE_LPC_ORDER; t++)
+ {
+ psDelDec->sAR2_Q14[t] = _mm_shuffle_epi8(psDelDec->sAR2_Q14[t], tmp0);
+ }
+ for (t = 0; t < DECISION_DELAY; t++)
+ {
+ psDelDec->Samples[t].RandState = _mm_shuffle_epi8(psDelDec->Samples[t].RandState, tmp0);
+ psDelDec->Samples[t].Q_Q10 = _mm_shuffle_epi8(psDelDec->Samples[t].Q_Q10, tmp0);
+ psDelDec->Samples[t].Xq_Q14 = _mm_shuffle_epi8(psDelDec->Samples[t].Xq_Q14, tmp0);
+ psDelDec->Samples[t].Pred_Q15 = _mm_shuffle_epi8(psDelDec->Samples[t].Pred_Q15, tmp0);
+ psDelDec->Samples[t].Shape_Q14 = _mm_shuffle_epi8(psDelDec->Samples[t].Shape_Q14, tmp0);
+ }
+ mask = _mm256_castsi128_si256(_mm_blendv_epi8(_mm_set_epi32(0x3, 0x2, 0x1, 0x0), _mm_set1_epi32(RDmin_ind + 4), tmp1));
+ SS_Q_Q10 = _mm256_permutevar8x32_epi32(SS_Q_Q10, mask);
+ SS_RD_Q10 = _mm256_permutevar8x32_epi32(SS_RD_Q10, mask);
+ SS_xq_Q14 = _mm256_permutevar8x32_epi32(SS_xq_Q14, mask);
+ SS_LF_AR_Q14 = _mm256_permutevar8x32_epi32(SS_LF_AR_Q14, mask);
+ SS_Diff_Q14 = _mm256_permutevar8x32_epi32(SS_Diff_Q14, mask);
+ SS_sLTP_shp_Q14 = _mm256_permutevar8x32_epi32(SS_sLTP_shp_Q14, mask);
+ SS_LPC_exc_Q14 = _mm256_permutevar8x32_epi32(SS_LPC_exc_Q14, mask);
+ }
+
+ /* Write samples from winner to output and long-term filter states */
+ if (subfr > 0 || i >= decisionDelay)
+ {
+ pulses[i - decisionDelay] =
+ (opus_int8)silk_sar_round_32(silk_select_winner(psLastSample->Q_Q10, Winner_selector), 10);
+ xq[i - decisionDelay] =
+ silk_sat16((opus_int32)silk_sar_round_smulww(silk_select_winner(psLastSample->Xq_Q14, Winner_selector), delayedGain_Q10[last_smple_idx], 8));
+ NSQ->sLTP_shp_Q14[NSQ->sLTP_shp_buf_idx - decisionDelay] =
+ silk_select_winner(psLastSample->Shape_Q14, Winner_selector);
+ sLTP_Q15[NSQ->sLTP_buf_idx - decisionDelay] =
+ silk_select_winner(psLastSample->Pred_Q15, Winner_selector);
+ }
+ NSQ->sLTP_shp_buf_idx++;
+ NSQ->sLTP_buf_idx++;
+
+ /* Update states */
+ psSample = &psDelDec->Samples[*smpl_buf_idx];
+ psDelDec->Seed = _mm_add_epi32(psDelDec->Seed, silk_mm_srai_round_epi32(_mm256_castsi256_si128(SS_Q_Q10), 10));
+ psDelDec->LF_AR_Q14 = _mm256_castsi256_si128(SS_LF_AR_Q14);
+ psDelDec->Diff_Q14 = _mm256_castsi256_si128(SS_Diff_Q14);
+ psDelDec->sLPC_Q14[i + NSQ_LPC_BUF_LENGTH] = _mm256_castsi256_si128(SS_xq_Q14);
+ psDelDec->RD_Q10 = _mm256_castsi256_si128(SS_RD_Q10);
+ psSample->Xq_Q14 = _mm256_castsi256_si128(SS_xq_Q14);
+ psSample->Q_Q10 = _mm256_castsi256_si128(SS_Q_Q10);
+ psSample->Pred_Q15 = _mm256_castsi256_si128(SS_LPC_exc_Q14);
+ psSample->Shape_Q14 = _mm256_castsi256_si128(SS_sLTP_shp_Q14);
+ psSample->RandState = psDelDec->Seed;
+ delayedGain_Q10[*smpl_buf_idx] = Gain_Q10;
+ }
+ /* Update LPC states */
+ for (i = 0; i < NSQ_LPC_BUF_LENGTH; i++)
+ {
+ psDelDec->sLPC_Q14[i] = (&psDelDec->sLPC_Q14[length])[i];
+ }
+}
+
+static OPUS_INLINE void silk_nsq_del_dec_scale_states_avx2(
+ const silk_encoder_state *psEncC, /* I Encoder State */
+ silk_nsq_state *NSQ, /* I/O NSQ state */
+ NSQ_del_dec_struct *psDelDec, /* I/O Delayed decision states */
+ const opus_int16 x16[], /* I Input */
+ opus_int32 x_sc_Q10[MAX_SUB_FRAME_LENGTH], /* O Input scaled with 1/Gain in Q10 */
+ const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */
+ opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */
+ opus_int subfr, /* I Subframe number */
+ const opus_int LTP_scale_Q14, /* I LTP state scaling */
+ const opus_int32 Gains_Q16[MAX_NB_SUBFR], /* I */
+ const opus_int pitchL[MAX_NB_SUBFR], /* I Pitch lag */
+ const opus_int signal_type, /* I Signal type */
+ const opus_int decisionDelay /* I Decision delay */
+)
+{
+ int i;
+ opus_int lag;
+ opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26;
+ NSQ_del_dec_sample_struct *psSample;
+
+ lag = pitchL[subfr];
+ inv_gain_Q31 = silk_INVERSE32_varQ(silk_max(Gains_Q16[subfr], 1), 47);
+ silk_assert(inv_gain_Q31 != 0);
+
+ /* Scale input */
+ inv_gain_Q26 = silk_sar_round_32(inv_gain_Q31, 5);
+ for (i = 0; i < psEncC->subfr_length; i+=4)
+ {
+ __m256i x = _mm256_cvtepi16_epi64(_mm_loadu_si64(&x16[i]));
+ x = _mm256_slli_epi64(_mm256_mul_epi32(x, _mm256_set1_epi32(inv_gain_Q26)), 16);
+ _mm_storeu_si128((__m128i_u*)&x_sc_Q10[i], silk_cvtepi64_epi32_high(x));
+ }
+
+ /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */
+ if (NSQ->rewhite_flag)
+ {
+ if (subfr == 0)
+ {
+ /* Do LTP downscaling */
+ inv_gain_Q31 = silk_LSHIFT(silk_SMULWB(inv_gain_Q31, LTP_scale_Q14), 2);
+ }
+ for (i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++)
+ {
+ silk_assert(i < MAX_FRAME_LENGTH);
+ sLTP_Q15[i] = silk_SMULWB(inv_gain_Q31, sLTP[i]);
+ }
+ }
+
+ /* Adjust for changing gain */
+ if (Gains_Q16[subfr] != NSQ->prev_gain_Q16)
+ {
+ gain_adj_Q16 = silk_DIV32_varQ(NSQ->prev_gain_Q16, Gains_Q16[subfr], 16);
+
+ /* Scale long-term shaping state */
+ for (i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx; i+=4)
+ {
+ __m128i_u* p = (__m128i_u*)&NSQ->sLTP_shp_Q14[i];
+ *p = silk_mm_smulww_epi32(*p, gain_adj_Q16);
+ }
+
+ /* Scale long-term prediction state */
+ if (signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0)
+ {
+ for (i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx - decisionDelay; i++)
+ {
+ sLTP_Q15[i] = ((opus_int64)sLTP_Q15[i]) * ((opus_int64)gain_adj_Q16) >> 16;
+ }
+ }
+
+ /* Scale scalar states */
+ psDelDec->LF_AR_Q14 = silk_mm_smulww_epi32(psDelDec->LF_AR_Q14, gain_adj_Q16);
+ psDelDec->Diff_Q14 = silk_mm_smulww_epi32(psDelDec->Diff_Q14, gain_adj_Q16);
+
+ /* Scale short-term prediction and shaping states */
+ for (i = 0; i < NSQ_LPC_BUF_LENGTH; i++)
+ {
+ psDelDec->sLPC_Q14[i] = silk_mm_smulww_epi32(psDelDec->sLPC_Q14[i], gain_adj_Q16);
+ }
+ for (i = 0; i < DECISION_DELAY; i++)
+ {
+ psSample = &psDelDec->Samples[i];
+ psSample->Pred_Q15 = silk_mm_smulww_epi32(psSample->Pred_Q15, gain_adj_Q16);
+ psSample->Shape_Q14 = silk_mm_smulww_epi32(psSample->Shape_Q14, gain_adj_Q16);
+ }
+ for (i = 0; i < MAX_SHAPE_LPC_ORDER; i++)
+ {
+ psDelDec->sAR2_Q14[i] = silk_mm_smulww_epi32(psDelDec->sAR2_Q14[i], gain_adj_Q16);
+ }
+
+ /* Save inverse gain */
+ NSQ->prev_gain_Q16 = Gains_Q16[subfr];
+ }
+}
+
+static OPUS_INLINE void silk_LPC_analysis_filter_avx2(
+ opus_int16 *out, /* O Output signal */
+ const opus_int16 *in, /* I Input signal */
+ const opus_int16 *B, /* I MA prediction coefficients, Q12 [order] */
+ const opus_int32 len, /* I Signal length */
+ const opus_int32 order /* I Filter order */
+)
+{
+ int i;
+ opus_int32 out32_Q12, out32;
+ silk_assert(order == 10 || order == 16);
+
+ for(i = order; i < len; i++ )
+ {
+ const opus_int16 *in_ptr = &in[ i ];
+ /* Allowing wrap around so that two wraps can cancel each other. The rare
+ cases where the result wraps around can only be triggered by invalid streams*/
+
+ __m256i in_v = _mm256_cvtepi16_epi32(_mm_loadu_si128((__m128i_u*)&in_ptr[-8]));
+ __m256i B_v = _mm256_cvtepi16_epi32(_mm_loadu_si128((__m128i_u*)& B[0]));
+ __m256i sum = _mm256_mullo_epi32(in_v, silk_mm256_reverse_epi32(B_v));
+ if (order > 10)
+ {
+ in_v = _mm256_cvtepi16_epi32(_mm_loadu_si128((__m128i_u*)&in_ptr[-16]));
+ B_v = _mm256_cvtepi16_epi32(_mm_loadu_si128((__m128i_u*)&B [8]));
+ B_v = silk_mm256_reverse_epi32(B_v);
+ }
+ else
+ {
+ in_v = _mm256_cvtepi16_epi32(_mm_loadu_si32(&in_ptr[-10]));
+ B_v = _mm256_cvtepi16_epi32(_mm_loadu_si32(&B [8]));
+ B_v = _mm256_shuffle_epi32(B_v, 0x01);
+ }
+ sum = _mm256_add_epi32(sum, _mm256_mullo_epi32(in_v, B_v));
+
+ out32_Q12 = silk_mm256_hsum_epi32(sum);
+
+ /* Subtract prediction */
+ out32_Q12 = silk_SUB32_ovflw( silk_LSHIFT( (opus_int32)*in_ptr, 12 ), out32_Q12 );
+
+ /* Scale to Q0 */
+ out32 = silk_sar_round_32(out32_Q12, 12);
+
+ /* Saturate output */
+ out[ i ] = silk_sat16(out32);
+ }
+
+ /* Set first d output samples to zero */
+ silk_memset( out, 0, order * sizeof( opus_int16 ) );
+}
diff --git a/silk/x86/NSQ_del_dec_sse4_1.c b/silk/x86/NSQ_del_dec_sse4_1.c
index a58a76cd..5937682d 100644
--- a/silk/x86/NSQ_del_dec_sse4_1.c
+++ b/silk/x86/NSQ_del_dec_sse4_1.c
@@ -119,7 +119,7 @@ void silk_NSQ_del_dec_sse4_1(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -428,7 +428,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1(
LTP_pred_Q14 = 2;
{
__m128i tmpa, tmpb, pred_lag_ptr_tmp;
- pred_lag_ptr_tmp = _mm_loadu_si128( (__m128i *)(&pred_lag_ptr[ -3 ] ) );
+ pred_lag_ptr_tmp = _mm_loadu_si128( (__m128i *)(void*)(&pred_lag_ptr[ -3 ] ) );
pred_lag_ptr_tmp = _mm_shuffle_epi32( pred_lag_ptr_tmp, 0x1B );
tmpa = _mm_mul_epi32( pred_lag_ptr_tmp, b_Q12_0123 );
tmpa = _mm_srli_si128( tmpa, 2 );
@@ -483,7 +483,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1(
tmpb = _mm_setzero_si128();
/* step 1 */
- psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -3 ] ) ); /* -3, -2 , -1, 0 */
+ psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -3 ] ) ); /* -3, -2 , -1, 0 */
psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B ); /* 0, -1, -2, -3 */
tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_0123 ); /* 0, -1, -2, -3 * 0123 -> 0*0, 2*-2 */
@@ -497,7 +497,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1(
tmpb = _mm_add_epi32( tmpb, psLPC_Q14_tmp );
/* step 2 */
- psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -7 ] ) );
+ psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -7 ] ) );
psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B );
tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_4567 );
tmpa = _mm_srli_epi64( tmpa, 16 );
@@ -512,7 +512,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1(
if ( opus_likely( predictLPCOrder == 16 ) )
{
/* step 3 */
- psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -11 ] ) );
+ psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -11 ] ) );
psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B );
tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_89AB );
tmpa = _mm_srli_epi64( tmpa, 16 );
@@ -525,7 +525,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1(
tmpb = _mm_add_epi32( tmpb, psLPC_Q14_tmp );
/* step 4 */
- psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -15 ] ) );
+ psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -15 ] ) );
psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B );
tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_CDEF );
tmpa = _mm_srli_epi64( tmpa, 16 );
@@ -561,18 +561,18 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1(
/* Output of lowpass section */
tmp2 = silk_SMLAWB( psDD->Diff_Q14, psDD->sAR2_Q14[ 0 ], warping_Q16 );
/* Output of allpass section */
- tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ 0 ], psDD->sAR2_Q14[ 1 ] - tmp2, warping_Q16 );
+ tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ 0 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ 1 ], tmp2), warping_Q16 );
psDD->sAR2_Q14[ 0 ] = tmp2;
n_AR_Q14 = silk_RSHIFT( shapingLPCOrder, 1 );
n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ 0 ] );
/* Loop over allpass sections */
for( j = 2; j < shapingLPCOrder; j += 2 ) {
/* Output of allpass section */
- tmp2 = silk_SMLAWB( psDD->sAR2_Q14[ j - 1 ], psDD->sAR2_Q14[ j + 0 ] - tmp1, warping_Q16 );
+ tmp2 = silk_SMLAWB( psDD->sAR2_Q14[ j - 1 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ j + 0 ], tmp1), warping_Q16 );
psDD->sAR2_Q14[ j - 1 ] = tmp1;
n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp1, AR_shp_Q13[ j - 1 ] );
/* Output of allpass section */
- tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ j + 0 ], psDD->sAR2_Q14[ j + 1 ] - tmp2, warping_Q16 );
+ tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ j + 0 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ j + 1 ], tmp2), warping_Q16 );
psDD->sAR2_Q14[ j + 0 ] = tmp2;
n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ j ] );
}
@@ -590,7 +590,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1(
/* Input minus prediction plus noise feedback */
/* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */
tmp1 = silk_ADD_SAT32( n_AR_Q14, n_LF_Q14 ); /* Q14 */
- tmp2 = silk_ADD32( n_LTP_Q14, LPC_pred_Q14 ); /* Q13 */
+ tmp2 = silk_ADD32_ovflw( n_LTP_Q14, LPC_pred_Q14 ); /* Q13 */
tmp1 = silk_SUB_SAT32( tmp2, tmp1 ); /* Q13 */
tmp1 = silk_RSHIFT_ROUND( tmp1, 4 ); /* Q10 */
@@ -667,11 +667,11 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1(
/* Add predictions */
LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 );
- xq_Q14 = silk_ADD32( LPC_exc_Q14, LPC_pred_Q14 );
+ xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, LPC_pred_Q14 );
/* Update states */
- psSS[ 0 ].Diff_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_Q10[ i ], 4 );
- sLF_AR_shp_Q14 = silk_SUB32( psSS[ 0 ].Diff_Q14, n_AR_Q14 );
+ psSS[ 0 ].Diff_Q14 = silk_SUB32_ovflw( xq_Q14, silk_LSHIFT32( x_Q10[ i ], 4 ) );
+ sLF_AR_shp_Q14 = silk_SUB32_ovflw( psSS[ 0 ].Diff_Q14, n_AR_Q14 );
psSS[ 0 ].sLTP_shp_Q14 = silk_SUB_SAT32( sLF_AR_shp_Q14, n_LF_Q14 );
psSS[ 0 ].LF_AR_Q14 = sLF_AR_shp_Q14;
psSS[ 0 ].LPC_exc_Q14 = LPC_exc_Q14;
@@ -687,11 +687,11 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1(
/* Add predictions */
LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 );
- xq_Q14 = silk_ADD32( LPC_exc_Q14, LPC_pred_Q14 );
+ xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, LPC_pred_Q14 );
/* Update states */
- psSS[ 1 ].Diff_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_Q10[ i ], 4 );
- sLF_AR_shp_Q14 = silk_SUB32( psSS[ 1 ].Diff_Q14, n_AR_Q14 );
+ psSS[ 1 ].Diff_Q14 = silk_SUB32_ovflw( xq_Q14, silk_LSHIFT32( x_Q10[ i ], 4 ) );
+ sLF_AR_shp_Q14 = silk_SUB32_ovflw( psSS[ 1 ].Diff_Q14, n_AR_Q14 );
psSS[ 1 ].sLTP_shp_Q14 = silk_SUB_SAT32( sLF_AR_shp_Q14, n_LF_Q14 );
psSS[ 1 ].LF_AR_Q14 = sLF_AR_shp_Q14;
psSS[ 1 ].LPC_exc_Q14 = LPC_exc_Q14;
@@ -830,7 +830,7 @@ static OPUS_INLINE void silk_nsq_del_dec_scale_states_sse4_1(
xmm_x16_x2x0 = _mm_blend_epi16( xmm_x16_x2x0, xmm_x16_x3x1, 0xCC );
- _mm_storeu_si128( (__m128i *)(&(x_sc_Q10[ i ] ) ), xmm_x16_x2x0 );
+ _mm_storeu_si128( (__m128i *)(void*)(&(x_sc_Q10[ i ] ) ), xmm_x16_x2x0 );
}
for( ; i < psEncC->subfr_length; i++ ) {
@@ -862,7 +862,7 @@ static OPUS_INLINE void silk_nsq_del_dec_scale_states_sse4_1(
for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx - 3; i += 4 )
{
- xmm_sLTP_shp_Q14_x2x0 = _mm_loadu_si128( (__m128i *)(&(NSQ->sLTP_shp_Q14[ i ] ) ) );
+ xmm_sLTP_shp_Q14_x2x0 = _mm_loadu_si128( (__m128i *)(void*)(&(NSQ->sLTP_shp_Q14[ i ] ) ) );
/* equal shift right 4 bytes*/
xmm_sLTP_shp_Q14_x3x1 = _mm_shuffle_epi32( xmm_sLTP_shp_Q14_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) );
@@ -874,7 +874,7 @@ static OPUS_INLINE void silk_nsq_del_dec_scale_states_sse4_1(
xmm_sLTP_shp_Q14_x2x0 = _mm_blend_epi16( xmm_sLTP_shp_Q14_x2x0, xmm_sLTP_shp_Q14_x3x1, 0xCC );
- _mm_storeu_si128( (__m128i *)(&(NSQ->sLTP_shp_Q14[ i ] ) ), xmm_sLTP_shp_Q14_x2x0 );
+ _mm_storeu_si128( (__m128i *)(void*)(&(NSQ->sLTP_shp_Q14[ i ] ) ), xmm_sLTP_shp_Q14_x2x0 );
}
for( ; i < NSQ->sLTP_shp_buf_idx; i++ ) {
diff --git a/silk/x86/NSQ_sse4_1.c b/silk/x86/NSQ_sse4_1.c
index d5ae1d3b..3c9aca7b 100644
--- a/silk/x86/NSQ_sse4_1.c
+++ b/silk/x86/NSQ_sse4_1.c
@@ -77,7 +77,7 @@ void silk_NSQ_sse4_1(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -338,21 +338,21 @@ static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1(
xmm_one = _mm_set_epi8( 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14 );
/* load a_Q12[0] - a_Q12[7] */
- a_Q12_01234567 = _mm_loadu_si128( (__m128i *)(&a_Q12[ 0 ] ) );
+ a_Q12_01234567 = _mm_loadu_si128( (__m128i *)(void*)(&a_Q12[ 0 ] ) );
/* load a_Q12[ 8 ] - a_Q12[ 15 ] */
- a_Q12_89ABCDEF = _mm_loadu_si128( (__m128i *)(&a_Q12[ 8 ] ) );
+ a_Q12_89ABCDEF = _mm_loadu_si128( (__m128i *)(void*)(&a_Q12[ 8 ] ) );
a_Q12_01234567 = _mm_shuffle_epi8( a_Q12_01234567, xmm_one );
a_Q12_89ABCDEF = _mm_shuffle_epi8( a_Q12_89ABCDEF, xmm_one );
/* load AR_shp_Q13 */
- AR_shp_Q13_76543210 = _mm_loadu_si128( (__m128i *)(&AR_shp_Q13[0] ) );
+ AR_shp_Q13_76543210 = _mm_loadu_si128( (__m128i *)(void*)(&AR_shp_Q13[0] ) );
/* load psLPC_Q14 */
xmm_one = _mm_set_epi8(15, 14, 11, 10, 7, 6, 3, 2, 13, 12, 9, 8, 5, 4, 1, 0 );
- xmm_tempa = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[-16]) );
- xmm_tempb = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[-12]) );
+ xmm_tempa = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[-16]) );
+ xmm_tempb = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[-12]) );
xmm_tempa = _mm_shuffle_epi8( xmm_tempa, xmm_one );
xmm_tempb = _mm_shuffle_epi8( xmm_tempb, xmm_one );
@@ -360,8 +360,8 @@ static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1(
psLPC_Q14_hi_89ABCDEF = _mm_unpackhi_epi64( xmm_tempa, xmm_tempb );
psLPC_Q14_lo_89ABCDEF = _mm_unpacklo_epi64( xmm_tempa, xmm_tempb );
- xmm_tempa = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -8 ]) );
- xmm_tempb = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -4 ]) );
+ xmm_tempa = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -8 ]) );
+ xmm_tempb = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -4 ]) );
xmm_tempa = _mm_shuffle_epi8( xmm_tempa, xmm_one );
xmm_tempb = _mm_shuffle_epi8( xmm_tempb, xmm_one );
@@ -370,8 +370,8 @@ static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1(
psLPC_Q14_lo_01234567 = _mm_unpacklo_epi64( xmm_tempa, xmm_tempb );
/* load sAR2_Q14 */
- xmm_tempa = _mm_loadu_si128( (__m128i *)(&(NSQ->sAR2_Q14[ 0 ]) ) );
- xmm_tempb = _mm_loadu_si128( (__m128i *)(&(NSQ->sAR2_Q14[ 4 ]) ) );
+ xmm_tempa = _mm_loadu_si128( (__m128i *)(void*)(&(NSQ->sAR2_Q14[ 0 ]) ) );
+ xmm_tempb = _mm_loadu_si128( (__m128i *)(void*)(&(NSQ->sAR2_Q14[ 4 ]) ) );
xmm_tempa = _mm_shuffle_epi8( xmm_tempa, xmm_one );
xmm_tempb = _mm_shuffle_epi8( xmm_tempb, xmm_one );
@@ -443,7 +443,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1(
b_Q14_0123 = _mm_shuffle_epi32( b_Q14_3210, 0x1B );
/* loaded: [0] [-1] [-2] [-3] */
- pred_lag_ptr_0123 = _mm_loadu_si128( (__m128i *)(&pred_lag_ptr[ -3 ] ) );
+ pred_lag_ptr_0123 = _mm_loadu_si128( (__m128i *)(void*)(&pred_lag_ptr[ -3 ] ) );
/* shuffle to [-3] [-2] [-1] [0] and to new xmm */
xmm_tempa = _mm_shuffle_epi32( pred_lag_ptr_0123, 0x1B );
/*64-bit multiply, a[2] * b[-2], a[0] * b[0] */
@@ -595,8 +595,8 @@ static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1(
/* write back sAR2_Q14 */
xmm_tempa = _mm_unpackhi_epi16( sAR2_Q14_lo_76543210, sAR2_Q14_hi_76543210 );
xmm_tempb = _mm_unpacklo_epi16( sAR2_Q14_lo_76543210, sAR2_Q14_hi_76543210 );
- _mm_storeu_si128( (__m128i *)(&NSQ->sAR2_Q14[ 4 ]), xmm_tempa );
- _mm_storeu_si128( (__m128i *)(&NSQ->sAR2_Q14[ 0 ]), xmm_tempb );
+ _mm_storeu_si128( (__m128i *)(void*)(&NSQ->sAR2_Q14[ 4 ]), xmm_tempa );
+ _mm_storeu_si128( (__m128i *)(void*)(&NSQ->sAR2_Q14[ 0 ]), xmm_tempb );
/* xq[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( psLPC_Q14[ i ], Gain_Q10 ), 8 ) ); */
{
@@ -612,8 +612,8 @@ static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1(
/* process xq */
for (i = 0; i < length - 7; i += 8)
{
- xmm_xq_Q14_3210 = _mm_loadu_si128( (__m128i *)(&(psLPC_Q14[ i + 0 ] ) ) );
- xmm_xq_Q14_7654 = _mm_loadu_si128( (__m128i *)(&(psLPC_Q14[ i + 4 ] ) ) );
+ xmm_xq_Q14_3210 = _mm_loadu_si128( (__m128i *)(void*)(&(psLPC_Q14[ i + 0 ] ) ) );
+ xmm_xq_Q14_7654 = _mm_loadu_si128( (__m128i *)(void*)(&(psLPC_Q14[ i + 4 ] ) ) );
/* equal shift right 4 bytes*/
xmm_xq_Q14_x3x1 = _mm_shuffle_epi32( xmm_xq_Q14_3210, _MM_SHUFFLE( 0, 3, 2, 1 ) );
@@ -644,7 +644,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1(
xmm_xq_Q14_3210 = _mm_packs_epi32( xmm_xq_Q14_3210, xmm_xq_Q14_7654 );
/* save to xq */
- _mm_storeu_si128( (__m128i *)(&xq[ i ] ), xmm_xq_Q14_3210 );
+ _mm_storeu_si128( (__m128i *)(void*)(&xq[ i ] ), xmm_xq_Q14_3210 );
}
}
for ( ; i < length; i++)
@@ -698,7 +698,7 @@ static OPUS_INLINE void silk_nsq_scale_states_sse4_1(
xmm_x16_x2x0 = _mm_blend_epi16( xmm_x16_x2x0, xmm_x16_x3x1, 0xCC );
- _mm_storeu_si128( (__m128i *)(&(x_sc_Q10[ i ] ) ), xmm_x16_x2x0 );
+ _mm_storeu_si128( (__m128i *)(void*)(&(x_sc_Q10[ i ] ) ), xmm_x16_x2x0 );
}
for( ; i < psEncC->subfr_length; i++ ) {
@@ -729,7 +729,7 @@ static OPUS_INLINE void silk_nsq_scale_states_sse4_1(
for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx - 3; i += 4 )
{
- xmm_sLTP_shp_Q14_x2x0 = _mm_loadu_si128( (__m128i *)(&(NSQ->sLTP_shp_Q14[ i ] ) ) );
+ xmm_sLTP_shp_Q14_x2x0 = _mm_loadu_si128( (__m128i *)(void*)(&(NSQ->sLTP_shp_Q14[ i ] ) ) );
/* equal shift right 4 bytes*/
xmm_sLTP_shp_Q14_x3x1 = _mm_shuffle_epi32( xmm_sLTP_shp_Q14_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) );
@@ -741,7 +741,7 @@ static OPUS_INLINE void silk_nsq_scale_states_sse4_1(
xmm_sLTP_shp_Q14_x2x0 = _mm_blend_epi16( xmm_sLTP_shp_Q14_x2x0, xmm_sLTP_shp_Q14_x3x1, 0xCC );
- _mm_storeu_si128( (__m128i *)(&(NSQ->sLTP_shp_Q14[ i ] ) ), xmm_sLTP_shp_Q14_x2x0 );
+ _mm_storeu_si128( (__m128i *)(void*)(&(NSQ->sLTP_shp_Q14[ i ] ) ), xmm_sLTP_shp_Q14_x2x0 );
}
for( ; i < NSQ->sLTP_shp_buf_idx; i++ ) {
diff --git a/silk/x86/VAD_sse4_1.c b/silk/x86/VAD_sse4_1.c
index e7eaf971..9e06bc79 100644
--- a/silk/x86/VAD_sse4_1.c
+++ b/silk/x86/VAD_sse4_1.c
@@ -144,7 +144,7 @@ opus_int silk_VAD_GetSA_Q8_sse4_1( /* O Return value, 0 if s
for( i = 0; i < dec_subframe_length - 7; i += 8 )
{
- xmm_X = _mm_loadu_si128( (__m128i *)&(X[ X_offset[ b ] + i + dec_subframe_offset ] ) );
+ xmm_X = _mm_loadu_si128( (__m128i *)(void*)&(X[ X_offset[ b ] + i + dec_subframe_offset ] ) );
xmm_X = _mm_srai_epi16( xmm_X, 3 );
xmm_X = _mm_madd_epi16( xmm_X, xmm_X );
xmm_acc = _mm_add_epi32( xmm_acc, xmm_X );
diff --git a/silk/x86/VQ_WMat_EC_sse4_1.c b/silk/x86/VQ_WMat_EC_sse4_1.c
index 2c7d18d0..df4626b6 100644
--- a/silk/x86/VQ_WMat_EC_sse4_1.c
+++ b/silk/x86/VQ_WMat_EC_sse4_1.c
@@ -65,7 +65,7 @@ void silk_VQ_WMat_EC_sse4_1(
neg_xX_Q24[ 3 ] = -silk_LSHIFT32( xX_Q17[ 3 ], 7 );
neg_xX_Q24[ 4 ] = -silk_LSHIFT32( xX_Q17[ 4 ], 7 );
- v_XX_31_Q17 = _mm_loadu_si128( (__m128i *)(&XX_Q17[ 1 ] ) );
+ v_XX_31_Q17 = _mm_loadu_si128( (__m128i *)(void*)(&XX_Q17[ 1 ] ) );
v_XX_42_Q17 = _mm_shuffle_epi32( v_XX_31_Q17, _MM_SHUFFLE( 0, 3, 2, 1 ) );
/* Loop over codebook */
diff --git a/silk/x86/main_sse.h b/silk/x86/main_sse.h
index a01d7f6c..b254d53e 100644
--- a/silk/x86/main_sse.h
+++ b/silk/x86/main_sse.h
@@ -88,7 +88,7 @@ void silk_NSQ_sse4_1(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -116,7 +116,7 @@ extern void (*const SILK_NSQ_IMPL[OPUS_ARCHMASK + 1])(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -142,7 +142,7 @@ void silk_NSQ_del_dec_sse4_1(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -154,7 +154,33 @@ void silk_NSQ_del_dec_sse4_1(
const opus_int LTP_scale_Q14 /* I LTP state scaling */
);
-# if defined OPUS_X86_PRESUME_SSE4_1
+void silk_NSQ_del_dec_avx2(
+ const silk_encoder_state *psEncC, /* I Encoder State */
+ silk_nsq_state *NSQ, /* I/O NSQ state */
+ SideInfoIndices *psIndices, /* I/O Quantization Indices */
+ const opus_int16 x16[], /* I Input */
+ opus_int8 pulses[], /* O Quantized pulse signal */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
+ const opus_int16 LTPCoef_Q14[LTP_ORDER * MAX_NB_SUBFR], /* I Long term prediction coefs */
+ const opus_int16 AR_Q13[MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER], /* I Noise shaping coefs */
+ const opus_int HarmShapeGain_Q14[MAX_NB_SUBFR], /* I Long term shaping coefs */
+ const opus_int Tilt_Q14[MAX_NB_SUBFR], /* I Spectral tilt */
+ const opus_int32 LF_shp_Q14[MAX_NB_SUBFR], /* I Low frequency shaping coefs */
+ const opus_int32 Gains_Q16[MAX_NB_SUBFR], /* I Quantization step sizes */
+ const opus_int32 pitchL[MAX_NB_SUBFR], /* I Pitch lags */
+ const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */
+ const opus_int LTP_scale_Q14 /* I LTP state scaling */
+);
+
+# if defined (OPUS_X86_PRESUME_AVX2)
+
+# define OVERRIDE_silk_NSQ_del_dec
+# define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \
+ HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \
+ ((void)(arch),silk_NSQ_del_dec_avx2(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \
+ HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14))
+
+# elif defined (OPUS_X86_PRESUME_SSE4_1) && !defined(OPUS_X86_MAY_HAVE_AVX2)
# define OVERRIDE_silk_NSQ_del_dec
# define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \
@@ -170,7 +196,7 @@ extern void (*const SILK_NSQ_DEL_DEC_IMPL[OPUS_ARCHMASK + 1])(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -243,5 +269,31 @@ extern opus_int (*const SILK_VAD_GETSA_Q8_IMPL[OPUS_ARCHMASK + 1])(
# endif
+#ifndef FIXED_POINT
+double silk_inner_product_FLP_avx2(
+ const silk_float *data1,
+ const silk_float *data2,
+ opus_int dataSize
+);
+
+#if defined (OPUS_X86_PRESUME_AVX2)
+
+#define OVERRIDE_inner_product_FLP
+#define silk_inner_product_FLP(data1, data2, dataSize, arch) ((void)arch,silk_inner_product_FLP_avx2(data1, data2, dataSize))
+
+#elif defined(OPUS_HAVE_RTCD) && defined(OPUS_X86_MAY_HAVE_AVX2)
+
+#define OVERRIDE_inner_product_FLP
+extern double (*const SILK_INNER_PRODUCT_FLP_IMPL[OPUS_ARCHMASK + 1])(
+ const silk_float *data1,
+ const silk_float *data2,
+ opus_int dataSize
+);
+
+#define silk_inner_product_FLP(data1, data2, dataSize, arch) ((void)arch,(*SILK_INNER_PRODUCT_FLP_IMPL[(arch) & OPUS_ARCHMASK])(data1, data2, dataSize))
+
+#endif
+#endif
+
# endif
#endif
diff --git a/silk/x86/x86_silk_map.c b/silk/x86/x86_silk_map.c
index 70f60078..39ad7527 100644
--- a/silk/x86/x86_silk_map.c
+++ b/silk/x86/x86_silk_map.c
@@ -32,10 +32,13 @@
#include "celt/x86/x86cpu.h"
#include "structs.h"
#include "SigProc_FIX.h"
+#ifndef FIXED_POINT
+#include "SigProc_FLP.h"
+#endif
#include "pitch.h"
#include "main.h"
-#if defined(OPUS_HAVE_RTCD) && !defined(OPUS_X86_PRESUME_SSE4_1)
+#if defined(OPUS_HAVE_RTCD) && !defined(OPUS_X86_PRESUME_AVX2)
#if defined(FIXED_POINT)
@@ -72,7 +75,7 @@ void (*const SILK_NSQ_IMPL[ OPUS_ARCHMASK + 1 ] )(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -117,7 +120,7 @@ void (*const SILK_NSQ_DEL_DEC_IMPL[ OPUS_ARCHMASK + 1 ] )(
SideInfoIndices *psIndices, /* I/O Quantization Indices */
const opus_int16 x16[], /* I Input */
opus_int8 pulses[], /* O Quantized pulse signal */
- const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */
+ const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */
const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */
const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */
const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */
@@ -132,7 +135,7 @@ void (*const SILK_NSQ_DEL_DEC_IMPL[ OPUS_ARCHMASK + 1 ] )(
silk_NSQ_del_dec_c,
silk_NSQ_del_dec_c,
MAY_HAVE_SSE4_1( silk_NSQ_del_dec ), /* sse4.1 */
- MAY_HAVE_SSE4_1( silk_NSQ_del_dec ) /* avx */
+ MAY_HAVE_AVX2( silk_NSQ_del_dec ) /* avx */
};
#if defined(FIXED_POINT)
@@ -156,4 +159,21 @@ void (*const SILK_BURG_MODIFIED_IMPL[ OPUS_ARCHMASK + 1 ] )(
};
#endif
+
+#ifndef FIXED_POINT
+
+double (*const SILK_INNER_PRODUCT_FLP_IMPL[ OPUS_ARCHMASK + 1 ] )(
+ const silk_float *data1,
+ const silk_float *data2,
+ opus_int dataSize
+) = {
+ silk_inner_product_FLP_c, /* non-sse */
+ silk_inner_product_FLP_c,
+ silk_inner_product_FLP_c,
+ silk_inner_product_FLP_c, /* sse4.1 */
+ MAY_HAVE_AVX2( silk_inner_product_FLP ) /* avx */
+};
+
+#endif
+
#endif
diff --git a/silk_sources.mk b/silk_sources.mk
index 3df24816..3780b164 100644
--- a/silk_sources.mk
+++ b/silk_sources.mk
@@ -86,6 +86,9 @@ silk/x86/NSQ_del_dec_sse4_1.c \
silk/x86/VAD_sse4_1.c \
silk/x86/VQ_WMat_EC_sse4_1.c
+SILK_SOURCES_AVX2 = \
+silk/x86/NSQ_del_dec_avx2.c
+
SILK_SOURCES_ARM_RTCD = \
silk/arm/arm_silk_map.c
@@ -156,3 +159,6 @@ silk/float/scale_copy_vector_FLP.c \
silk/float/scale_vector_FLP.c \
silk/float/schur_FLP.c \
silk/float/sort_FLP.c
+
+SILK_SOURCES_FLOAT_AVX2 = \
+silk/float/x86/inner_product_FLP_avx2.c \ No newline at end of file
diff --git a/src/analysis.c b/src/analysis.c
index 8b4f22d0..b6f13b09 100644
--- a/src/analysis.c
+++ b/src/analysis.c
@@ -932,9 +932,9 @@ static void tonality_analysis(TonalityAnalysisState *tonal, const CELTMode *celt
features[23] = info->tonality_slope + 0.069216f;
features[24] = tonal->lowECount - 0.067930f;
- compute_dense(&layer0, layer_out, features);
- compute_gru(&layer1, tonal->rnn_state, layer_out);
- compute_dense(&layer2, frame_probs, tonal->rnn_state);
+ analysis_compute_dense(&layer0, layer_out, features);
+ analysis_compute_gru(&layer1, tonal->rnn_state, layer_out);
+ analysis_compute_dense(&layer2, frame_probs, tonal->rnn_state);
/* Probability of speech or music vs noise */
info->activity_probability = frame_probs[1];
diff --git a/src/extensions.c b/src/extensions.c
new file mode 100644
index 00000000..bb6c0b02
--- /dev/null
+++ b/src/extensions.c
@@ -0,0 +1,315 @@
+/* Copyright (c) 2022 Amazon */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#include "opus_types.h"
+#include "opus_defines.h"
+#include "arch.h"
+#include "os_support.h"
+#include "opus_private.h"
+
+
+/* Given an extension payload, advance data to the next extension and return the
+ length of the remaining extensions. */
+opus_int32 skip_extension(const unsigned char **data, opus_int32 len, opus_int32 *header_size)
+{
+ int id, L;
+ if (len==0)
+ return 0;
+ id = **data>>1;
+ L = **data&1;
+ if (id == 0 && L == 1)
+ {
+ *header_size = 1;
+ if (len < 1)
+ return -1;
+ (*data)++;
+ len--;
+ return len;
+ } else if (id > 0 && id < 32)
+ {
+ if (len < 1+L)
+ return -1;
+ *data += 1+L;
+ len -= 1+L;
+ *header_size = 1;
+ return len;
+ } else {
+ if (L==0)
+ {
+ *data += len;
+ *header_size = 1;
+ return 0;
+ } else {
+ opus_int32 bytes=0;
+ *header_size = 1;
+ do {
+ (*data)++;
+ len--;
+ if (len == 0)
+ return -1;
+ bytes += **data;
+ (*header_size)++;
+ } while (**data == 255);
+ (*data)++;
+ len--;
+ if (bytes <= len)
+ {
+ len -= bytes;
+ *data += bytes;
+ } else {
+ return -1;
+ }
+ return len;
+ }
+ }
+}
+
+/* Count the number of extensions, excluding real padding and separators. */
+opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len)
+{
+ opus_int32 curr_len;
+ opus_int32 count=0;
+ const unsigned char *curr_data = data;
+
+ celt_assert(len >= 0);
+ celt_assert(data != NULL || len == 0);
+
+ curr_len = len;
+ while (curr_len > 0)
+ {
+ int id;
+ opus_int32 header_size;
+ id = *curr_data>>1;
+ curr_len = skip_extension(&curr_data, curr_len, &header_size);
+ if (curr_len < 0)
+ return OPUS_INVALID_PACKET;
+ if (id > 1)
+ count++;
+ }
+ return count;
+}
+
+/* Extract extensions from Opus padding (excluding real padding and separators) */
+opus_int32 opus_packet_extensions_parse(const unsigned char *data, opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions)
+{
+ const unsigned char *curr_data;
+ opus_int32 curr_len;
+ int curr_frame=0;
+ opus_int32 count=0;
+
+ celt_assert(len >= 0);
+ celt_assert(data != NULL || len == 0);
+ celt_assert(nb_extensions != NULL);
+ celt_assert(extensions != NULL || *nb_extensions == 0);
+
+ curr_data = data;
+ curr_len = len;
+ while (curr_len > 0)
+ {
+ int id;
+ opus_int32 header_size;
+ opus_extension_data curr_ext;
+ id = *curr_data>>1;
+ if (id > 1)
+ {
+ curr_ext.id = id;
+ curr_ext.frame = curr_frame;
+ curr_ext.data = curr_data;
+ } else if (id == 1)
+ {
+ int L = *curr_data&1;
+ if (L==0)
+ curr_frame++;
+ else {
+ if (curr_len >= 2)
+ curr_frame += curr_data[1];
+ /* Else we're at the end and it doesn't matter. */
+ }
+ if (curr_frame >= 48)
+ {
+ *nb_extensions = count;
+ return OPUS_INVALID_PACKET;
+ }
+ }
+ curr_len = skip_extension(&curr_data, curr_len, &header_size);
+ /* printf("curr_len = %d, header_size = %d\n", curr_len, header_size); */
+ if (curr_len < 0)
+ {
+ *nb_extensions = count;
+ return OPUS_INVALID_PACKET;
+ }
+ celt_assert(curr_data - data == len - curr_len);
+ if (id > 1)
+ {
+ if (count == *nb_extensions)
+ {
+ return OPUS_BUFFER_TOO_SMALL;
+ }
+ curr_ext.len = curr_data - curr_ext.data - header_size;
+ curr_ext.data += header_size;
+ extensions[count++] = curr_ext;
+ }
+ }
+ celt_assert(curr_len == 0);
+ *nb_extensions = count;
+ return OPUS_OK;
+}
+
+opus_int32 opus_packet_extensions_generate(unsigned char *data, opus_int32 len, const opus_extension_data *extensions, opus_int32 nb_extensions, int pad)
+{
+ int max_frame=0;
+ opus_int32 i;
+ int frame;
+ int curr_frame = 0;
+ opus_int32 pos = 0;
+ opus_int32 written = 0;
+
+ celt_assert(len >= 0);
+
+ for (i=0;i<nb_extensions;i++)
+ {
+ max_frame = IMAX(max_frame, extensions[i].frame);
+ if (extensions[i].id < 2 || extensions[i].id > 127)
+ return OPUS_BAD_ARG;
+ }
+ if (max_frame >= 48) return OPUS_BAD_ARG;
+ for (frame=0;frame<=max_frame;frame++)
+ {
+ for (i=0;i<nb_extensions;i++)
+ {
+ if (extensions[i].frame == frame)
+ {
+ /* Insert separator when needed. */
+ if (frame != curr_frame) {
+ int diff = frame - curr_frame;
+ if (len-pos < 2)
+ return OPUS_BUFFER_TOO_SMALL;
+ if (diff == 1) {
+ if (data) data[pos] = 0x02;
+ pos++;
+ } else {
+ if (data) data[pos] = 0x03;
+ pos++;
+ if (data) data[pos] = diff;
+ pos++;
+ }
+ curr_frame = frame;
+ }
+ if (extensions[i].id < 32)
+ {
+ if (extensions[i].len < 0 || extensions[i].len > 1)
+ return OPUS_BAD_ARG;
+ if (len-pos < extensions[i].len+1)
+ return OPUS_BUFFER_TOO_SMALL;
+ if (data) data[pos] = (extensions[i].id<<1) + extensions[i].len;
+ pos++;
+ if (extensions[i].len > 0) {
+ if (data) data[pos] = extensions[i].data[0];
+ pos++;
+ }
+ } else {
+ int last;
+ opus_int32 length_bytes;
+ if (extensions[i].len < 0)
+ return OPUS_BAD_ARG;
+ last = (written == nb_extensions - 1);
+ length_bytes = 1 + extensions[i].len/255;
+ if (last)
+ length_bytes = 0;
+ if (len-pos < 1 + length_bytes + extensions[i].len)
+ return OPUS_BUFFER_TOO_SMALL;
+ if (data) data[pos] = (extensions[i].id<<1) + !last;
+ pos++;
+ if (!last)
+ {
+ opus_int32 j;
+ for (j=0;j<extensions[i].len/255;j++) {
+ if (data) data[pos] = 255;
+ pos++;
+ }
+ if (data) data[pos] = extensions[i].len % 255;
+ pos++;
+ }
+ if (data) OPUS_COPY(&data[pos], extensions[i].data, extensions[i].len);
+ pos += extensions[i].len;
+ }
+ written++;
+ }
+ }
+ }
+ /* If we need to pad, just prepend 0x01 bytes. Even better would be to fill the
+ end with zeros, but that requires checking that turning the last extesion into
+ an L=1 case still fits. */
+ if (pad && pos < len)
+ {
+ opus_int32 padding = len - pos;
+ if (data) {
+ OPUS_MOVE(data+padding, data, pos);
+ for (i=0;i<padding;i++)
+ data[i] = 0x01;
+ }
+ pos += padding;
+ }
+ return pos;
+}
+
+#if 0
+#include <stdio.h>
+int main()
+{
+ opus_extension_data ext[] = {{2, 0, (const unsigned char *)"a", 1},
+ {32, 10, (const unsigned char *)"DRED", 4},
+ {33, 1, (const unsigned char *)"NOT DRED", 8},
+ {3, 4, (const unsigned char *)NULL, 0}
+ };
+ opus_extension_data ext2[10];
+ int i, len;
+ int nb_ext = 10;
+ unsigned char packet[10000];
+ len = opus_packet_extensions_generate(packet, 32, ext, 4, 1);
+ for (i=0;i<len;i++)
+ {
+ printf("%#04x ", packet[i]);
+ if (i%16 == 15)
+ printf("\n");
+ }
+ printf("\n");
+ printf("count = %d\n", opus_packet_extensions_count(packet, len));
+ opus_packet_extensions_parse(packet, len, ext2, &nb_ext);
+ for (i=0;i<nb_ext;i++)
+ {
+ int j;
+ printf("%d %d {", ext2[i].id, ext2[i].frame);
+ for (j=0;j<ext2[i].len;j++) printf("%#04x ", ext2[i].data[j]);
+ printf("} %d\n", ext2[i].len);
+ }
+}
+#endif
diff --git a/src/mapping_matrix.c b/src/mapping_matrix.c
index 31298af0..3f78ab59 100644
--- a/src/mapping_matrix.c
+++ b/src/mapping_matrix.c
@@ -302,6 +302,287 @@ const opus_int16 mapping_matrix_toa_mixing_data[324] = {
0, 0, 0, 32767
};
+const MappingMatrix mapping_matrix_fourthoa_mixing = { 27, 27, 0 };
+const opus_int16 mapping_matrix_fourthoa_mixing_data[729] = {
+ 9243, 0, 16010, 0, 0, 0, 20669, 0,
+ 0, 0, 0, 0, 24456, 0, 0, 0,
+ 0, 0, 0, 0, 27731, 0, 0, 0,
+ 0, 0, 0, 9243, 0, 10884, 11741, 0,
+ 0, 3995, 17849, 9626, 0, 0, 0, -5727,
+ 14399, 17315, 7625, 0, 0, 0, 0, -11747,
+ 2574, 18637, 15552, 5930, 0, 0, 9243, -14302,
+ -2682, -6677, 13337, 5357, -9464, 2501, -11170, 4770,
+ -5911, 11501, 5858, 5369, 4951, 17901, -19071, -2397,
+ -9281, -9198, 7576, -4294, 7773, -8997, -3399, 0,
+ 0, 9243, 9940, 11991, -3705, -5144, 16647, 7057,
+ -6206, -5941, -2698, -10194, 16781, -1788, -6256, -11772,
+ 4935, 3912, -6062, -13039, 9446, -9758, -3521, -15058,
+ 11089, 565, 0, 0, 9243, -15376, 3720, 2461,
+ -5285, -7989, -8660, 1278, -16087, 15811, -3249, 10500,
+ -7757, -1680, -9890, -8153, 10884, 11022, 2847, 12828,
+ 5137, -2053, 8666, -5684, 14776, 0, 0, 9243,
+ -10577, 10304, -6186, 9139, -15222, 2507, -8902, -5140,
+ -145, 15562, -10596, -7311, -6197, -8753, 8667, -6014,
+ -281, 15033, 938, -11859, 548, -8456, 16735, -3654,
+ 0, 0, 9243, 8974, 4839, -12343, -15472, 6066,
+ -7501, -8343, 5015, 15920, -12374, -4559, -9400, 6271,
+ 4011, 5191, -9932, 14438, 4828, -8768, 1909, 12059,
+ -1565, 4707, -13711, 0, 0, 9243, 15799, 2085,
+ -1534, -3386, 4602, -9808, -447, -17267, -18054, -1167,
+ -13525, -4644, 1313, -5951, 5397, 7485, -7056, 2584,
+ -8120, 8669, 788, 13177, 2109, 18349, 0, 0,
+ 9243, 12371, -10036, 1597, 2760, -17341, 1848, -2239,
+ -10509, -8474, -4577, 11164, 7935, 1441, 17430, -3436,
+ -3713, 15936, 4184, 2647, -11730, 341, -15934, 6462,
+ 6581, 0, 0, 9243, -8963, 2184, 13084, -16381,
+ -2734, -9757, 3991, 6345, -18297, -5912, 7604, -4849,
+ -11100, 2290, -4304, -13305, -7488, 12338, 4805, 8505,
+ -7014, -4779, -1761, -14597, 0, 0, 9243, 1301,
+ -15498, 3799, 690, -2816, 18718, -8223, 889, 255,
+ -1768, 4485, -19951, 13097, -2278, 167, 78, -740,
+ 3324, -6139, 19488, -17925, 4283, -486, 20, 0,
+ 0, 9243, -13470, -6719, 5452, -10257, 12641, -4873,
+ -5116, -10595, 5856, 11389, 1502, 10876, -608, 11765,
+ -13218, 13911, -7373, -2070, -13679, -4154, 5536, -2138,
+ 16643, 451, 0, 0, 9243, 2455, -3679, -15387,
+ -5277, -1261, -8697, 7906, 16112, 8147, 3208, -1690,
+ 7687, 10593, -9796, -15852, -10884, -5616, 2881, 2032,
+ 5246, -12735, -8796, 10928, 14833, 0, 0, 9243,
+ -6849, 2775, -14202, 13586, -2655, -9402, -5505, 10809,
+ -18013, 6231, 5444, -6041, 11288, 4958, -4078, 18799,
+ -9368, -9291, 4535, 7383, 9405, -7391, -2121, -4336,
+ 0, 0, 9243, 6423, -9040, 11548, 10359, -8109,
+ -450, -14580, 6431, 10857, -15475, 3569, 9707, 6416,
+ -9607, 521, 8528, -18391, 11049, 3815, -10423, 6860,
+ 6860, -883, -4221, 0, 0, 9243, 11932, -5968,
+ -8850, -14749, -9946, -6026, 7377, -4472, 5206, 14547,
+ -3406, 10508, 2526, 4411, 14543, 8444, -5822, 347,
+ 12347, -1709, -9158, 105, -16265, -12642, 0, 0,
+ 9243, 13044, -150, 9282, 16910, -274, -10332, -194,
+ -5864, 5428, -420, -12196, 344, -8679, 145, -18554,
+ -12695, -152, -14635, 503, 10389, 358, 5076, 522,
+ -16100, 0, 0, 9243, -8374, -13590, -1221, 1428,
+ 15896, 12005, 2318, -4793, 2590, -3209, -20390, -6256,
+ -2974, 10766, 1202, -876, -6597, 5004, 19896, -1541,
+ 2902, -16788, -3062, 1340, 0, 0, 9243, 9879,
+ 10267, 7300, 10073, 14167, 2416, 10469, -3094, 2899,
+ 17092, 9762, -7400, 7214, -5250, -8238, -3989, 5578,
+ 16392, -1050, -11848, -776, -5034, -15850, -5882, 0,
+ 0, 9243, -4974, -9068, 12221, -8490, 6299, -388,
+ -15478, 8702, -9920, 12723, -2810, 9668, 6905, -13040,
+ 4325, -9456, 16856, -9159, -2909, -10476, 7149, 9387,
+ -7350, 233, 0, 0, 9243, 3627, -13823, -7218,
+ -3656, -7002, 12776, 13935, 2719, 2446, 8352, 9252,
+ -7676, -18413, -6212, -429, -1272, -6335, -13356, -9510,
+ 295, 18926, 9934, 1112, -382, 0, 0, 9243,
+ -6383, -9343, -11326, 10097, 8329, 223, 14780, 6114,
+ -10348, -15590, -4195, 9257, -7445, -9439, -323, 7902,
+ 18117, 12101, -3142, -10944, -5577, 7327, 566, -4133,
+ 0, 0, 9243, 2626, 865, 15769, 5783, 317,
+ -10244, 1905, 16884, 9144, 826, -2420, -1972, -14536,
+ 2413, 16939, 12500, 1482, -4906, -578, 10096, -3476,
+ -14323, 2745, 16105, 0, 0, 9243, -8975, 12086,
+ 5450, -6832, -15149, 7333, 9200, -3550, -362, -13645,
+ -15525, -1391, 9428, -7091, -5442, 3105, -820, -17685,
+ -9175, -9462, 5572, -9191, -12325, -2180, 0, 0,
+ 9243, -114, 11576, -11058, 177, -185, 5875, -17880,
+ 8539, -198, 339, -173, -3411, -16698, 16336, -6369,
+ 193, -430, 408, -75, -10806, -7225, 19670, -13817,
+ 4665, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 32767, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 32767
+};
+
+const MappingMatrix mapping_matrix_fifthoa_mixing = { 38, 38, 0 };
+const opus_int16 mapping_matrix_fifthoa_mixing_data[1444] = {
+ 9243, 0, 16010, 0, 0, 0, 20669, 0,
+ 0, 0, 0, 0, 24456, 0, 0, 0,
+ 0, 0, 0, 0, 27731, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 30657, 0,
+ 0, 0, 0, 0, 0, 0, 9243, 0,
+ -7023, 14387, 0, 0, -4369, -14112, 14455, 0,
+ 0, 0, 10931, -510, -16777, 14031, 0, 0,
+ 0, 0, -5118, 14286, 4343, -18465, 13374, 0,
+ 0, 0, 0, 0, -6494, -12221, 11761, 8513,
+ -19458, 12605, 0, 0, 9243, -14128, 5093, 5547,
+ -10946, -10050, -7197, 3945, -11790, 7142, -9213, 6529,
+ -9701, -2563, -9923, -14846, 16521, 6816, 2764, 14103,
+ 1118, -5537, 2977, -14168, 1228, 4866, 17430, -528,
+ 10639, 2641, 10437, -1037, 11460, 1098, 1296, 15737,
+ 0, 0, 9243, 1128, -14775, 6062, 955, -2329,
+ 16069, -12511, 2477, 579, -2333, 3440, -14197, 18478,
+ -6050, 940, 303, -1604, 4106, -4223, 9829, -22688,
+ 10647, -2604, 334, 145, -927, 3203, -6017, 4507,
+ -3812, 24212, -15600, 5198, -1023, 110, 0, 0,
+ 9243, 1158, 12997, 9277, 1501, 2103, 10097, 16840,
+ 5916, 1402, 3225, 2488, 2929, 19916, 12706, 3585,
+ 1137, 3415, 4698, 2078, -5442, 16634, 18511, 8731,
+ 2095, 850, 3061, 5733, 5225, 960, -11728, 7689,
+ 20588, 14659, 5642, 1187, 0, 0, 9243, -4663,
+ -3081, -15003, 9771, 2007, -9185, 6457, 14199, -14357,
+ -4976, 3554, 6625, 11434, -7231, -11297, 17760, 8291,
+ -6267, -3368, 6712, -10837, -9107, 6524, 6793, -19531,
+ -11338, 7934, 7335, -2205, -9215, -7094, 10659, 6243,
+ -4337, -1250, 0, 0, 9243, -13515, 7679, -3831,
+ 7232, -14496, -3201, -4109, -11731, 8828, 9178, -1901,
+ -10848, -539, -14888, 9626, -10860, 12703, 3824, 12334,
+ -7104, 3496, -6203, 13852, 5461, -2109, -17277, 7837,
+ -4714, 13901, 4097, 3940, 7647, 8546, 8688, -10986,
+ 0, 0, 9243, 8113, -9860, 9657, 10943, -11174,
+ 1426, -13300, 1915, 8178, -17833, 6805, 8309, 8100,
+ -3121, -4742, 2683, -15111, 15688, 2358, -11590, 2807,
+ 2746, 8762, -7430, -2251, -5481, 16370, -4081, -9694,
+ 5872, -11539, -714, -9492, 15177, -6126, 0, 0,
+ 9243, 9933, -9215, -8528, -11831, -12785, -62, 10976,
+ -1811, 5593, 18018, 6100, 9455, -5237, 2758, 8971,
+ 2743, -9659, -13517, 5330, -10737, -4576, -2069, -15491,
+ -8749, -7226, -5237, 9191, -181, -12277, 2815, 10540,
+ -27, 14741, 16703, 3103, 0, 0, 9243, -10067,
+ -8881, -8723, 12265, 12487, -793, 10821, -1762, -6021,
+ -18002, -5072, 9912, -4395, 2587, 9368, -2767, 10021,
+ 12259, -6468, -10113, -5605, -1761, -15590, -9430, 7800,
+ 5092, -8835, 2293, 12314, 1222, 10671, -329, 13745,
+ 17349, 3563, 0, 0, 9243, -6485, 12991, -6743,
+ 6108, -11768, 10080, -12236, 238, -2883, 13115, -13907,
+ 2900, -14460, 511, 2564, 186, -7019, 19094, -11597,
+ -5472, -12058, 744, 6243, -2384, 930, 501, -11778,
+ 21214, -5330, -11746, -5542, 827, 10475, -6418, 1132,
+ 0, 0, 9243, 3862, 5238, -14627, -7891, 2826,
+ -7015, -10701, 13900, 11410, -6831, -1679, -9861, 6359,
+ 12032, -11660, -14041, 11199, 1713, -3895, 657, 14749,
+ -3017, -11445, 8380, 15575, -15236, -346, 7690, -923,
+ 10317, 3498, -13545, 354, 9093, -4476, 0, 0,
+ 9243, -8417, 13183, 3418, -4018, -15498, 10685, 6294,
+ -4132, 1419, -8755, -18818, 3926, 7642, -9001, -3235,
+ 2125, 3506, -13037, -16570, -4337, 6729, -13404, -7991,
+ 59, 443, 5804, 6005, -15011, -9060, -11044, 3679,
+ -15434, -13685, 161, 1185, 0, 0, 9243, -5288,
+ 6773, -13508, 9977, -5002, -4784, -12780, 10790, -12942,
+ 11168, 519, -10890, 1326, 12078, -6274, 13780, -16427,
+ 2186, 5352, -4328, 13671, 2364, -7963, 1080, -12568,
+ 19336, -6557, -8574, 4084, 7277, 10433, -9273, -3178,
+ 1516, 3817, 0, 0, 9243, 9660, 7817, 10093,
+ 13619, 10548, -2942, 11021, 597, 9663, 17594, 1736,
+ -10794, 1814, 771, -8469, 1041, 14155, 7891, -8597,
+ -7498, -8982, 346, -12407, -11848, -6809, 1686, 9181,
+ -8306, -10247, 3538, -10706, -364, -8047, -19188, -8493,
+ 0, 0, 9243, -7163, -1020, 14282, -14289, 1021,
+ -10208, -2036, 10660, -18919, 2410, 6564, 2323, -13088,
+ -1798, 3365, -19498, 3619, 12022, -1858, 9978, 3705,
+ -8969, -643, -5794, -15523, 4123, 15113, -3949, -6265,
+ -3596, 12490, 2946, -2688, 1225, -14570, 0, 0,
+ 9243, -12187, 772, -10354, 17623, -1314, -10262, -1117,
+ -2885, -9937, 2249, 11267, -1763, 9572, -368, 16506,
+ -6510, -1438, -15014, 2402, 10157, 2041, 2458, 2389,
+ -19346, 19860, -1041, 8067, -3704, -10931, 2743, -9286,
+ 606, -13399, -3095, 7924, 0, 0, 9243, 15545,
+ -2367, -3011, -6538, -5139, -9657, 995, -16242, -15706,
+ 2557, -12952, 5226, 2508, 6353, 10156, 13593, 6966,
+ 4795, 8960, 8183, -1735, 11914, -4504, 14149, 11727,
+ -6665, 10460, -3962, 10145, -7648, -1965, -9845, -6764,
+ -6938, -16633, 0, 0, 9243, 3098, 12983, -8841,
+ -3826, 5618, 10053, -16031, 4787, 3283, -8209, 6632,
+ 2856, -18922, 10272, -2055, -2344, 7987, -11939, 5516,
+ -5520, -15739, 14940, -5001, 530, 1465, -6306, 13388,
+ -13243, 2513, -11772, -7170, 16572, -8384, 1426, 168,
+ 0, 0, 9243, -15767, -2008, -1916, 4220, 4422,
+ -9846, 537, -17105, 17650, -1400, 13589, 4481, 1651,
+ 5677, 6701, -9241, -6642, -3252, -7827, 8792, -951,
+ 13182, -2522, 17586, -17005, 3845, -12562, 2213, -11472,
+ -6688, -1394, -8970, -4769, -7316, -11753, 0, 0,
+ 9243, -13344, -3829, 7975, -14863, 7136, -8561, -4265,
+ -7992, -801, 9405, 8912, 7937, -5326, 5057, -17681,
+ 15207, 575, 7717, -11360, 4847, 6789, 4150, 12686,
+ -10050, 16730, -12063, 322, -12920, -3313, -10267, 1980,
+ -6948, 7112, 7972, 8042, 0, 0, 9243, 7791,
+ -1021, 13949, 15180, -1111, -10208, -1989, 9348, 19199,
+ -2561, -7140, 2323, -12782, -1577, 817, 18164, -3673,
+ -12771, 2022, 9978, 3620, -7865, -156, -9155, 11924,
+ -3842, -15336, 4196, 6814, -3596, 12199, 2583, -652,
+ 1936, -17637, 0, 0, 9243, -4810, -15144, -1958,
+ 1315, 10175, 17406, 4142, -1348, 263, -3292, -15632,
+ -17046, -6363, 3374, 605, -227, -748, 5997, 20334,
+ 14481, 8277, -6146, -1717, 5, 27, 712, 1542,
+ -9197, -23572, -10163, -9595, 9425, 3539, -17, -72,
+ 0, 0, 9243, -7366, 8261, 11568, -11901, -8499,
+ -2079, 13347, 5556, -12049, -16247, -2282, -10529, 3584,
+ 7585, -1577, -8464, -18652, -8902, 5913, -8688, -9287,
+ 4156, -2442, -7089, -2993, -14485, -13949, 5422, 8459,
+ 1638, -13285, -2531, -1826, -12132, -9456, 0, 0,
+ 9243, 11716, 698, -10889, -17818, 1143, -10275, -1062,
+ -1305, 12057, -2057, -10855, -1595, 10088, -150, 15043,
+ 2978, 1578, 15225, -2090, 10201, 1943, 1115, 1969,
+ -20211, -17636, 430, -9826, 3391, 10572, 2485, -9826,
+ 248, -12259, -2924, 12131, 0, 0, 9243, 4361,
+ -4594, -14703, -8956, -2798, -7781, 9434, 13769, 12936,
+ 6800, -2400, 9082, 8091, -10453, -11023, -15786, -11136,
+ 3285, 4153, 2658, -14002, -5051, 9489, 7000, 17206,
+ 15024, -2777, -8491, -42, -10626, 141, 13053, 2366,
+ -6662, -2231, 0, 0, 9243, -752, -11933, -10646,
+ 1119, 1254, 6890, 17745, 7875, -1203, -2207, -1251,
+ 2024, -17706, -15532, -5600, 1128, 2691, 2800, 683,
+ -9927, 9661, 19706, 12522, 3889, -978, -2789, -3992,
+ -2440, 206, 12695, 2921, -17173, -18575, -9616, -2657,
+ 0, 0, 9243, 4791, -15001, -2887, -1931, -10037,
+ 16885, 6048, -1020, 46, 4789, 15191, -15922, -9154,
+ 2530, 823, 252, -130, -8608, -19335, 12613, 11651,
+ -4549, -2314, -172, -101, -784, 265, 12975, 21741,
+ -7551, -13101, 6856, 4710, 535, -46, 0, 0,
+ 9243, -12153, -10395, 754, -1281, 17644, 2735, -1095,
+ -10274, 8359, 2200, -12593, 7083, 782, 17650, -1573,
+ 1685, -16282, -2164, -530, -11878, 32, -17359, 3065,
+ 6651, -5212, -3628, 19365, 965, 13180, 8243, -818,
+ 7746, -3645, -14323, 1670, 0, 0, 9243, -6961,
+ -11198, 9081, -8829, 10887, 4833, -14202, 2374, -6524,
+ 16339, -9417, 4737, 12284, -4394, -2691, -2683, 13690,
+ -18539, 2830, -11438, -3692, 4985, 5648, -4628, 514,
+ 6225, -18409, 12672, 5311, 11170, -6928, -3407, -7595,
+ 10737, -3977, 0, 0, 9243, 12099, -10405, 1294,
+ 2187, -17582, 2760, -1880, -10105, -8058, -3760, 12583,
+ 7058, 1346, 17376, -2667, -2829, 15710, 3705, 468,
+ -11880, 50, -17123, 5201, 6230, 4698, 6098, -18716,
+ -1665, -13088, 8285, -1400, 7696, -6196, -13429, 2770,
+ 0, 0, 9243, 8602, 13392, 1722, 2070, 16090,
+ 11359, 3222, -4960, -2638, 4581, 20106, 5099, 4026,
+ -10978, -1778, -1314, -6620, 6988, 18701, -2965, 3745,
+ -16745, -4461, 1300, 584, -3646, -11588, 8350, 11847,
+ -10050, 2372, -20010, -7809, 3608, 887, 0, 0,
+ 9243, 14252, -1958, 7026, 13986, -3899, -9870, -1922,
+ -10736, -3693, -4527, -12333, 4376, -6080, 3475, -18537,
+ -19222, 1355, -10843, 6913, 8869, 3408, 8323, 6804,
+ -5141, -13648, 7800, 2649, 7171, 10505, -6548, 5179,
+ -5505, 13299, 2086, 15579, 0, 0, 9243, 11323,
+ 9021, -6835, -10810, 14267, -489, -8613, -5689, 639,
+ -16117, 6224, -9731, -3757, -8482, 10882, 7873, 1080,
+ -11447, -6791, -10388, 4099, -6025, 18396, -5407, -7536,
+ 14714, 984, 1267, -13940, -1889, 8416, 666, 16762,
+ -10106, -3418, 0, 0, 9243, 871, 4833, 15238,
+ 1855, 588, -7508, 10287, 16162, 2857, 1481, -443,
+ -9392, -7758, 12910, 16506, 3837, 2588, -581, -851,
+ 1928, -14879, -5066, 14950, 16498, 4773, 3842, -425,
+ -1785, -82, 10578, -1435, -15554, -2459, 16520, 16250,
+ 0, 0, 9243, 14762, 5967, 1673, 3450, 12303,
+ -6027, 1394, -15022, -14571, 3402, -4217, -10507, -478,
+ -14813, -5131, -6634, -16293, -82, -15276, -1705, -1731,
+ 358, -5738, 13681, 12503, -8200, -3023, -3290, -7384,
+ 9272, -837, 14328, -1064, 16913, 7915, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 32767, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 32767
+};
+
const MappingMatrix mapping_matrix_foa_demixing = { 6, 6, 0 };
const opus_int16 mapping_matrix_foa_demixing_data[36] = {
16384, 16384, 16384, 16384, 0, 0, 0, 23170,
@@ -376,3 +657,283 @@ const opus_int16 mapping_matrix_toa_demixing_data[324] = {
0, 0, 0, 32767
};
+const MappingMatrix mapping_matrix_fourthoa_demixing = { 27, 27, 0 };
+const opus_int16 mapping_matrix_fourthoa_demixing_data[729] = {
+ 4870, 4484, 4870, 4347, 4440, 4726, 4683, 4821,
+ 4883, 4842, 4603, 4484, 4683, 4698, 4234, 4368,
+ 4603, 4783, 4783, 4820, 4821, 4347, 4820, 4440,
+ 4698, 0, 0, 101, 84, -7818, 4640, -7178,
+ -5492, 4629, 8384, 6547, -4966, 617, -6345, 1061,
+ -3241, 2939, 5549, 6390, -4434, 4994, -2610, 1993,
+ -2873, 1547, -4356, -164, 0, 0, 8797, 5074,
+ -1553, 5383, 1906, 5297, 2722, 1158, -5226, 1311,
+ -7760, -3327, -1940, 1586, -4093, -2951, -214, -6873,
+ 5450, -4875, -7193, -4438, 558, 5593, 5607, 0,
+ 0, -26, 5761, -3723, -1460, 1195, -3065, -6357,
+ -1175, 608, 6965, 2310, 2759, -8023, -7138, 5162,
+ -3624, 5006, -809, 3592, 6209, -4159, -4968, 8150,
+ 2513, -5702, 0, 0, 301, 109, 7161, -2462,
+ -2443, 5044, -7125, -2256, 1967, -9107, 259, -4928,
+ -2592, 6514, 4111, -7236, 8695, 635, 5009, -4025,
+ -1937, 4794, 3420, -3507, -400, 0, 0, -134,
+ 85, 2771, 7842, -3649, -8225, 2866, 2586, -9200,
+ -1945, -1563, 6155, -720, -1061, -3494, -4513, -487,
+ 8389, 7317, 3348, -3721, 3806, 371, -6896, 70,
+ 0, 0, 10919, 2072, -4867, 3472, -4429, 1721,
+ -4066, -5193, 1032, -5253, 9501, -2017, -3971, -5261,
+ -306, -2737, -5137, 5713, 1237, -8, 6387, 364,
+ -5423, 3364, 2888, 0, 0, -48, 8946, 1048,
+ -2691, 602, -4332, -4302, -514, -1730, 2459, -4328,
+ -2156, 3335, -2748, -6029, 4023, 155, 897, 5268,
+ -8380, 7625, 7395, 508, 3945, -8951, 0, 0,
+ 39, 4151, -5965, -3398, -7006, -3534, 2697, -8989,
+ -5237, 2913, 46, -5540, 8196, 5766, 2711, -2520,
+ -3043, -2146, -948, 4965, 1806, 2472, 8988, -1266,
+ 4840, 0, 0, -407, -189, 2179, -1627, 6516,
+ 259, 7196, -9449, -4905, -9766, 561, 4021, 3371,
+ -8650, 5032, 3329, 2534, 641, 2224, -5747, 1047,
+ -4074, 5252, -24, 674, 0, 0, 664, 237,
+ -2837, -4072, -1205, 8252, -5875, -1670, -2743, -3984,
+ 381, 5059, 1765, 2666, -8295, 7403, 1154, -2086,
+ 7622, 7105, 3677, -6943, 1050, -6632, -694, 0,
+ 0, 382, -133, 5699, 7650, 5154, -5713, -1645,
+ -6902, 6181, 4450, 1151, 410, -993, 3829, 2444,
+ -2405, -6618, -9514, 5366, -1896, 5844, -2886, -1524,
+ -7321, -1007, 0, 0, 12767, -2530, 3183, -1409,
+ -4015, -2894, -5155, -1710, 3841, -2107, -10274, 5119,
+ 3979, -4010, 5550, 4822, -746, -2507, -3080, 4289,
+ -3675, 4333, -1416, -1230, -1122, 0, 0, 17,
+ 8048, 2398, -2167, -73, -3606, 3125, 398, 731,
+ -5973, 5705, -1032, 4679, 7305, 3134, 1301, -3858,
+ -89, 2938, 4359, -9155, -4805, -8407, 3673, -8645,
+ 0, 0, 187, 7355, 3145, -6719, -4432, -5939,
+ 2541, -2810, 9723, 778, -1105, 5687, -4174, 2534,
+ -4461, 1017, -244, 5481, -1655, -6765, -3350, -4894,
+ 1592, -2318, 8827, 0, 0, 196, 3588, 9631,
+ 3063, -4564, 6043, 2683, 2595, -2488, -2186, 173,
+ -6059, -8270, -2386, 409, 7441, -8608, 376, -4364,
+ 2321, -280, 97, 8331, -3022, -4721, 0, 0,
+ 117, -748, -10833, 1533, 4200, -2875, -997, -109,
+ -3661, -6119, 4454, 8808, -9189, 8294, 1521, 7265,
+ -2348, -5094, -948, -5400, -3193, 8914, 5763, 1716,
+ -1070, 0, 0, 2497, 399, -5201, -2038, 7843,
+ -376, 7567, -5073, 7616, -5537, 2086, -3453, -5544,
+ -56, -11648, -1314, 3546, -3432, -117, 8694, -4245,
+ 9621, 3098, -2582, -2351, 0, 0, 4386, -3104,
+ -3132, -10512, 566, 5217, 5128, 4967, 1348, 7035,
+ -1470, 91, -125, -3548, 8244, -3029, -10033, 2186,
+ 9745, -6440, -2074, 3638, -1477, -7045, -562, 0,
+ 0, 2154, 8116, -6102, 6570, 12998, -712, -4126,
+ -4996, 30, 1571, -6393, -12794, 425, 5036, 1190,
+ 5763, 5653, 12933, -6671, 5197, -2964, -3316, -6354,
+ -10554, -2652, 0, 0, 12618, -3737, 93, -5901,
+ 4262, -3364, 4444, 3103, -2767, 3403, 4925, -2584,
+ -989, 4977, -3714, -1965, 3076, 326, -2946, -2568,
+ 1026, -2980, 3362, -6132, -5966, 0, 0, 6001,
+ 48, -1979, -7275, 3476, -2096, 10591, 3793, 2629,
+ -447, -14747, -3689, -5525, 8358, 6883, -9703, -4556,
+ 7471, 2965, 4056, 13221, -7327, -3073, -2353, -6720,
+ 0, 0, 621, 11034, -44, -2828, 5978, -1850,
+ -1772, 3894, -7471, -1397, 945, -2028, -2928, -2240,
+ 3172, 2222, 4544, -4243, -5645, 3745, 2573, 3511,
+ -8206, -7286, 5700, 0, 0, 321, 10818, -4982,
+ 7813, -749, 9907, 1360, -1443, 568, -1103, 2305,
+ 6045, 2270, -1063, -1920, -3073, 5893, -3476, -11346,
+ -1657, -588, 2957, -2287, -8527, -8041, 0, 0,
+ 119, -268, 2372, -3040, 4979, -3789, -5630, 10619,
+ 5900, -5109, -4585, -3862, 10467, -3527, -385, -10034,
+ -9991, 4860, 984, 2362, 2311, -6804, 6324, 433,
+ 5291, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 32767, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 32767
+};
+
+const MappingMatrix mapping_matrix_fifthoa_demixing = { 38, 38, 0 };
+const opus_int16 mapping_matrix_fifthoa_demixing_data[1444] = {
+ 3188, 3247, 3268, 3368, 3368, 3138, 3268, 3099,
+ 3211, 3368, 3099, 3247, 3211, 3368, 3368, 3368,
+ 3149, 3268, 3247, 3211, 3099, 3188, 3138, 3149,
+ 3099, 3188, 3368, 3149, 3188, 3247, 3268, 3138,
+ 3211, 3368, 3138, 3149, 0, 0, 118, -47,
+ -5011, 282, 333, -1497, -4584, 2908, 3388, -3647,
+ -2493, 1139, -2882, -1719, 3604, -2543, -4328, 5443,
+ 1286, -5498, -4583, 2510, -1743, -2556, 4168, 1446,
+ -290, 1812, -4074, -2377, 4152, 2847, 4991, 3980,
+ 393, 5072, 0, 0, 5489, -2235, 1507, -5326,
+ 4609, -1096, 2926, -3427, -3301, -3078, 4226, 1730,
+ 4627, 2561, 2966, -592, 143, -677, 4617, -755,
+ -956, -433, -5138, 3037, 157, -1394, -4498, -4984,
+ -3661, -4112, -3756, 4628, -570, 3356, 1605, 1803,
+ 0, 0, -162, 5162, 2132, 2392, 3556, -5141,
+ -1536, 2975, -3001, -3350, -2231, -5230, 1294, -4965,
+ 3494, 5230, -3292, -1359, -2945, -773, 2670, 4867,
+ -660, 3720, -3415, -5112, -3700, -1211, 407, 3013,
+ 763, 591, 2481, -2657, 5210, 784, 0, 0,
+ -156, 338, -4246, 510, 462, 3296, 2846, 3333,
+ -4292, 4574, 1940, -2986, -1275, 3701, 5022, -5250,
+ 5780, -2676, -1180, 1516, -4852, 4877, 342, -3923,
+ -5703, -2920, 379, -657, -361, -3346, 1044, 795,
+ 5257, -4004, 698, 1115, 0, 0, 47, -140,
+ -3292, -1097, 652, 855, -5260, -3691, -4470, 4521,
+ -3863, 1093, -5552, -2016, 3831, 334, -456, -1532,
+ 2068, 1788, 2054, -295, 3668, -2820, 328, -994,
+ 295, -3301, 5770, 4282, -6353, 5632, -1371, 5005,
+ 238, 4041, 0, 0, 6764, -1659, -2730, 5726,
+ 3715, -3216, -933, 531, -52, -345, 3022, -2818,
+ 4005, -1617, -1189, -3748, -3403, -3592, 4040, -3553,
+ -2806, -3444, 6023, -711, -3298, -2503, 2548, 5564,
+ 940, 1848, 1207, 4010, -3488, -358, -2511, -1966,
+ 0, 0, -64, -5039, 1403, -4455, 6240, 2189,
+ -1716, -4348, 4183, 3951, -4042, -3606, 2399, -4563,
+ 4050, -612, -395, 348, -5791, 391, -1440, -735,
+ 1398, 4359, -518, 2969, 6556, 1951, -518, -4993,
+ -925, 998, -569, -2934, 3460, 420, 0, 0,
+ 16, 5482, -4122, 770, 2082, 5020, -3961, 485,
+ -584, -793, 3, 5222, -1416, 3673, 78, 3549,
+ -937, -5723, 1673, -6162, -2540, 3082, -355, 1838,
+ -615, 4601, 2832, -359, -3346, 668, -3393, -1583,
+ -3774, -2206, 5754, -4961, 0, 0, -328, 299,
+ 2470, 317, 525, -4494, 2805, 2617, 2383, -2363,
+ -1037, 4085, 895, -4622, 3218, -6607, -3381, -5933,
+ 1397, 6394, -446, 5694, 14, -4510, 4329, 3690,
+ -334, 0, 2932, -2478, -2944, -577, -599, -230,
+ 1553, -4736, 0, 0, -324, 142, -3252, -867,
+ 1111, -1882, 3378, -6055, 6502, -6840, 4280, -2694,
+ -2876, 4190, 6454, 655, 1061, 626, -2669, -798,
+ 3192, -985, -898, -5482, -548, 2315, -558, 1302,
+ 900, 5747, -1325, 1599, -1384, -5749, 624, 1110,
+ 0, 0, 321, 312, 2188, 1322, 237, 708,
+ -304, 2463, 1500, -1094, -5112, -1010, -6799, 646,
+ 992, 1969, 3423, -3996, 2628, 4451, 3432, -2833,
+ -6101, -330, -3768, -3, -707, 5961, -4037, -3736,
+ 4080, 7254, -4113, 2151, 54, -2150, 0, 0,
+ 7735, 4064, -3884, -5240, 577, 2229, -3947, 2914,
+ 3555, 4011, 774, -3519, 1985, -3701, -3824, 330,
+ -905, 2085, 1155, 2176, 3006, 340, -5533, -3264,
+ -902, 3114, 344, -5060, 1524, 1805, 1926, 2350,
+ 1905, -3203, -2762, -4162, 0, 0, 193, -151,
+ -1434, 6289, 7354, 4234, 169, 2868, -1977, -1375,
+ -4987, 2345, 2742, 599, 939, -4837, 2688, 991,
+ -6907, 716, -1542, -4346, -1833, 1493, 3134, 2903,
+ -7019, -2835, 93, 4395, 621, 870, -2357, -975,
+ -2933, -127, 0, 0, -616, -5968, -3479, -1651,
+ 4932, -2445, -5512, -1451, 691, 739, 479, 4227,
+ -2886, 3853, 8, -501, 188, 1990, 3842, 2270,
+ 1662, -174, 1290, 2456, 67, -3267, -5535, 483,
+ 5721, -1642, 6501, -3432, 1184, -3246, 4101, -4880,
+ 0, 0, -465, 5264, -4812, 682, 1683, -4539,
+ 2916, -1985, 2899, 3324, 1060, -4398, -745, -2137,
+ -3827, 1044, 6225, 3609, -532, 1980, -6001, 564,
+ -209, -1299, 5336, -3605, -1484, 37, 19, -1295,
+ -665, -385, -6773, 3651, 6153, -1291, 0, 0,
+ 193, -415, 5166, -110, 626, 6743, -2860, 1425,
+ 1101, -1341, 80, -4533, 249, 4231, -119, -6009,
+ -2970, 5170, -822, -2610, 4527, 5948, 182, -2589,
+ 837, -5471, 371, -43, 373, -665, -1233, -626,
+ -7353, 2606, 1339, -1398, 0, 0, -533, 147,
+ 2075, -672, 1043, 3503, 4402, -4971, -3287, 3731,
+ -2606, 3817, 1972, -5603, 5114, 1185, -1318, 1906,
+ 3018, -1999, 343, -1943, 207, -6744, 913, -4060,
+ 645, -349, -5667, 4766, 5575, -1733, 1116, 160,
+ 1534, -5690, 0, 0, -137, -36, 1556, 1325,
+ 1553, -2230, 1188, 5296, -5104, 4673, 6295, 498,
+ -4723, 933, 2994, 4067, -4700, 1758, -4116, -1252,
+ 2444, -4092, 1653, -2802, 5069, 1133, 790, -2355,
+ -934, -6304, 1642, 2045, -4259, -3873, -213, 215,
+ 0, 0, -364, 423, 4888, -1316, 118, -950,
+ 4027, 114, 2961, -3136, -3012, -883, -6192, 1340,
+ -3210, -1193, 1376, 3128, 1596, -2994, -3194, 533,
+ 8502, 2487, -1485, 1032, 301, -8007, -577, 887,
+ 297, 7778, 3121, -1901, -94, -6401, 0, 0,
+ 9260, -1845, 668, 2787, -2255, 2699, -2512, -3737,
+ -3675, -3601, -1803, 210, -1701, -1442, -2700, 3457,
+ 2868, 2079, -2113, 3178, 1277, 3578, 5240, -2482,
+ 3324, 1020, -4027, 3835, -3758, -3633, -3170, -1310,
+ 2509, -3110, 713, 174, 0, 0, -399, 4969,
+ -2321, -7744, 6494, -3776, 1478, 758, -1794, -2233,
+ -4059, 4932, 2770, 4761, -3475, 1243, 829, -651,
+ -5358, -436, 2381, 1360, 2561, -3118, 858, -4366,
+ 3933, 3646, -43, -1310, -16, 924, 1197, 1415,
+ -5036, -376, 0, 0, 100, 1410, 1290, 3199,
+ 7091, -3638, -2641, 1118, 45, -441, 794, -974,
+ -5033, 889, 438, -3102, 895, 3555, 4672, 4795,
+ 1129, -2408, -2153, 1742, 159, -2040, 7578, -2006,
+ -5737, 1986, -5568, -6413, 2428, -1387, -2441, 667,
+ 0, 0, -37, -6031, -4434, -904, 3290, 1806,
+ 4736, 2516, -5905, -5927, 1754, -4300, -2468, -2203,
+ -4836, -672, 1444, -1591, -1631, -1789, 4311, -153,
+ -688, -1222, 1058, 3139, 4659, -353, 1543, 1838,
+ 2180, -1448, 2432, 6277, 5304, -1692, 0, 0,
+ -280, 4506, 807, -477, 823, 3550, 1427, -1856,
+ -3003, -3501, -1203, 2679, 933, 778, -4954, -1977,
+ -7458, 4687, 435, 7045, -4053, -3130, 257, -3917,
+ -6165, 1889, 927, 235, 1889, -1097, 1985, 630,
+ -2172, -2130, 7080, 4810, 0, 0, -300, 496,
+ 2808, 279, 667, -7179, -2661, -526, -2832, 1751,
+ 2849, 4829, -906, -4151, -1124, -3062, 8166, 5361,
+ -1656, -6017, 3265, 2551, -864, -432, -6966, 6295,
+ -168, 901, 442, -582, 269, 236, -3574, 799,
+ 472, 565, 0, 0, 805, -2466, 6208, -4592,
+ -170, -6701, -5610, 3678, -4242, 4561, -724, -5534,
+ 2415, 7354, 2761, 2699, -349, 3822, -2372, 1756,
+ -5523, -3445, -588, -5749, -3986, 9804, -3871, 5375,
+ -2308, 5504, -2766, -1651, 1472, 6832, 2705, -5104,
+ 0, 0, -700, -1179, 4402, 400, 1383, 939,
+ -1342, 6013, 2577, -3472, 472, 2883, 1450, -3917,
+ 2849, 5084, 4990, 5392, 342, -4925, -3329, -5372,
+ -2674, -6035, -5072, -836, 179, 2506, 7987, -3647,
+ -8202, -1437, 1891, 2400, 1607, -3611, 0, 0,
+ -4706, -4003, 9928, -379, 5557, 3738, -8789, 685,
+ 1937, -5157, 13388, 7995, -4119, -9909, -5079, 4804,
+ 5586, 774, -5430, 299, -9943, 3264, -3690, -3901,
+ -1133, -6199, 3182, 1544, 5467, 3686, -2639, 4068,
+ 1163, -185, -1299, -506, 0, 0, 843, 1005,
+ -1059, 467, -1279, -2259, 6057, -1694, -5885, 5342,
+ -5160, -3748, -1382, 4420, -697, -2000, -3808, 3100,
+ 2685, -4073, 531, 318, -7822, 2414, 2901, 3399,
+ -1340, 8449, 3685, 463, -3341, 2423, 2304, -2723,
+ 84, -2622, 0, 0, 12088, -265, 2562, -435,
+ -4348, -2426, 3538, 1552, 1279, 883, -4166, 2634,
+ -6130, 2994, 3729, -1570, -601, -1753, -5124, -2788,
+ -2096, -1920, -2649, 2793, -1079, -1952, 2983, -1530,
+ 2499, 1769, 1492, -6757, -2108, 2841, 1466, 2597,
+ 0, 0, -3830, -4093, 2448, 12720, 7737, -665,
+ -832, -9257, 2971, -2400, 791, 1873, 1072, -587,
+ -7440, 8055, 1531, -4736, 616, -1782, -2982, 9663,
+ -5057, -5926, 1610, -4489, 7033, -8658, 6010, -5673,
+ 5648, 812, -271, -1802, -4500, 4392, 0, 0,
+ -888, -327, 3373, -1084, 7959, 2430, 1898, -2360,
+ -1820, -1377, -1090, -4436, -3422, -1106, -3230, 3876,
+ -41, -5128, 6375, -1848, -3824, 5844, 617, -1957,
+ 4232, 1345, -1439, -83, 3046, -214, 5458, -5566,
+ -4387, -3738, -5740, 8657, 0, 0, 6978, 6239,
+ -3686, -981, -2854, 78, 5859, -357, 4618, 7391,
+ -138, 971, -5799, 2135, 4478, -7004, -5949, 1668,
+ -6933, -1163, 7010, -5624, 2990, 6192, -8075, 3567,
+ -8308, 2236, -5098, -2120, -4355, -4238, 4955, 10230,
+ 692, -5606, 0, 0, -1348, -7069, -12, -4927,
+ 1211, 651, 1360, 7744, 3404, 5069, -2438, -105,
+ 2332, 1494, -4686, 1336, -3628, -881, 2474, 1736,
+ -26, -257, 2135, -4452, 446, -641, -4704, 2605,
+ -6436, 6662, -4939, 990, -1100, -3782, 5028, 4753,
+ 0, 0, -2875, 6410, 3518, 3950, 1271, 869,
+ -2842, -5837, 1532, -2899, 1140, -597, 1712, -1988,
+ -4819, -4783, 4773, -8796, 2240, -4596, 3565, -4853,
+ -556, -3974, 7366, -4370, 3113, -3548, 3552, -5450,
+ 3869, 2514, 6736, -4570, 6074, 3151, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 32767, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 32767
+};
diff --git a/src/mapping_matrix.h b/src/mapping_matrix.h
index 98bc82df..53646cb1 100644
--- a/src/mapping_matrix.h
+++ b/src/mapping_matrix.h
@@ -117,6 +117,12 @@ extern const opus_int16 mapping_matrix_soa_mixing_data[121];
extern const MappingMatrix mapping_matrix_toa_mixing;
extern const opus_int16 mapping_matrix_toa_mixing_data[324];
+extern const MappingMatrix mapping_matrix_fourthoa_mixing;
+extern const opus_int16 mapping_matrix_fourthoa_mixing_data[729];
+
+extern const MappingMatrix mapping_matrix_fifthoa_mixing;
+extern const opus_int16 mapping_matrix_fifthoa_mixing_data[1444];
+
extern const MappingMatrix mapping_matrix_foa_demixing;
extern const opus_int16 mapping_matrix_foa_demixing_data[36];
@@ -126,6 +132,12 @@ extern const opus_int16 mapping_matrix_soa_demixing_data[121];
extern const MappingMatrix mapping_matrix_toa_demixing;
extern const opus_int16 mapping_matrix_toa_demixing_data[324];
+extern const MappingMatrix mapping_matrix_fourthoa_demixing;
+extern const opus_int16 mapping_matrix_fourthoa_demixing_data[729];
+
+extern const MappingMatrix mapping_matrix_fifthoa_demixing;
+extern const opus_int16 mapping_matrix_fifthoa_demixing_data[1444];
+
#ifdef __cplusplus
}
#endif
diff --git a/src/meson.build b/src/meson.build
index cc07ff06..5537c67c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -17,7 +17,7 @@ opus_lib = library('opus',
darwin_versions: macosversion,
c_args: opus_lib_c_args,
include_directories: opus_includes,
- link_with: [celt_lib, silk_lib],
+ link_whole: [celt_lib, silk_lib, dnn_lib],
dependencies: libm,
install: true)
diff --git a/src/mlp.c b/src/mlp.c
index 964c6a98..e658ccde 100644
--- a/src/mlp.c
+++ b/src/mlp.c
@@ -33,35 +33,23 @@
#include "opus_types.h"
#include "opus_defines.h"
#include "arch.h"
-#include "tansig_table.h"
#include "mlp.h"
+#define fmadd(a, b, c) ((a)*(b)+(c))
static OPUS_INLINE float tansig_approx(float x)
{
- int i;
- float y, dy;
- float sign=1;
- /* Tests are reversed to catch NaNs */
- if (!(x<8))
- return 1;
- if (!(x>-8))
- return -1;
-#ifndef FIXED_POINT
- /* Another check in case of -ffast-math */
- if (celt_isnan(x))
- return 0;
-#endif
- if (x<0)
- {
- x=-x;
- sign=-1;
- }
- i = (int)floor(.5f+25*x);
- x -= .04f*i;
- y = tansig_table[i];
- dy = 1-y*y;
- y = y + x*dy*(1 - y*x);
- return sign*y;
+ const float N0 = 952.52801514f;
+ const float N1 = 96.39235687f;
+ const float N2 = 0.60863042f;
+ const float D0 = 952.72399902f;
+ const float D1 = 413.36801147f;
+ const float D2 = 11.88600922f;
+ float X2, num, den;
+ X2 = x*x;
+ num = fmadd(fmadd(N2, X2, N1), X2, N0);
+ den = fmadd(fmadd(D2, X2, D1), X2, D0);
+ num = num*x/den;
+ return MAX32(-1.f, MIN32(1.f, num));
}
static OPUS_INLINE float sigmoid_approx(float x)
@@ -79,7 +67,7 @@ static void gemm_accum(float *out, const opus_int8 *weights, int rows, int cols,
}
}
-void compute_dense(const DenseLayer *layer, float *output, const float *input)
+void analysis_compute_dense(const AnalysisDenseLayer *layer, float *output, const float *input)
{
int i;
int N, M;
@@ -101,7 +89,7 @@ void compute_dense(const DenseLayer *layer, float *output, const float *input)
}
}
-void compute_gru(const GRULayer *gru, float *state, const float *input)
+void analysis_compute_gru(const AnalysisGRULayer *gru, float *state, const float *input)
{
int i;
int N, M;
diff --git a/src/mlp.h b/src/mlp.h
index d7670550..e6b1a8f7 100644
--- a/src/mlp.h
+++ b/src/mlp.h
@@ -24,8 +24,8 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef _MLP_H_
-#define _MLP_H_
+#ifndef MLP_H_
+#define MLP_H_
#include "opus_types.h"
@@ -39,7 +39,7 @@ typedef struct {
int nb_inputs;
int nb_neurons;
int sigmoid;
-} DenseLayer;
+} AnalysisDenseLayer;
typedef struct {
const opus_int8 *bias;
@@ -47,14 +47,14 @@ typedef struct {
const opus_int8 *recurrent_weights;
int nb_inputs;
int nb_neurons;
-} GRULayer;
+} AnalysisGRULayer;
-extern const DenseLayer layer0;
-extern const GRULayer layer1;
-extern const DenseLayer layer2;
+extern const AnalysisDenseLayer layer0;
+extern const AnalysisGRULayer layer1;
+extern const AnalysisDenseLayer layer2;
-void compute_dense(const DenseLayer *layer, float *output, const float *input);
+void analysis_compute_dense(const AnalysisDenseLayer *layer, float *output, const float *input);
-void compute_gru(const GRULayer *gru, float *state, const float *input);
+void analysis_compute_gru(const AnalysisGRULayer *gru, float *state, const float *input);
-#endif /* _MLP_H_ */
+#endif /* MLP_H_ */
diff --git a/src/mlp_data.c b/src/mlp_data.c
index ae4178df..65f7448e 100644
--- a/src/mlp_data.c
+++ b/src/mlp_data.c
@@ -651,20 +651,20 @@ static const opus_int8 layer2_bias[2] = {
14, 117
};
-const DenseLayer layer0 = {
+const AnalysisDenseLayer layer0 = {
layer0_bias,
layer0_weights,
25, 32, 0
};
-const GRULayer layer1 = {
+const AnalysisGRULayer layer1 = {
layer1_bias,
layer1_weights,
layer1_recur_weights,
32, 24
};
-const DenseLayer layer2 = {
+const AnalysisDenseLayer layer2 = {
layer2_bias,
layer2_weights,
24, 2, 1
diff --git a/src/opus.c b/src/opus.c
index 538b5ea7..816a4dd5 100644
--- a/src/opus.c
+++ b/src/opus.c
@@ -194,7 +194,8 @@ int opus_packet_get_samples_per_frame(const unsigned char *data,
int opus_packet_parse_impl(const unsigned char *data, opus_int32 len,
int self_delimited, unsigned char *out_toc,
const unsigned char *frames[48], opus_int16 size[48],
- int *payload_offset, opus_int32 *packet_offset)
+ int *payload_offset, opus_int32 *packet_offset,
+ const unsigned char **padding, opus_int32 *padding_len)
{
int i, bytes;
int count;
@@ -337,6 +338,11 @@ int opus_packet_parse_impl(const unsigned char *data, opus_int32 len,
data += size[i];
}
+ if (padding != NULL)
+ {
+ *padding = data;
+ *padding_len = pad;
+ }
if (packet_offset)
*packet_offset = pad+(opus_int32)(data-data0);
@@ -351,6 +357,6 @@ int opus_packet_parse(const unsigned char *data, opus_int32 len,
opus_int16 size[48], int *payload_offset)
{
return opus_packet_parse_impl(data, len, 0, out_toc,
- frames, size, payload_offset, NULL);
+ frames, size, payload_offset, NULL, NULL, NULL);
}
diff --git a/src/opus_decoder.c b/src/opus_decoder.c
index 6520e748..b57c8094 100644
--- a/src/opus_decoder.c
+++ b/src/opus_decoder.c
@@ -52,6 +52,15 @@
#include "mathops.h"
#include "cpu_support.h"
+#ifdef ENABLE_DEEP_PLC
+#include "dred_rdovae_dec_data.h"
+#include "dred_rdovae_dec.h"
+#endif
+
+#ifdef ENABLE_OSCE
+#include "osce.h"
+#endif
+
struct OpusDecoder {
int celt_dec_offset;
int silk_dec_offset;
@@ -59,7 +68,11 @@ struct OpusDecoder {
opus_int32 Fs; /** Sampling rate (at the API level) */
silk_DecControlStruct DecControl;
int decode_gain;
+ int complexity;
int arch;
+#ifdef ENABLE_DEEP_PLC
+ LPCNetPLCState lpcnet;
+#endif
/* Everything beyond this point gets cleared on a reset */
#define OPUS_DECODER_RESET_START stream_channels
@@ -135,6 +148,7 @@ int opus_decoder_init(OpusDecoder *st, opus_int32 Fs, int channels)
silk_dec = (char*)st+st->silk_dec_offset;
celt_dec = (CELTDecoder*)((char*)st+st->celt_dec_offset);
st->stream_channels = st->channels = channels;
+ st->complexity = 0;
st->Fs = Fs;
st->DecControl.API_sampleRate = st->Fs;
@@ -152,6 +166,9 @@ int opus_decoder_init(OpusDecoder *st, opus_int32 Fs, int channels)
st->prev_mode = 0;
st->frame_size = Fs/400;
+#ifdef ENABLE_DEEP_PLC
+ lpcnet_plc_init( &st->lpcnet);
+#endif
st->arch = opus_select_arch();
return OPUS_OK;
}
@@ -370,7 +387,7 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data,
pcm_ptr = pcm_silk;
if (st->prev_mode==MODE_CELT_ONLY)
- silk_InitDecoder( silk_dec );
+ silk_ResetDecoder( silk_dec );
/* The SILK PLC cannot produce frames of less than 10 ms */
st->DecControl.payloadSize_ms = IMAX(10, 1000 * audiosize / st->Fs);
@@ -394,14 +411,28 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data,
st->DecControl.internalSampleRate = 16000;
}
}
+ st->DecControl.enable_deep_plc = st->complexity >= 5;
+#ifdef ENABLE_OSCE
+ st->DecControl.osce_method = OSCE_METHOD_NONE;
+#ifndef DISABLE_LACE
+ if (st->complexity >= 6) {st->DecControl.osce_method = OSCE_METHOD_LACE;}
+#endif
+#ifndef DISABLE_NOLACE
+ if (st->complexity >= 7) {st->DecControl.osce_method = OSCE_METHOD_NOLACE;}
+#endif
+#endif
- lost_flag = data == NULL ? 1 : 2 * decode_fec;
+ lost_flag = data == NULL ? 1 : 2 * !!decode_fec;
decoded_samples = 0;
do {
/* Call SILK decoder */
int first_frame = decoded_samples == 0;
silk_ret = silk_Decode( silk_dec, &st->DecControl,
- lost_flag, first_frame, &dec, pcm_ptr, &silk_frame_size, st->arch );
+ lost_flag, first_frame, &dec, pcm_ptr, &silk_frame_size,
+#ifdef ENABLE_DEEP_PLC
+ &st->lpcnet,
+#endif
+ st->arch );
if( silk_ret ) {
if (lost_flag) {
/* PLC failure should not be fatal */
@@ -521,8 +552,12 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data,
if (mode != st->prev_mode && st->prev_mode > 0 && !st->prev_redundancy)
MUST_SUCCEED(celt_decoder_ctl(celt_dec, OPUS_RESET_STATE));
/* Decode CELT */
- celt_ret = celt_decode_with_ec(celt_dec, decode_fec ? NULL : data,
- len, pcm, celt_frame_size, &dec, celt_accum);
+ celt_ret = celt_decode_with_ec_dred(celt_dec, decode_fec ? NULL : data,
+ len, pcm, celt_frame_size, &dec, celt_accum
+#ifdef ENABLE_DEEP_PLC
+ , &st->lpcnet
+#endif
+ );
} else {
unsigned char silence[2] = {0xFF, 0xFF};
if (!celt_accum)
@@ -634,7 +669,7 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data,
int opus_decode_native(OpusDecoder *st, const unsigned char *data,
opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec,
- int self_delimited, opus_int32 *packet_offset, int soft_clip)
+ int self_delimited, opus_int32 *packet_offset, int soft_clip, const OpusDRED *dred, opus_int32 dred_offset)
{
int i, nb_samples;
int count, offset;
@@ -648,6 +683,35 @@ int opus_decode_native(OpusDecoder *st, const unsigned char *data,
/* For FEC/PLC, frame_size has to be to have a multiple of 2.5 ms */
if ((decode_fec || len==0 || data==NULL) && frame_size%(st->Fs/400)!=0)
return OPUS_BAD_ARG;
+#ifdef ENABLE_DRED
+ if (dred != NULL && dred->process_stage == 2) {
+ int F10;
+ int features_per_frame;
+ int needed_feature_frames;
+ int init_frames;
+ lpcnet_plc_fec_clear(&st->lpcnet);
+ F10 = st->Fs/100;
+ /* if blend==0, the last PLC call was "update" and we need to feed two extra 10-ms frames. */
+ init_frames = (st->lpcnet.blend == 0) ? 2 : 0;
+ features_per_frame = IMAX(1, frame_size/F10);
+ needed_feature_frames = init_frames + features_per_frame;
+ lpcnet_plc_fec_clear(&st->lpcnet);
+ for (i=0;i<needed_feature_frames;i++) {
+ int feature_offset;
+ /* We floor instead of rounding because 5-ms overlap compensates for the missing 0.5 rounding offset. */
+ feature_offset = init_frames - i - 2 + (int)floor(((float)dred_offset + dred->dred_offset*F10/4)/F10);
+ if (feature_offset <= 4*dred->nb_latents-1 && feature_offset >= 0) {
+ lpcnet_plc_fec_add(&st->lpcnet, dred->fec_features+feature_offset*DRED_NUM_FEATURES);
+ } else {
+ if (feature_offset >= 0) lpcnet_plc_fec_add(&st->lpcnet, NULL);
+ }
+
+ }
+ }
+#else
+ (void)dred;
+ (void)dred_offset;
+#endif
if (len==0 || data==NULL)
{
int pcm_count=0;
@@ -672,7 +736,7 @@ int opus_decode_native(OpusDecoder *st, const unsigned char *data,
packet_stream_channels = opus_packet_get_nb_channels(data);
count = opus_packet_parse_impl(data, len, self_delimited, &toc, NULL,
- size, &offset, packet_offset);
+ size, &offset, packet_offset, NULL, NULL);
if (count<0)
return count;
@@ -684,12 +748,12 @@ int opus_decode_native(OpusDecoder *st, const unsigned char *data,
int ret;
/* If no FEC can be present, run the PLC (recursive call) */
if (frame_size < packet_frame_size || packet_mode == MODE_CELT_ONLY || st->mode == MODE_CELT_ONLY)
- return opus_decode_native(st, NULL, 0, pcm, frame_size, 0, 0, NULL, soft_clip);
+ return opus_decode_native(st, NULL, 0, pcm, frame_size, 0, 0, NULL, soft_clip, NULL, 0);
/* Otherwise, run the PLC on everything except the size for which we might have FEC */
duration_copy = st->last_packet_duration;
if (frame_size-packet_frame_size!=0)
{
- ret = opus_decode_native(st, NULL, 0, pcm, frame_size-packet_frame_size, 0, 0, NULL, soft_clip);
+ ret = opus_decode_native(st, NULL, 0, pcm, frame_size-packet_frame_size, 0, 0, NULL, soft_clip, NULL, 0);
if (ret<0)
{
st->last_packet_duration = duration_copy;
@@ -753,7 +817,7 @@ int opus_decode(OpusDecoder *st, const unsigned char *data,
{
if(frame_size<=0)
return OPUS_BAD_ARG;
- return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL, 0);
+ return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL, 0, NULL, 0);
}
#ifndef DISABLE_FLOAT_API
@@ -781,7 +845,7 @@ int opus_decode_float(OpusDecoder *st, const unsigned char *data,
celt_assert(st->channels == 1 || st->channels == 2);
ALLOC(out, frame_size*st->channels, opus_int16);
- ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 0);
+ ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 0, NULL, 0);
if (ret > 0)
{
for (i=0;i<ret*st->channels;i++)
@@ -819,7 +883,7 @@ int opus_decode(OpusDecoder *st, const unsigned char *data,
celt_assert(st->channels == 1 || st->channels == 2);
ALLOC(out, frame_size*st->channels, float);
- ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 1);
+ ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 1, NULL, 0);
if (ret > 0)
{
for (i=0;i<ret*st->channels;i++)
@@ -834,7 +898,7 @@ int opus_decode_float(OpusDecoder *st, const unsigned char *data,
{
if(frame_size<=0)
return OPUS_BAD_ARG;
- return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL, 0);
+ return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL, 0, NULL, 0);
}
#endif
@@ -864,6 +928,27 @@ int opus_decoder_ctl(OpusDecoder *st, int request, ...)
*value = st->bandwidth;
}
break;
+ case OPUS_SET_COMPLEXITY_REQUEST:
+ {
+ opus_int32 value = va_arg(ap, opus_int32);
+ if(value<0 || value>10)
+ {
+ goto bad_arg;
+ }
+ st->complexity = value;
+ celt_decoder_ctl(celt_dec, OPUS_SET_COMPLEXITY(value));
+ }
+ break;
+ case OPUS_GET_COMPLEXITY_REQUEST:
+ {
+ opus_int32 *value = va_arg(ap, opus_int32*);
+ if (!value)
+ {
+ goto bad_arg;
+ }
+ *value = st->complexity;
+ }
+ break;
case OPUS_GET_FINAL_RANGE_REQUEST:
{
opus_uint32 *value = va_arg(ap, opus_uint32*);
@@ -881,9 +966,12 @@ int opus_decoder_ctl(OpusDecoder *st, int request, ...)
((char*)&st->OPUS_DECODER_RESET_START - (char*)st));
celt_decoder_ctl(celt_dec, OPUS_RESET_STATE);
- silk_InitDecoder( silk_dec );
+ silk_ResetDecoder( silk_dec );
st->stream_channels = st->channels;
st->frame_size = st->Fs/400;
+#ifdef ENABLE_DEEP_PLC
+ lpcnet_plc_reset( &st->lpcnet );
+#endif
}
break;
case OPUS_GET_SAMPLE_RATE_REQUEST:
@@ -959,6 +1047,20 @@ int opus_decoder_ctl(OpusDecoder *st, int request, ...)
ret = celt_decoder_ctl(celt_dec, OPUS_GET_PHASE_INVERSION_DISABLED(value));
}
break;
+#ifdef USE_WEIGHTS_FILE
+ case OPUS_SET_DNN_BLOB_REQUEST:
+ {
+ const unsigned char *data = va_arg(ap, const unsigned char *);
+ opus_int32 len = va_arg(ap, opus_int32);
+ if(len<0 || data == NULL)
+ {
+ goto bad_arg;
+ }
+ ret = lpcnet_plc_load_model(&st->lpcnet, data, len);
+ ret = silk_LoadOSCEModels(silk_dec, data, len) || ret;
+ }
+ break;
+#endif
default:
/*fprintf(stderr, "unknown opus_decoder_ctl() request: %d", request);*/
ret = OPUS_UNIMPLEMENTED;
@@ -1034,8 +1136,373 @@ int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len,
return samples;
}
+int opus_packet_has_lbrr(const unsigned char packet[], opus_int32 len)
+{
+ int ret;
+ const unsigned char *frames[48];
+ opus_int16 size[48];
+ int packet_mode, packet_frame_size, packet_stream_channels;
+ int nb_frames=1;
+ int lbrr;
+
+ packet_mode = opus_packet_get_mode(packet);
+ if (packet_mode == MODE_CELT_ONLY)
+ return 0;
+ packet_frame_size = opus_packet_get_samples_per_frame(packet, 48000);
+ if (packet_frame_size > 960)
+ nb_frames = packet_frame_size/960;
+ packet_stream_channels = opus_packet_get_nb_channels(packet);
+ ret = opus_packet_parse(packet, len, NULL, frames, size, NULL);
+ if (ret <= 0)
+ return ret;
+ lbrr = (frames[0][0] >> (7-nb_frames)) & 0x1;
+ if (packet_stream_channels == 2)
+ lbrr = lbrr || ((frames[0][0] >> (6-2*nb_frames)) & 0x1);
+ return lbrr;
+}
+
int opus_decoder_get_nb_samples(const OpusDecoder *dec,
const unsigned char packet[], opus_int32 len)
{
return opus_packet_get_nb_samples(packet, len, dec->Fs);
}
+
+struct OpusDREDDecoder {
+#ifdef ENABLE_DRED
+ RDOVAEDec model;
+#endif
+ int loaded;
+ int arch;
+ opus_uint32 magic;
+};
+
+#if defined(ENABLE_DRED) && (defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS))
+static void validate_dred_decoder(OpusDREDDecoder *st)
+{
+ celt_assert(st->magic == 0xD8EDDEC0);
+#ifdef OPUS_ARCHMASK
+ celt_assert(st->arch >= 0);
+ celt_assert(st->arch <= OPUS_ARCHMASK);
+#endif
+}
+#define VALIDATE_DRED_DECODER(st) validate_dred_decoder(st)
+#else
+#define VALIDATE_DRED_DECODER(st)
+#endif
+
+
+int opus_dred_decoder_get_size(void)
+{
+ return sizeof(OpusDREDDecoder);
+}
+
+#ifdef ENABLE_DRED
+int dred_decoder_load_model(OpusDREDDecoder *dec, const unsigned char *data, int len)
+{
+ WeightArray *list;
+ int ret;
+ parse_weights(&list, data, len);
+ ret = init_rdovaedec(&dec->model, list);
+ opus_free(list);
+ if (ret == 0) dec->loaded = 1;
+ return (ret == 0) ? OPUS_OK : OPUS_BAD_ARG;
+}
+#endif
+
+int opus_dred_decoder_init(OpusDREDDecoder *dec)
+{
+ int ret = 0;
+ dec->loaded = 0;
+#if defined(ENABLE_DRED) && !defined(USE_WEIGHTS_FILE)
+ ret = init_rdovaedec(&dec->model, rdovaedec_arrays);
+ if (ret == 0) dec->loaded = 1;
+#endif
+ dec->arch = opus_select_arch();
+ /* To make sure nobody forgets to init, use a magic number. */
+ dec->magic = 0xD8EDDEC0;
+ return (ret == 0) ? OPUS_OK : OPUS_UNIMPLEMENTED;
+}
+
+OpusDREDDecoder *opus_dred_decoder_create(int *error)
+{
+ int ret;
+ OpusDREDDecoder *dec;
+ dec = (OpusDREDDecoder *)opus_alloc(opus_dred_decoder_get_size());
+ if (dec == NULL)
+ {
+ if (error)
+ *error = OPUS_ALLOC_FAIL;
+ return NULL;
+ }
+ ret = opus_dred_decoder_init(dec);
+ if (error)
+ *error = ret;
+ if (ret != OPUS_OK)
+ {
+ opus_free(dec);
+ dec = NULL;
+ }
+ return dec;
+}
+
+void opus_dred_decoder_destroy(OpusDREDDecoder *dec)
+{
+ if (dec) dec->magic = 0xDE57801D;
+ opus_free(dec);
+}
+
+int opus_dred_decoder_ctl(OpusDREDDecoder *dred_dec, int request, ...)
+{
+#ifdef ENABLE_DRED
+ int ret = OPUS_OK;
+ va_list ap;
+
+ va_start(ap, request);
+ (void)dred_dec;
+ switch (request)
+ {
+# ifdef USE_WEIGHTS_FILE
+ case OPUS_SET_DNN_BLOB_REQUEST:
+ {
+ const unsigned char *data = va_arg(ap, const unsigned char *);
+ opus_int32 len = va_arg(ap, opus_int32);
+ if(len<0 || data == NULL)
+ {
+ goto bad_arg;
+ }
+ return dred_decoder_load_model(dred_dec, data, len);
+ }
+ break;
+# endif
+ default:
+ /*fprintf(stderr, "unknown opus_decoder_ctl() request: %d", request);*/
+ ret = OPUS_UNIMPLEMENTED;
+ break;
+ }
+ va_end(ap);
+ return ret;
+# ifdef USE_WEIGHTS_FILE
+bad_arg:
+ va_end(ap);
+ return OPUS_BAD_ARG;
+# endif
+#else
+ (void)dred_dec;
+ (void)request;
+ return OPUS_UNIMPLEMENTED;
+#endif
+}
+
+#ifdef ENABLE_DRED
+static int dred_find_payload(const unsigned char *data, opus_int32 len, const unsigned char **payload, int *dred_frame_offset)
+{
+ const unsigned char *data0;
+ int len0;
+ int frame = 0;
+ int ret;
+ const unsigned char *frames[48];
+ opus_int16 size[48];
+ int frame_size;
+
+ *payload = NULL;
+ /* Get the padding section of the packet. */
+ ret = opus_packet_parse_impl(data, len, 0, NULL, frames, size, NULL, NULL, &data0, &len0);
+ if (ret < 0)
+ return ret;
+ frame_size = opus_packet_get_samples_per_frame(data, 48000);
+ data = data0;
+ len = len0;
+ /* Scan extensions in order until we find the earliest frame with DRED data. */
+ while (len > 0)
+ {
+ opus_int32 header_size;
+ int id, L;
+ len0 = len;
+ data0 = data;
+ id = *data0 >> 1;
+ L = *data0 & 0x1;
+ len = skip_extension(&data, len, &header_size);
+ if (len < 0)
+ break;
+ if (id == 1)
+ {
+ if (L==0)
+ {
+ frame++;
+ } else {
+ frame += data0[1];
+ }
+ } else if (id == DRED_EXTENSION_ID)
+ {
+ const unsigned char *curr_payload;
+ opus_int32 curr_payload_len;
+ curr_payload = data0+header_size;
+ curr_payload_len = (data-data0)-header_size;
+ /* DRED position in the packet, in units of 2.5 ms like for the signaled DRED offset. */
+ *dred_frame_offset = frame*frame_size/120;
+#ifdef DRED_EXPERIMENTAL_VERSION
+ /* Check that temporary extension type and version match.
+ This check will be removed once extension is finalized. */
+ if (curr_payload_len > DRED_EXPERIMENTAL_BYTES && curr_payload[0] == 'D' && curr_payload[1] == DRED_EXPERIMENTAL_VERSION) {
+ *payload = curr_payload+2;
+ return curr_payload_len-2;
+ }
+#else
+ if (curr_payload_len > 0) {
+ *payload = curr_payload;
+ return curr_payload_len;
+ }
+#endif
+ }
+ }
+ return 0;
+}
+#endif
+
+int opus_dred_get_size(void)
+{
+#ifdef ENABLE_DRED
+ return sizeof(OpusDRED);
+#else
+ return 0;
+#endif
+}
+
+OpusDRED *opus_dred_alloc(int *error)
+{
+#ifdef ENABLE_DRED
+ OpusDRED *dec;
+ dec = (OpusDRED *)opus_alloc(opus_dred_get_size());
+ if (dec == NULL)
+ {
+ if (error)
+ *error = OPUS_ALLOC_FAIL;
+ return NULL;
+ }
+ return dec;
+#else
+ if (error)
+ *error = OPUS_UNIMPLEMENTED;
+ return NULL;
+#endif
+}
+
+void opus_dred_free(OpusDRED *dec)
+{
+#ifdef ENABLE_DRED
+ opus_free(dec);
+#else
+ (void)dec;
+#endif
+}
+
+int opus_dred_parse(OpusDREDDecoder *dred_dec, OpusDRED *dred, const unsigned char *data, opus_int32 len, opus_int32 max_dred_samples, opus_int32 sampling_rate, int *dred_end, int defer_processing)
+{
+#ifdef ENABLE_DRED
+ const unsigned char *payload;
+ opus_int32 payload_len;
+ int dred_frame_offset=0;
+ VALIDATE_DRED_DECODER(dred_dec);
+ if (!dred_dec->loaded) return OPUS_UNIMPLEMENTED;
+ dred->process_stage = -1;
+ payload_len = dred_find_payload(data, len, &payload, &dred_frame_offset);
+ if (payload_len < 0)
+ return payload_len;
+ if (payload != NULL)
+ {
+ int offset;
+ int min_feature_frames;
+ offset = 100*max_dred_samples/sampling_rate;
+ min_feature_frames = IMIN(2 + offset, 2*DRED_NUM_REDUNDANCY_FRAMES);
+ dred_ec_decode(dred, payload, payload_len, min_feature_frames, dred_frame_offset);
+ if (!defer_processing)
+ opus_dred_process(dred_dec, dred, dred);
+ if (dred_end) *dred_end = IMAX(0, -dred->dred_offset*sampling_rate/400);
+ return IMAX(0, dred->nb_latents*sampling_rate/25 - dred->dred_offset* sampling_rate/400);
+ }
+ if (dred_end) *dred_end = 0;
+ return 0;
+#else
+ (void)dred_dec;
+ (void)dred;
+ (void)data;
+ (void)len;
+ (void)max_dred_samples;
+ (void)sampling_rate;
+ (void)defer_processing;
+ (void)dred_end;
+ return OPUS_UNIMPLEMENTED;
+#endif
+}
+
+int opus_dred_process(OpusDREDDecoder *dred_dec, const OpusDRED *src, OpusDRED *dst)
+{
+#ifdef ENABLE_DRED
+ if (dred_dec == NULL || src == NULL || dst == NULL || (src->process_stage != 1 && src->process_stage != 2))
+ return OPUS_BAD_ARG;
+ VALIDATE_DRED_DECODER(dred_dec);
+ if (!dred_dec->loaded) return OPUS_UNIMPLEMENTED;
+ if (src != dst)
+ OPUS_COPY(dst, src, 1);
+ if (dst->process_stage == 2)
+ return OPUS_OK;
+ DRED_rdovae_decode_all(&dred_dec->model, dst->fec_features, dst->state, dst->latents, dst->nb_latents, dred_dec->arch);
+ dst->process_stage = 2;
+ return OPUS_OK;
+#else
+ (void)dred_dec;
+ (void)src;
+ (void)dst;
+ return OPUS_UNIMPLEMENTED;
+#endif
+}
+
+int opus_decoder_dred_decode(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, opus_int16 *pcm, opus_int32 frame_size)
+{
+#ifdef ENABLE_DRED
+ VARDECL(float, out);
+ int ret, i;
+ ALLOC_STACK;
+
+ if(frame_size<=0)
+ {
+ RESTORE_STACK;
+ return OPUS_BAD_ARG;
+ }
+
+ celt_assert(st->channels == 1 || st->channels == 2);
+ ALLOC(out, frame_size*st->channels, float);
+
+ ret = opus_decode_native(st, NULL, 0, out, frame_size, 0, 0, NULL, 1, dred, dred_offset);
+ if (ret > 0)
+ {
+ for (i=0;i<ret*st->channels;i++)
+ pcm[i] = FLOAT2INT16(out[i]);
+ }
+ RESTORE_STACK;
+ return ret;
+#else
+ (void)st;
+ (void)dred;
+ (void)dred_offset;
+ (void)pcm;
+ (void)frame_size;
+ return OPUS_UNIMPLEMENTED;
+#endif
+}
+
+int opus_decoder_dred_decode_float(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, float *pcm, opus_int32 frame_size)
+{
+#ifdef ENABLE_DRED
+ if(frame_size<=0)
+ return OPUS_BAD_ARG;
+ return opus_decode_native(st, NULL, 0, pcm, frame_size, 0, 0, NULL, 0, dred, dred_offset);
+#else
+ (void)st;
+ (void)dred;
+ (void)dred_offset;
+ (void)pcm;
+ (void)frame_size;
+ return OPUS_UNIMPLEMENTED;
+#endif
+}
diff --git a/src/opus_demo.c b/src/opus_demo.c
index 4cc26a6c..2876fff8 100644
--- a/src/opus_demo.c
+++ b/src/opus_demo.c
@@ -39,9 +39,80 @@
#include "opus_types.h"
#include "opus_private.h"
#include "opus_multistream.h"
+#ifdef ENABLE_LOSSGEN
+#include "lossgen.h"
+#endif
#define MAX_PACKET 1500
+#ifdef USE_WEIGHTS_FILE
+# if __unix__
+# include <fcntl.h>
+# include <sys/mman.h>
+# include <unistd.h>
+# include <sys/stat.h>
+/* When available, mmap() is preferable to reading the file, as it leads to
+ better resource utilization, especially if multiple processes are using the same
+ file (mapping will be shared in cache). */
+void *load_blob(const char *filename, int *len) {
+ int fd;
+ void *data;
+ struct stat st;
+ if (stat(filename, &st)) {
+ *len = 0;
+ return NULL;
+ }
+ *len = st.st_size;
+ fd = open(filename, O_RDONLY);
+ if (fd<0) {
+ *len = 0;
+ return NULL;
+ }
+ data = mmap(NULL, *len, PROT_READ, MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED) {
+ *len = 0;
+ data = NULL;
+ }
+ close(fd);
+ return data;
+}
+void free_blob(void *blob, int len) {
+ if (blob) munmap(blob, len);
+}
+# else
+void *load_blob(const char *filename, int *len) {
+ FILE *file;
+ void *data;
+ file = fopen(filename, "r");
+ if (file == NULL)
+ {
+ perror("could not open blob file");
+ *len = 0;
+ return NULL;
+ }
+ fseek(file, 0L, SEEK_END);
+ *len = ftell(file);
+ fseek(file, 0L, SEEK_SET);
+ if (*len <= 0) {
+ *len = 0;
+ return NULL;
+ }
+ data = malloc(*len);
+ if (!data) {
+ *len = 0;
+ return NULL;
+ }
+ *len = fread(data, 1, *len, file);
+ return data;
+}
+void free_blob(void *blob, int len) {
+ free(blob);
+ (void)len;
+}
+# endif
+#endif
+
+
void print_usage( char* argv[] )
{
fprintf(stderr, "Usage: %s [-e] <application> <sampling rate (Hz)> <channels (1/2)> "
@@ -58,11 +129,17 @@ void print_usage( char* argv[] )
fprintf(stderr, "-bandwidth <NB|MB|WB|SWB|FB> : audio bandwidth (from narrowband to fullband); default: sampling rate\n" );
fprintf(stderr, "-framesize <2.5|5|10|20|40|60|80|100|120> : frame size in ms; default: 20 \n" );
fprintf(stderr, "-max_payload <bytes> : maximum payload size in bytes, default: 1024\n" );
- fprintf(stderr, "-complexity <comp> : complexity, 0 (lowest) ... 10 (highest); default: 10\n" );
+ fprintf(stderr, "-complexity <comp> : encoder complexity, 0 (lowest) ... 10 (highest); default: 10\n" );
+ fprintf(stderr, "-dec_complexity <comp> : decoder complexity, 0 (lowest) ... 10 (highest); default: 0\n" );
fprintf(stderr, "-inbandfec : enable SILK inband FEC\n" );
fprintf(stderr, "-forcemono : force mono encoding, even for stereo input\n" );
fprintf(stderr, "-dtx : enable SILK DTX\n" );
- fprintf(stderr, "-loss <perc> : simulate packet loss, in percent (0-100); default: 0\n" );
+ fprintf(stderr, "-loss <perc> : optimize for loss percentage and simulate packet loss, in percent (0-100); default: 0\n" );
+#ifdef ENABLE_LOSSGEN
+ fprintf(stderr, "-sim_loss <perc> : simulate realistic (bursty) packet loss from percentage, using generative model\n" );
+#endif
+ fprintf(stderr, "-lossfile <file> : simulate packet loss, reading loss from file\n" );
+ fprintf(stderr, "-dred <frames> : add Deep REDundancy (in units of 10-ms frames)\n" );
}
static void int_to_char(opus_uint32 i, unsigned char ch[4])
@@ -80,6 +157,7 @@ static opus_uint32 char_to_int(unsigned char ch[4])
}
#define check_encoder_option(decode_only, opt) do {if (decode_only) {fprintf(stderr, "option %s is only for encoding\n", opt); goto failure;}} while(0)
+#define check_decoder_option(encode_only, opt) do {if (encode_only) {fprintf(stderr, "option %s is only for decoding\n", opt); goto failure;}} while(0)
static const int silk8_test[][4] = {
{MODE_SILK_ONLY, OPUS_BANDWIDTH_NARROWBAND, 960*3, 1},
@@ -207,6 +285,68 @@ static OpusDecoder *ms_opus_decoder_create(opus_int32 Fs, int channels, int *err
}
#endif
+
+#ifdef ENABLE_OSCE_TRAINING_DATA
+#define COMPLEXITY_MIN 0
+#define COMPLEXITY_MAX 10
+
+#define PACKET_LOSS_PERC_MIN 0
+#define PACKET_LOSS_PERC_MAX 50
+#define PACKET_LOSS_PERC_STEP 5
+
+#define CBR_BITRATE_LIMIT 8000
+
+#define NUM_BITRATES 102
+static int bitrates[NUM_BITRATES] = {
+ 6000, 6060, 6120, 6180, 6240, 6300, 6360, 6420, 6480,
+ 6525, 6561, 6598, 6634, 6670, 6707, 6743, 6780, 6816,
+ 6853, 6889, 6926, 6962, 6999, 7042, 7085, 7128, 7171,
+ 7215, 7258, 7301, 7344, 7388, 7431, 7474, 7512, 7541,
+ 7570, 7599, 7628, 7657, 7686, 7715, 7744, 7773, 7802,
+ 7831, 7860, 7889, 7918, 7947, 7976, 8013, 8096, 8179,
+ 8262, 8344, 8427, 8511, 8605, 8699, 8792, 8886, 8980,
+ 9100, 9227, 9354, 9480, 9561, 9634, 9706, 9779, 9851,
+ 9924, 9996, 10161, 10330, 10499, 10698, 10898, 11124, 11378,
+ 11575, 11719, 11862, 12014, 12345, 12751, 13195, 13561, 13795,
+ 14069, 14671, 15403, 15790, 16371, 17399, 17968, 19382, 20468,
+ 22000, 32000, 64000
+};
+
+static int randint(int min, int max, int step)
+{
+ double r = ((double) rand())/ (RAND_MAX + 1.);
+ int d;
+
+ d = ((int) ((max + 1 - min) * r / step) * step) + min;
+
+ return d;
+}
+
+static void new_random_setting(OpusEncoder *enc)
+{
+ int bitrate_bps;
+ int complexity;
+ int packet_loss_perc;
+ int use_vbr;
+
+ bitrate_bps = bitrates[randint(0, NUM_BITRATES - 1, 1)];
+ complexity = randint(COMPLEXITY_MIN, COMPLEXITY_MAX, 1);
+ packet_loss_perc = randint(PACKET_LOSS_PERC_MIN, PACKET_LOSS_PERC_MAX, PACKET_LOSS_PERC_STEP);
+ use_vbr = bitrate_bps < CBR_BITRATE_LIMIT ? 1 : randint(0, 1, 1);
+
+ if (1)
+ {
+ printf("changing settings to %d\t%d\t%d\t%d\n", bitrate_bps, complexity, packet_loss_perc, use_vbr);
+ }
+
+ opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate_bps));
+ opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity));
+ opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(packet_loss_perc));
+ opus_encoder_ctl(enc, OPUS_SET_VBR(use_vbr));
+}
+
+#endif
+
int main(int argc, char *argv[])
{
int err;
@@ -215,21 +355,28 @@ int main(int argc, char *argv[])
FILE *fout=NULL;
OpusEncoder *enc=NULL;
OpusDecoder *dec=NULL;
+ OpusDRED *dred=NULL;
+ OpusDREDDecoder *dred_dec=NULL;
int args;
- int len[2];
+ int len;
int frame_size, channels;
opus_int32 bitrate_bps=0;
- unsigned char *data[2] = {NULL, NULL};
+ unsigned char *data = NULL;
unsigned char *fbytes=NULL;
opus_int32 sampling_rate;
int use_vbr;
int max_payload_bytes;
int complexity;
+ int dec_complexity;
int use_inbandfec;
int use_dtx;
int forcechannels;
int cvbr = 0;
int packet_loss_perc;
+#ifdef ENABLE_LOSSGEN
+ float lossgen_perc = -1.f;
+ LossGenState lossgen;
+#endif
opus_int32 count=0, count_act=0;
int k;
opus_int32 skip=0;
@@ -243,8 +390,7 @@ int main(int argc, char *argv[])
int bandwidth=OPUS_AUTO;
const char *bandwidth_string;
int lost = 0, lost_prev = 1;
- int toggle = 0;
- opus_uint32 enc_final_range[2];
+ opus_uint32 enc_final_range;
opus_uint32 dec_final_range;
int encode_only=0, decode_only=0;
int max_frame_size = 48000*2;
@@ -264,6 +410,19 @@ int main(int argc, char *argv[])
int variable_duration=OPUS_FRAMESIZE_ARG;
int delayed_decision=0;
int ret = EXIT_FAILURE;
+ int lost_count=0;
+ FILE *packet_loss_file=NULL;
+ int dred_duration=0;
+#ifdef ENABLE_OSCE_TRAINING_DATA
+ int silk_random_switching = 0;
+ int silk_frame_counter = 0;
+#endif
+#ifdef USE_WEIGHTS_FILE
+ int blob_len;
+ void *blob_data;
+ const char *filename = "weights_blob.bin";
+ blob_data = load_blob(filename, &blob_len);
+#endif
if (argc < 5 )
{
@@ -335,6 +494,7 @@ int main(int argc, char *argv[])
use_vbr = 1;
max_payload_bytes = MAX_PACKET;
complexity = 10;
+ dec_complexity = 0;
use_inbandfec = 0;
forcechannels = OPUS_AUTO;
use_dtx = 0;
@@ -400,6 +560,10 @@ int main(int argc, char *argv[])
check_encoder_option(decode_only, "-complexity");
complexity = atoi( argv[ args + 1 ] );
args += 2;
+ } else if( strcmp( argv[ args ], "-dec_complexity" ) == 0 ) {
+ check_decoder_option(encode_only, "-dec_complexity");
+ dec_complexity = atoi( argv[ args + 1 ] );
+ args += 2;
} else if( strcmp( argv[ args ], "-inbandfec" ) == 0 ) {
use_inbandfec = 1;
args++;
@@ -422,6 +586,22 @@ int main(int argc, char *argv[])
} else if( strcmp( argv[ args ], "-loss" ) == 0 ) {
packet_loss_perc = atoi( argv[ args + 1 ] );
args += 2;
+#ifdef ENABLE_LOSSGEN
+ } else if( strcmp( argv[ args ], "-sim_loss" ) == 0 ) {
+ lossgen_perc = atof( argv[ args + 1 ] );
+ lossgen_init(&lossgen);
+ args += 2;
+#endif
+ } else if( strcmp( argv[ args ], "-lossfile" ) == 0 ) {
+ packet_loss_file = fopen( argv[ args + 1 ], "r" );
+ if (packet_loss_file == NULL) {
+ fprintf(stderr, "failed to open loss file %s\n", argv[ args + 1 ] );
+ exit(1);
+ }
+ args += 2;
+ } else if( strcmp( argv[ args ], "-dred" ) == 0 ) {
+ dred_duration = atoi( argv[ args + 1 ] );
+ args += 2;
} else if( strcmp( argv[ args ], "-sweep" ) == 0 ) {
check_encoder_option(decode_only, "-sweep");
sweep_bps = atoi( argv[ args + 1 ] );
@@ -473,6 +653,12 @@ int main(int argc, char *argv[])
mode_list = celt_hq_test;
nb_modes_in_list = 4;
args++;
+#ifdef ENABLE_OSCE_TRAINING_DATA
+ } else if( strcmp( argv[ args ], "-silk_random_switching" ) == 0 ){
+ silk_random_switching = atoi( argv[ args + 1 ] );
+ printf("switching encoding parameters every %dth frame\n", silk_random_switching);
+ args += 2;
+#endif
} else {
printf( "Error: unrecognized setting: %s\n\n", argv[ args ] );
print_usage( argv );
@@ -537,6 +723,10 @@ int main(int argc, char *argv[])
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&skip));
opus_encoder_ctl(enc, OPUS_SET_LSB_DEPTH(16));
opus_encoder_ctl(enc, OPUS_SET_EXPERT_FRAME_DURATION(variable_duration));
+ if (dred_duration > 0)
+ {
+ opus_encoder_ctl(enc, OPUS_SET_DRED_DURATION(dred_duration));
+ }
}
if (!encode_only)
{
@@ -546,9 +736,8 @@ int main(int argc, char *argv[])
fprintf(stderr, "Cannot create decoder: %s\n", opus_strerror(err));
goto failure;
}
+ opus_decoder_ctl(dec, OPUS_SET_COMPLEXITY(dec_complexity));
}
-
-
switch(bandwidth)
{
case OPUS_BANDWIDTH_NARROWBAND:
@@ -587,10 +776,7 @@ int main(int argc, char *argv[])
out = (short*)malloc(max_frame_size*channels*sizeof(short));
/* We need to allocate for 16-bit PCM data, but we store it as unsigned char. */
fbytes = (unsigned char*)malloc(max_frame_size*channels*sizeof(short));
- data[0] = (unsigned char*)calloc(max_payload_bytes,sizeof(unsigned char));
- if ( use_inbandfec ) {
- data[1] = (unsigned char*)calloc(max_payload_bytes,sizeof(unsigned char));
- }
+ data = (unsigned char*)calloc(max_payload_bytes,sizeof(unsigned char));
if(delayed_decision)
{
if (frame_size==sampling_rate/400)
@@ -614,6 +800,13 @@ int main(int argc, char *argv[])
opus_encoder_ctl(enc, OPUS_SET_EXPERT_FRAME_DURATION(variable_duration));
frame_size = 2*48000;
}
+ dred_dec = opus_dred_decoder_create(&err);
+ dred = opus_dred_alloc(&err);
+#ifdef USE_WEIGHTS_FILE
+ if (enc) opus_encoder_ctl(enc, OPUS_SET_DNN_BLOB(blob_data, blob_len));
+ if (dec) opus_decoder_ctl(dec, OPUS_SET_DNN_BLOB(blob_data, blob_len));
+ if (dred_dec) opus_dred_decoder_ctl(dred_dec, OPUS_SET_DNN_BLOB(blob_data, blob_len));
+#endif
while (!stop)
{
if (delayed_celt)
@@ -652,22 +845,22 @@ int main(int argc, char *argv[])
num_read = fread(ch, 1, 4, fin);
if (num_read!=4)
break;
- len[toggle] = char_to_int(ch);
- if (len[toggle]>max_payload_bytes || len[toggle]<0)
+ len = char_to_int(ch);
+ if (len>max_payload_bytes || len<0)
{
- fprintf(stderr, "Invalid payload length: %d\n",len[toggle]);
+ fprintf(stderr, "Invalid payload length: %d\n",len);
break;
}
num_read = fread(ch, 1, 4, fin);
if (num_read!=4)
break;
- enc_final_range[toggle] = char_to_int(ch);
- num_read = fread(data[toggle], 1, len[toggle], fin);
- if (num_read!=(size_t)len[toggle])
+ enc_final_range = char_to_int(ch);
+ num_read = fread(data, 1, len, fin);
+ if (num_read!=(size_t)len)
{
fprintf(stderr, "Ran out of input, "
"expecting %d bytes got %d\n",
- len[toggle],(int)num_read);
+ len,(int)num_read);
break;
}
} else {
@@ -679,6 +872,15 @@ int main(int argc, char *argv[])
opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(mode_list[curr_mode][3]));
frame_size = mode_list[curr_mode][2];
}
+#ifdef ENABLE_OSCE_TRAINING_DATA
+ if (silk_random_switching)
+ {
+ silk_frame_counter += 1;
+ if (silk_frame_counter % silk_random_switching == 0) {
+ new_random_setting(enc);
+ }
+ }
+#endif
num_read = fread(fbytes, sizeof(short)*channels, frame_size-remaining, fin);
curr_read = (int)num_read;
tot_in += curr_read;
@@ -696,8 +898,13 @@ int main(int argc, char *argv[])
if (encode_only || decode_only)
stop = 1;
}
- len[toggle] = opus_encode(enc, in, frame_size, data[toggle], max_payload_bytes);
- nb_encoded = opus_packet_get_samples_per_frame(data[toggle], sampling_rate)*opus_packet_get_nb_frames(data[toggle], len[toggle]);
+ len = opus_encode(enc, in, frame_size, data, max_payload_bytes);
+ if (len < 0)
+ {
+ fprintf (stderr, "opus_encode() returned %d\n", len);
+ goto failure;
+ }
+ nb_encoded = opus_packet_get_samples_per_frame(data, sampling_rate)*opus_packet_get_nb_frames(data, len);
remaining = frame_size-nb_encoded;
for(i=0;i<remaining*channels;i++)
in[i] = in[nb_encoded*channels+i];
@@ -716,12 +923,7 @@ int main(int argc, char *argv[])
bitrate_bps = 1000;
opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate_bps));
}
- opus_encoder_ctl(enc, OPUS_GET_FINAL_RANGE(&enc_final_range[toggle]));
- if (len[toggle] < 0)
- {
- fprintf (stderr, "opus_encode() returned %d\n", len[toggle]);
- goto failure;
- }
+ opus_encoder_ctl(enc, OPUS_GET_FINAL_RANGE(&enc_final_range));
curr_mode_count += frame_size;
if (curr_mode_count > mode_switch_time && curr_mode < nb_modes_in_list-1)
{
@@ -731,56 +933,84 @@ int main(int argc, char *argv[])
}
#if 0 /* This is for testing the padding code, do not enable by default */
- if (len[toggle]<1275)
+ if (len<1275)
{
- int new_len = len[toggle]+rand()%(max_payload_bytes-len[toggle]);
- if ((err = opus_packet_pad(data[toggle], len[toggle], new_len)) != OPUS_OK)
+ int new_len = len+rand()%(max_payload_bytes-len);
+ if ((err = opus_packet_pad(data, len, new_len)) != OPUS_OK)
{
fprintf(stderr, "padding failed: %s\n", opus_strerror(err));
goto failure;
}
- len[toggle] = new_len;
+ len = new_len;
}
#endif
if (encode_only)
{
unsigned char int_field[4];
- int_to_char(len[toggle], int_field);
+ int_to_char(len, int_field);
if (fwrite(int_field, 1, 4, fout) != 4) {
fprintf(stderr, "Error writing.\n");
goto failure;
}
- int_to_char(enc_final_range[toggle], int_field);
+ int_to_char(enc_final_range, int_field);
if (fwrite(int_field, 1, 4, fout) != 4) {
fprintf(stderr, "Error writing.\n");
goto failure;
}
- if (fwrite(data[toggle], 1, len[toggle], fout) != (unsigned)len[toggle]) {
+ if (fwrite(data, 1, len, fout) != (unsigned)len) {
fprintf(stderr, "Error writing.\n");
goto failure;
}
tot_samples += nb_encoded;
} else {
- opus_int32 output_samples;
- lost = len[toggle]==0 || (packet_loss_perc>0 && rand()%100 < packet_loss_perc);
+ int fr;
+ int run_decoder;
+ int dred_input=0;
+ int dred_end=0;
+ if (packet_loss_file != NULL) {
+ if ( fscanf(packet_loss_file, "%d", &lost) != 1) {
+ lost = 0;
+ }
+#ifdef ENABLE_LOSSGEN
+ } else if (lossgen_perc >= 0) {
+ lost = sample_loss(&lossgen, lossgen_perc*.01f);
+#endif
+ } else {
+ lost = (packet_loss_perc>0) && (rand()%100 < packet_loss_perc);
+ }
+ if (len == 0) lost = 1;
if (lost)
- opus_decoder_ctl(dec, OPUS_GET_LAST_PACKET_DURATION(&output_samples));
- else
- output_samples = max_frame_size;
- if( count >= use_inbandfec ) {
- /* delay by one packet when using in-band FEC */
- if( use_inbandfec ) {
- if( lost_prev ) {
- /* attempt to decode with in-band FEC from next packet */
- opus_decoder_ctl(dec, OPUS_GET_LAST_PACKET_DURATION(&output_samples));
- output_samples = opus_decode(dec, lost ? NULL : data[toggle], len[toggle], out, output_samples, 1);
- } else {
- /* regular decode */
- output_samples = max_frame_size;
- output_samples = opus_decode(dec, data[1-toggle], len[1-toggle], out, output_samples, 0);
- }
+ {
+ lost_count++;
+ run_decoder = 0;
+ } else {
+ run_decoder= 1;
+ }
+ if (run_decoder)
+ run_decoder += lost_count;
+ if (!lost && lost_count > 0) {
+ opus_int32 output_samples=0;
+ opus_decoder_ctl(dec, OPUS_GET_LAST_PACKET_DURATION(&output_samples));
+ dred_input = lost_count*output_samples;
+ /* Only decode the amount we need to fill in the gap. */
+ ret = opus_dred_parse(dred_dec, dred, data, len, IMIN(48000, IMAX(0, dred_input)), sampling_rate, &dred_end, 0);
+ dred_input = ret > 0 ? ret : 0;
+ }
+ /* FIXME: Figure out how to trigger the decoder when the last packet of the file is lost. */
+ for (fr=0;fr<run_decoder;fr++) {
+ opus_int32 output_samples=0;
+ if (fr == lost_count-1 && opus_packet_has_lbrr(data, len)) {
+ opus_decoder_ctl(dec, OPUS_GET_LAST_PACKET_DURATION(&output_samples));
+ output_samples = opus_decode(dec, data, len, out, output_samples, 1);
+ } else if (fr < lost_count) {
+ opus_decoder_ctl(dec, OPUS_GET_LAST_PACKET_DURATION(&output_samples));
+ if (dred_input > 0)
+ output_samples = opus_decoder_dred_decode(dec, dred, (lost_count-fr)*output_samples, out, output_samples);
+ else
+ output_samples = opus_decode(dec, NULL, 0, out, output_samples, 0);
} else {
- output_samples = opus_decode(dec, lost ? NULL : data[toggle], len[toggle], out, output_samples, 0);
+ output_samples = max_frame_size;
+ output_samples = opus_decode(dec, data, len, out, output_samples, 0);
}
if (output_samples>0)
{
@@ -817,24 +1047,26 @@ int main(int argc, char *argv[])
if (!encode_only)
opus_decoder_ctl(dec, OPUS_GET_FINAL_RANGE(&dec_final_range));
/* compare final range encoder rng values of encoder and decoder */
- if( enc_final_range[toggle^use_inbandfec]!=0 && !encode_only
+ if( enc_final_range!=0 && !encode_only
&& !lost && !lost_prev
- && dec_final_range != enc_final_range[toggle^use_inbandfec] ) {
+ && dec_final_range != enc_final_range ) {
fprintf (stderr, "Error: Range coder state mismatch "
"between encoder and decoder "
"in frame %ld: 0x%8lx vs 0x%8lx\n",
(long)count,
- (unsigned long)enc_final_range[toggle^use_inbandfec],
+ (unsigned long)enc_final_range,
(unsigned long)dec_final_range);
goto failure;
}
lost_prev = lost;
+ if (!lost)
+ lost_count = 0;
if( count >= use_inbandfec ) {
/* count bits */
- bits += len[toggle]*8;
- bits_max = ( len[toggle]*8 > bits_max ) ? len[toggle]*8 : bits_max;
- bits2 += len[toggle]*len[toggle]*64;
+ bits += len*8;
+ bits_max = ( len*8 > bits_max ) ? len*8 : bits_max;
+ bits2 += len*len*64;
if (!decode_only)
{
nrg = 0.0;
@@ -843,13 +1075,12 @@ int main(int argc, char *argv[])
}
nrg /= frame_size * channels;
if( nrg > 1e5 ) {
- bits_act += len[toggle]*8;
+ bits_act += len*8;
count_act++;
}
}
}
count++;
- toggle = (toggle + use_inbandfec) & 1;
}
if(decode_only && count > 0)
@@ -879,8 +1110,9 @@ int main(int argc, char *argv[])
failure:
opus_encoder_destroy(enc);
opus_decoder_destroy(dec);
- free(data[0]);
- free(data[1]);
+ opus_dred_free(dred);
+ opus_dred_decoder_destroy(dred_dec);
+ free(data);
if (fin)
fclose(fin);
if (fout)
@@ -888,5 +1120,8 @@ failure:
free(in);
free(out);
free(fbytes);
+#ifdef USE_WEIGHTS_FILE
+ free_blob(blob_data, blob_len);
+#endif
return ret;
}
diff --git a/src/opus_encoder.c b/src/opus_encoder.c
index 8c8db5a5..d18d582f 100644
--- a/src/opus_encoder.c
+++ b/src/opus_encoder.c
@@ -45,11 +45,19 @@
#include "analysis.h"
#include "mathops.h"
#include "tuning_parameters.h"
+
+#ifdef ENABLE_DRED
+#include "dred_coding.h"
+#endif
+
#ifdef FIXED_POINT
#include "fixed/structs_FIX.h"
#else
#include "float/structs_FLP.h"
#endif
+#ifdef ENABLE_OSCE_TRAINING_DATA
+#include <stdio.h>
+#endif
#define MAX_ENCODER_BUFFER 480
@@ -67,6 +75,9 @@ struct OpusEncoder {
int celt_enc_offset;
int silk_enc_offset;
silk_EncControlStruct silk_mode;
+#ifdef ENABLE_DRED
+ DREDEnc dred_encoder;
+#endif
int application;
int channels;
int delay_compensation;
@@ -116,6 +127,14 @@ struct OpusEncoder {
int nb_no_activity_ms_Q1;
opus_val32 peak_signal_energy;
#endif
+#ifdef ENABLE_DRED
+ int dred_duration;
+ int dred_q0;
+ int dred_dQ;
+ int dred_qmax;
+ int dred_target_chunks;
+ unsigned char activity_mem[DRED_MAX_FRAMES*4]; /* 2.5ms resolution*/
+#endif
int nonfinal_frame; /* current frame is not the final in a packet */
opus_uint32 rangeFinal;
};
@@ -224,6 +243,7 @@ int opus_encoder_init(OpusEncoder* st, opus_int32 Fs, int channels, int applicat
st->silk_mode.packetLossPercentage = 0;
st->silk_mode.complexity = 9;
st->silk_mode.useInBandFEC = 0;
+ st->silk_mode.useDRED = 0;
st->silk_mode.useDTX = 0;
st->silk_mode.useCBR = 0;
st->silk_mode.reducedDependency = 0;
@@ -236,6 +256,11 @@ int opus_encoder_init(OpusEncoder* st, opus_int32 Fs, int channels, int applicat
celt_encoder_ctl(celt_enc, CELT_SET_SIGNALLING(0));
celt_encoder_ctl(celt_enc, OPUS_SET_COMPLEXITY(st->silk_mode.complexity));
+#ifdef ENABLE_DRED
+ /* Initialize DRED Encoder */
+ dred_encoder_init( &st->dred_encoder, Fs, channels );
+#endif
+
st->use_vbr = 1;
/* Makes constrained VBR the default (safer for real-time use) */
st->vbr_constraint = 1;
@@ -544,6 +569,73 @@ OpusEncoder *opus_encoder_create(opus_int32 Fs, int channels, int application, i
return st;
}
+#ifdef ENABLE_DRED
+
+static const float dred_bits_table[16] = {73.2f, 68.1f, 62.5f, 57.0f, 51.5f, 45.7f, 39.9f, 32.4f, 26.4f, 20.4f, 16.3f, 13.f, 9.3f, 8.2f, 7.2f, 6.4f};
+static int estimate_dred_bitrate(int q0, int dQ, int qmax, int duration, opus_int32 target_bits, int *target_chunks) {
+ int dred_chunks;
+ int i;
+ float bits;
+ /* Signaling DRED costs 3 bytes. */
+ bits = 8*(3+DRED_EXPERIMENTAL_BYTES);
+ /* Approximation for the size of the IS. */
+ bits += 50.f+dred_bits_table[q0];
+ dred_chunks = IMIN((duration+5)/4, DRED_NUM_REDUNDANCY_FRAMES/2);
+ if (target_chunks != NULL) *target_chunks = 0;
+ for (i=0;i<dred_chunks;i++) {
+ int q = compute_quantizer(q0, dQ, qmax, i);
+ bits += dred_bits_table[q];
+ if (target_chunks != NULL && bits < target_bits) *target_chunks = i+1;
+ }
+ return (int)floor(.5f+bits);
+}
+
+static opus_int32 compute_dred_bitrate(OpusEncoder *st, opus_int32 bitrate_bps, int frame_size)
+{
+ float dred_frac;
+ int bitrate_offset;
+ opus_int32 dred_bitrate;
+ opus_int32 target_dred_bitrate;
+ int target_chunks;
+ opus_int32 max_dred_bits;
+ int q0, dQ, qmax;
+ if (st->silk_mode.useInBandFEC) {
+ dred_frac = MIN16(.7f, 3.f*st->silk_mode.packetLossPercentage/100.f);
+ bitrate_offset = 20000;
+ } else {
+ if (st->silk_mode.packetLossPercentage > 5) {
+ dred_frac = MIN16(.8f, .55f + st->silk_mode.packetLossPercentage/100.f);
+ } else {
+ dred_frac = 12*st->silk_mode.packetLossPercentage/100.f;
+ }
+ bitrate_offset = 12000;
+ }
+ /* Account for the fact that longer packets require less redundancy. */
+ dred_frac = dred_frac/(dred_frac + (1-dred_frac)*(frame_size*50.f)/st->Fs);
+ /* Approximate fit based on a few experiments. Could probably be improved. */
+ q0 = IMIN(15, IMAX(4, 51 - 3*EC_ILOG(IMAX(1, bitrate_bps-bitrate_offset))));
+ dQ = bitrate_bps-bitrate_offset > 36000 ? 3 : 5;
+ qmax = 15;
+ target_dred_bitrate = IMAX(0, (int)(dred_frac*(bitrate_bps-bitrate_offset)));
+ if (st->dred_duration > 0) {
+ opus_int32 target_bits = target_dred_bitrate*frame_size/st->Fs;
+ max_dred_bits = estimate_dred_bitrate(q0, dQ, qmax, st->dred_duration, target_bits, &target_chunks);
+ } else {
+ max_dred_bits = 0;
+ target_chunks=0;
+ }
+ dred_bitrate = IMIN(target_dred_bitrate, max_dred_bits*st->Fs/frame_size);
+ /* If we can't afford enough bits, don't bother with DRED at all. */
+ if (target_chunks < 2)
+ dred_bitrate = 0;
+ st->dred_q0 = q0;
+ st->dred_dQ = dQ;
+ st->dred_qmax = qmax;
+ st->dred_target_chunks = target_chunks;
+ return dred_bitrate;
+}
+#endif
+
static opus_int32 user_bitrate_to_bitrate(OpusEncoder *st, int frame_size, int max_data_bytes)
{
if(!frame_size)frame_size=st->Fs/400;
@@ -872,7 +964,7 @@ static opus_val32 compute_frame_energy(const opus_val16 *pcm, int frame_size, in
/* Compute the right shift required in the MAC to avoid an overflow */
max_shift = celt_ilog2(len);
- shift = IMAX(0, (celt_ilog2(sample_max) << 1) + max_shift - 28);
+ shift = IMAX(0, (celt_ilog2(1+sample_max) << 1) + max_shift - 28);
/* Compute the energy */
for (i=0; i<len; i++)
@@ -922,105 +1014,6 @@ static int decide_dtx_mode(opus_int activity, /* indicates if this fr
#endif
-static opus_int32 encode_multiframe_packet(OpusEncoder *st,
- const opus_val16 *pcm,
- int nb_frames,
- int frame_size,
- unsigned char *data,
- opus_int32 out_data_bytes,
- int to_celt,
- int lsb_depth,
- int float_api)
-{
- int i;
- int ret = 0;
- VARDECL(unsigned char, tmp_data);
- int bak_mode, bak_bandwidth, bak_channels, bak_to_mono;
- VARDECL(OpusRepacketizer, rp);
- int max_header_bytes;
- opus_int32 bytes_per_frame;
- opus_int32 cbr_bytes;
- opus_int32 repacketize_len;
- int tmp_len;
- ALLOC_STACK;
-
- /* Worst cases:
- * 2 frames: Code 2 with different compressed sizes
- * >2 frames: Code 3 VBR */
- max_header_bytes = nb_frames == 2 ? 3 : (2+(nb_frames-1)*2);
-
- if (st->use_vbr || st->user_bitrate_bps==OPUS_BITRATE_MAX)
- repacketize_len = out_data_bytes;
- else {
- cbr_bytes = 3*st->bitrate_bps/(3*8*st->Fs/(frame_size*nb_frames));
- repacketize_len = IMIN(cbr_bytes, out_data_bytes);
- }
- bytes_per_frame = IMIN(1276, 1+(repacketize_len-max_header_bytes)/nb_frames);
-
- ALLOC(tmp_data, nb_frames*bytes_per_frame, unsigned char);
- ALLOC(rp, 1, OpusRepacketizer);
- opus_repacketizer_init(rp);
-
- bak_mode = st->user_forced_mode;
- bak_bandwidth = st->user_bandwidth;
- bak_channels = st->force_channels;
-
- st->user_forced_mode = st->mode;
- st->user_bandwidth = st->bandwidth;
- st->force_channels = st->stream_channels;
-
- bak_to_mono = st->silk_mode.toMono;
- if (bak_to_mono)
- st->force_channels = 1;
- else
- st->prev_channels = st->stream_channels;
-
- for (i=0;i<nb_frames;i++)
- {
- st->silk_mode.toMono = 0;
- st->nonfinal_frame = i<(nb_frames-1);
-
- /* When switching from SILK/Hybrid to CELT, only ask for a switch at the last frame */
- if (to_celt && i==nb_frames-1)
- st->user_forced_mode = MODE_CELT_ONLY;
-
- tmp_len = opus_encode_native(st, pcm+i*(st->channels*frame_size), frame_size,
- tmp_data+i*bytes_per_frame, bytes_per_frame, lsb_depth, NULL, 0, 0, 0, 0,
- NULL, float_api);
-
- if (tmp_len<0)
- {
- RESTORE_STACK;
- return OPUS_INTERNAL_ERROR;
- }
-
- ret = opus_repacketizer_cat(rp, tmp_data+i*bytes_per_frame, tmp_len);
-
- if (ret<0)
- {
- RESTORE_STACK;
- return OPUS_INTERNAL_ERROR;
- }
- }
-
- ret = opus_repacketizer_out_range_impl(rp, 0, nb_frames, data, repacketize_len, 0, !st->use_vbr);
-
- if (ret<0)
- {
- RESTORE_STACK;
- return OPUS_INTERNAL_ERROR;
- }
-
- /* Discard configs that were forced locally for the purpose of repacketization */
- st->user_forced_mode = bak_mode;
- st->user_bandwidth = bak_bandwidth;
- st->force_channels = bak_channels;
- st->silk_mode.toMono = bak_to_mono;
-
- RESTORE_STACK;
- return ret;
-}
-
static int compute_redundancy_bytes(opus_int32 max_data_bytes, opus_int32 bitrate_bps, int frame_rate, int channels)
{
int redundancy_bytes_cap;
@@ -1049,6 +1042,18 @@ static int compute_redundancy_bytes(opus_int32 max_data_bytes, opus_int32 bitrat
return redundancy_bytes;
}
+static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size,
+ unsigned char *data, opus_int32 max_data_bytes,
+ int float_api, int first_frame,
+#ifdef ENABLE_DRED
+ opus_int32 dred_bitrate_bps,
+#endif
+#ifndef DISABLE_FLOAT_API
+ AnalysisInfo *analysis_info, int is_silence,
+#endif
+ int redundancy, int celt_to_silk, int prefill,
+ opus_int32 equiv_rate, int to_celt);
+
opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size,
unsigned char *data, opus_int32 out_data_bytes, int lsb_depth,
const void *analysis_pcm, opus_int32 analysis_size, int c1, int c2,
@@ -1058,28 +1063,17 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
CELTEncoder *celt_enc;
int i;
int ret=0;
- opus_int32 nBytes;
- ec_enc enc;
- int bytes_target;
int prefill=0;
- int start_band = 0;
int redundancy = 0;
- int redundancy_bytes = 0; /* Number of bytes to use for redundancy frame */
int celt_to_silk = 0;
- VARDECL(opus_val16, pcm_buf);
- int nb_compr_bytes;
int to_celt = 0;
- opus_uint32 redundant_rng = 0;
- int cutoff_Hz, hp_freq_smth1;
int voice_est; /* Probability of voice in Q7 */
opus_int32 equiv_rate;
- int delay_compensation;
int frame_rate;
opus_int32 max_rate; /* Max bitrate we're allowed to use */
int curr_bandwidth;
- opus_val16 HB_gain;
opus_int32 max_data_bytes; /* Max number of bytes we're allowed to use */
- int total_buffer;
+ opus_int32 cbr_bytes=-1;
opus_val16 stereo_width;
const CELTMode *celt_mode;
#ifndef DISABLE_FLOAT_API
@@ -1088,10 +1082,9 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
int analysis_read_subframe_bak=-1;
int is_silence = 0;
#endif
- opus_int activity = VAD_NO_DECISION;
-
- VARDECL(opus_val16, tmp_prefill);
-
+#ifdef ENABLE_DRED
+ opus_int32 dred_bitrate_bps;
+#endif
ALLOC_STACK;
max_data_bytes = IMIN(1276, out_data_bytes);
@@ -1112,10 +1105,6 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
silk_enc = (char*)st+st->silk_enc_offset;
celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset);
- if (st->application == OPUS_APPLICATION_RESTRICTED_LOWDELAY)
- delay_compensation = 0;
- else
- delay_compensation = st->delay_compensation;
lsb_depth = IMIN(lsb_depth, st->lsb_depth);
@@ -1157,20 +1146,6 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
if (!is_silence)
st->voice_ratio = -1;
- if (is_silence)
- {
- activity = !is_silence;
- } else if (analysis_info.valid)
- {
- activity = analysis_info.activity_probability >= DTX_ACTIVITY_THRESHOLD;
- if (!activity)
- {
- /* Mark as active if this noise frame is sufficiently loud */
- opus_val32 noise_energy = compute_frame_energy(pcm, frame_size, st->channels, st->arch);
- activity = st->peak_signal_energy < (PSEUDO_SNR_THRESHOLD * noise_energy);
- }
- }
-
st->detected_bandwidth = 0;
if (analysis_info.valid)
{
@@ -1207,21 +1182,24 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
stereo_width = compute_stereo_width(pcm, frame_size, st->Fs, &st->width_mem);
else
stereo_width = 0;
- total_buffer = delay_compensation;
st->bitrate_bps = user_bitrate_to_bitrate(st, frame_size, max_data_bytes);
frame_rate = st->Fs/frame_size;
if (!st->use_vbr)
{
- int cbrBytes;
/* Multiply by 12 to make sure the division is exact. */
int frame_rate12 = 12*st->Fs/frame_size;
/* We need to make sure that "int" values always fit in 16 bits. */
- cbrBytes = IMIN( (12*st->bitrate_bps/8 + frame_rate12/2)/frame_rate12, max_data_bytes);
- st->bitrate_bps = cbrBytes*(opus_int32)frame_rate12*8/12;
+ cbr_bytes = IMIN( (12*st->bitrate_bps/8 + frame_rate12/2)/frame_rate12, max_data_bytes);
+ st->bitrate_bps = cbr_bytes*(opus_int32)frame_rate12*8/12;
/* Make sure we provide at least one byte to avoid failing. */
- max_data_bytes = IMAX(1, cbrBytes);
+ max_data_bytes = IMAX(1, cbr_bytes);
}
+#ifdef ENABLE_DRED
+ /* Allocate some of the bits to DRED if needed. */
+ dred_bitrate_bps = compute_dred_bitrate(st, st->bitrate_bps, frame_size);
+ st->bitrate_bps -= dred_bitrate_bps;
+#endif
if (max_data_bytes<3 || st->bitrate_bps < 3*frame_rate*8
|| (frame_rate<50 && (max_data_bytes*frame_rate<300 || st->bitrate_bps < 2400)))
{
@@ -1575,6 +1553,15 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
{
int enc_frame_size;
int nb_frames;
+ VARDECL(unsigned char, tmp_data);
+ VARDECL(OpusRepacketizer, rp);
+ int max_header_bytes;
+ opus_int32 repacketize_len;
+ opus_int32 max_len_sum;
+ opus_int32 tot_size=0;
+ unsigned char *curr_data;
+ int tmp_len;
+ int dtx_count = 0;
if (st->mode == MODE_SILK_ONLY)
{
@@ -1593,17 +1580,186 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
#ifndef DISABLE_FLOAT_API
if (analysis_read_pos_bak!= -1)
{
+ /* Reset analysis position to the beginning of the first frame so we
+ can use it one frame at a time. */
st->analysis.read_pos = analysis_read_pos_bak;
st->analysis.read_subframe = analysis_read_subframe_bak;
}
#endif
- ret = encode_multiframe_packet(st, pcm, nb_frames, enc_frame_size, data,
- out_data_bytes, to_celt, lsb_depth, float_api);
+ /* Worst cases:
+ * 2 frames: Code 2 with different compressed sizes
+ * >2 frames: Code 3 VBR */
+ max_header_bytes = nb_frames == 2 ? 3 : (2+(nb_frames-1)*2);
+
+ if (st->use_vbr || st->user_bitrate_bps==OPUS_BITRATE_MAX)
+ repacketize_len = out_data_bytes;
+ else {
+ celt_assert(cbr_bytes>=0);
+ repacketize_len = IMIN(cbr_bytes, out_data_bytes);
+ }
+ max_len_sum = nb_frames + repacketize_len - max_header_bytes;
+
+ ALLOC(tmp_data, max_len_sum, unsigned char);
+ curr_data = tmp_data;
+ ALLOC(rp, 1, OpusRepacketizer);
+ opus_repacketizer_init(rp);
+
+ int bak_to_mono = st->silk_mode.toMono;
+ if (bak_to_mono)
+ st->force_channels = 1;
+ else
+ st->prev_channels = st->stream_channels;
+
+ for (i=0;i<nb_frames;i++)
+ {
+ int first_frame;
+ int frame_to_celt;
+ int frame_redundancy;
+ opus_int32 curr_max;
+ /* Attempt DRED encoding until we have a non-DTX frame. In case of DTX refresh,
+ that allows for DRED not to be in the first frame. */
+ first_frame = (i == 0) || (i == dtx_count);
+ st->silk_mode.toMono = 0;
+ st->nonfinal_frame = i<(nb_frames-1);
+
+ /* When switching from SILK/Hybrid to CELT, only ask for a switch at the last frame */
+ frame_to_celt = to_celt && i==nb_frames-1;
+ frame_redundancy = redundancy && (frame_to_celt || (!to_celt && i==0));
+
+ curr_max = IMIN(3*st->bitrate_bps/(3*8*st->Fs/enc_frame_size), max_len_sum/nb_frames);
+#ifdef ENABLE_DRED
+ curr_max = IMIN(curr_max, (max_len_sum-3*dred_bitrate_bps/(3*8*st->Fs/frame_size))/nb_frames);
+ if (first_frame) curr_max += 3*dred_bitrate_bps/(3*8*st->Fs/frame_size);
+#endif
+ curr_max = IMIN(max_len_sum-tot_size, curr_max);
+#ifndef DISABLE_FLOAT_API
+ if (analysis_read_pos_bak != -1) {
+ is_silence = is_digital_silence(pcm, frame_size, st->channels, lsb_depth);
+ /* Get analysis for current frame. */
+ tonality_get_info(&st->analysis, &analysis_info, enc_frame_size);
+ }
+#endif
+
+ tmp_len = opus_encode_frame_native(st, pcm+i*(st->channels*enc_frame_size), enc_frame_size, curr_data, curr_max, float_api, first_frame,
+#ifdef ENABLE_DRED
+ dred_bitrate_bps,
+#endif
+#ifndef DISABLE_FLOAT_API
+ &analysis_info,
+ is_silence,
+#endif
+ frame_redundancy, celt_to_silk, prefill,
+ equiv_rate, frame_to_celt
+ );
+ if (tmp_len<0)
+ {
+ RESTORE_STACK;
+ return OPUS_INTERNAL_ERROR;
+ } else if (tmp_len==1) {
+ dtx_count++;
+ }
+ ret = opus_repacketizer_cat(rp, curr_data, tmp_len);
+
+ if (ret<0)
+ {
+ RESTORE_STACK;
+ return OPUS_INTERNAL_ERROR;
+ }
+ tot_size += tmp_len;
+ curr_data += tmp_len;
+ }
+ ret = opus_repacketizer_out_range_impl(rp, 0, nb_frames, data, repacketize_len, 0, !st->use_vbr && (dtx_count != nb_frames), NULL, 0);
+ if (ret<0)
+ {
+ ret = OPUS_INTERNAL_ERROR;
+ }
+ st->silk_mode.toMono = bak_to_mono;
RESTORE_STACK;
return ret;
+ } else {
+ ret = opus_encode_frame_native(st, pcm, frame_size, data, max_data_bytes, float_api, 1,
+#ifdef ENABLE_DRED
+ dred_bitrate_bps,
+#endif
+#ifndef DISABLE_FLOAT_API
+ &analysis_info,
+ is_silence,
+#endif
+ redundancy, celt_to_silk, prefill,
+ equiv_rate, to_celt
+ );
+ RESTORE_STACK;
+ return ret;
}
+}
+
+static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size,
+ unsigned char *data, opus_int32 max_data_bytes,
+ int float_api, int first_frame,
+#ifdef ENABLE_DRED
+ opus_int32 dred_bitrate_bps,
+#endif
+#ifndef DISABLE_FLOAT_API
+ AnalysisInfo *analysis_info, int is_silence,
+#endif
+ int redundancy, int celt_to_silk, int prefill,
+ opus_int32 equiv_rate, int to_celt)
+{
+ void *silk_enc;
+ CELTEncoder *celt_enc;
+ const CELTMode *celt_mode;
+ int i;
+ int ret=0;
+ opus_int32 nBytes;
+ ec_enc enc;
+ int bytes_target;
+ int start_band = 0;
+ int redundancy_bytes = 0; /* Number of bytes to use for redundancy frame */
+ int nb_compr_bytes;
+ opus_uint32 redundant_rng = 0;
+ int cutoff_Hz;
+ int hp_freq_smth1;
+ opus_val16 HB_gain;
+ int apply_padding;
+ int frame_rate;
+ int curr_bandwidth;
+ int delay_compensation;
+ int total_buffer;
+ opus_int activity = VAD_NO_DECISION;
+ VARDECL(opus_val16, pcm_buf);
+ VARDECL(opus_val16, tmp_prefill);
+ SAVE_STACK;
+
+ st->rangeFinal = 0;
+ silk_enc = (char*)st+st->silk_enc_offset;
+ celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset);
+ celt_encoder_ctl(celt_enc, CELT_GET_MODE(&celt_mode));
+ curr_bandwidth = st->bandwidth;
+ if (st->application == OPUS_APPLICATION_RESTRICTED_LOWDELAY)
+ delay_compensation = 0;
+ else
+ delay_compensation = st->delay_compensation;
+ total_buffer = delay_compensation;
+
+ frame_rate = st->Fs/frame_size;
+
+#ifndef DISABLE_FLOAT_API
+ if (is_silence)
+ {
+ activity = !is_silence;
+ } else if (analysis_info->valid)
+ {
+ activity = analysis_info->activity_probability >= DTX_ACTIVITY_THRESHOLD;
+ if (!activity)
+ {
+ /* Mark as active if this noise frame is sufficiently loud */
+ opus_val32 noise_energy = compute_frame_energy(pcm, frame_size, st->channels, st->arch);
+ activity = st->peak_signal_energy < (PSEUDO_SNR_THRESHOLD * noise_energy);
+ }
+ }
+#endif
/* For the first frame at a new SILK bandwidth */
if (st->silk_bw_switch)
@@ -1611,7 +1767,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
redundancy = 1;
celt_to_silk = 1;
st->silk_bw_switch = 0;
- /* Do a prefill without reseting the sampling rate control. */
+ /* Do a prefill without resetting the sampling rate control. */
prefill=2;
}
@@ -1651,6 +1807,25 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
if (st->application == OPUS_APPLICATION_VOIP)
{
hp_cutoff(pcm, cutoff_Hz, &pcm_buf[total_buffer*st->channels], st->hp_mem, frame_size, st->channels, st->Fs, st->arch);
+
+#ifdef ENABLE_OSCE_TRAINING_DATA
+ /* write out high pass filtered clean signal*/
+ static FILE *fout =NULL;
+ if (fout == NULL)
+ {
+ fout = fopen("clean_hp.s16", "wb");
+ }
+
+ {
+ int idx;
+ opus_int16 tmp;
+ for (idx = 0; idx < frame_size; idx++)
+ {
+ tmp = (opus_int16) (32768 * pcm_buf[total_buffer + idx] + 0.5f);
+ fwrite(&tmp, sizeof(tmp), 1, fout);
+ }
+ }
+#endif
} else {
dc_reject(pcm, 3, &pcm_buf[total_buffer*st->channels], st->hp_mem, frame_size, st->channels, st->Fs);
}
@@ -1667,8 +1842,24 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
st->hp_mem[0] = st->hp_mem[1] = st->hp_mem[2] = st->hp_mem[3] = 0;
}
}
+#else
+ (void)float_api;
#endif
+#ifdef ENABLE_DRED
+ if ( st->dred_duration > 0 && st->dred_encoder.loaded ) {
+ int frame_size_400Hz;
+ /* DRED Encoder */
+ dred_compute_latents( &st->dred_encoder, &pcm_buf[total_buffer*st->channels], frame_size, total_buffer, st->arch );
+ frame_size_400Hz = frame_size*400/st->Fs;
+ OPUS_MOVE(&st->activity_mem[frame_size_400Hz], st->activity_mem, 4*DRED_MAX_FRAMES-frame_size_400Hz);
+ for (i=0;i<frame_size_400Hz;i++)
+ st->activity_mem[i] = activity;
+ } else {
+ st->dred_encoder.latents_buffer_fill = 0;
+ OPUS_CLEAR(st->activity_mem, DRED_MAX_FRAMES);
+ }
+#endif
/* SILK processing */
HB_gain = Q15ONE;
@@ -1763,7 +1954,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
st->silk_mode.maxInternalSampleRate = 16000;
if (st->mode == MODE_SILK_ONLY)
{
- opus_int32 effective_max_rate = max_rate;
+ opus_int32 effective_max_rate = frame_rate*max_data_bytes*8;
if (frame_rate > 50)
effective_max_rate = effective_max_rate*2/3;
if (effective_max_rate < 8000)
@@ -1793,9 +1984,19 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
}
if (st->silk_mode.useCBR)
{
+ /* When we're in CBR mode, but we have non-SILK data to encode, switch SILK to VBR with cap to
+ save on complexity. Any variations will be absorbed by CELT and/or DRED and we can still
+ produce a constant bitrate without wasting bits. */
+#ifdef ENABLE_DRED
+ if (st->mode == MODE_HYBRID || dred_bitrate_bps > 0)
+#else
if (st->mode == MODE_HYBRID)
+#endif
{
- st->silk_mode.maxBits = IMIN(st->silk_mode.maxBits, st->silk_mode.bitRate * frame_size / st->Fs);
+ /* Allow SILK to steal up to 25% of the remaining bits */
+ opus_int16 other_bits = IMAX(0, st->silk_mode.maxBits - st->silk_mode.bitRate * frame_size / st->Fs);
+ st->silk_mode.maxBits = IMAX(0, st->silk_mode.maxBits - other_bits*3/4);
+ st->silk_mode.useCBR = 0;
}
} else {
/* Constrained VBR. */
@@ -1908,26 +2109,10 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
if (st->mode != MODE_SILK_ONLY)
{
opus_val32 celt_pred=2;
- celt_encoder_ctl(celt_enc, OPUS_SET_VBR(0));
/* We may still decide to disable prediction later */
if (st->silk_mode.reducedDependency)
celt_pred = 0;
celt_encoder_ctl(celt_enc, CELT_SET_PREDICTION(celt_pred));
-
- if (st->mode == MODE_HYBRID)
- {
- if( st->use_vbr ) {
- celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps-st->silk_mode.bitRate));
- celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(0));
- }
- } else {
- if (st->use_vbr)
- {
- celt_encoder_ctl(celt_enc, OPUS_SET_VBR(1));
- celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(st->vbr_constraint));
- celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps));
- }
- }
}
ALLOC(tmp_prefill, st->channels*st->Fs/400, opus_val16);
@@ -2021,13 +2206,27 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
ec_enc_done(&enc);
nb_compr_bytes = ret;
} else {
- nb_compr_bytes = (max_data_bytes-1)-redundancy_bytes;
- ec_enc_shrink(&enc, nb_compr_bytes);
+ nb_compr_bytes = (max_data_bytes-1)-redundancy_bytes;
+#ifdef ENABLE_DRED
+ if (st->dred_duration > 0)
+ {
+ int max_celt_bytes;
+ opus_int32 dred_bytes = dred_bitrate_bps/(frame_rate*8);
+ /* Allow CELT to steal up to 25% of the remaining bits. */
+ max_celt_bytes = nb_compr_bytes - dred_bytes*3/4;
+ /* But try to give CELT at least 5 bytes to prevent a mismatch with
+ the redundancy signaling. */
+ max_celt_bytes = IMAX((ec_tell(&enc)+7)/8 + 5, max_celt_bytes);
+ /* Subject to the original max. */
+ nb_compr_bytes = IMIN(nb_compr_bytes, max_celt_bytes);
+ }
+#endif
+ ec_enc_shrink(&enc, nb_compr_bytes);
}
#ifndef DISABLE_FLOAT_API
if (redundancy || st->mode != MODE_SILK_ONLY)
- celt_encoder_ctl(celt_enc, CELT_SET_ANALYSIS(&analysis_info));
+ celt_encoder_ctl(celt_enc, CELT_SET_ANALYSIS(analysis_info));
#endif
if (st->mode == MODE_HYBRID) {
SILKInfo info;
@@ -2057,6 +2256,34 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
if (st->mode != MODE_SILK_ONLY)
{
+ celt_encoder_ctl(celt_enc, OPUS_SET_VBR(st->use_vbr));
+ if (st->mode == MODE_HYBRID)
+ {
+ if( st->use_vbr ) {
+ celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps-st->silk_mode.bitRate));
+ celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(0));
+ }
+ } else {
+ if (st->use_vbr)
+ {
+ celt_encoder_ctl(celt_enc, OPUS_SET_VBR(1));
+ celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(st->vbr_constraint));
+ celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps));
+ }
+ }
+#ifdef ENABLE_DRED
+ /* When Using DRED CBR, we can actually make the CELT part VBR and have DRED pick up the slack. */
+ if (!st->use_vbr && st->dred_duration > 0)
+ {
+ opus_int32 celt_bitrate = st->bitrate_bps;
+ celt_encoder_ctl(celt_enc, OPUS_SET_VBR(1));
+ celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(0));
+ if (st->mode == MODE_HYBRID) {
+ celt_bitrate -= st->silk_mode.bitRate;
+ }
+ celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(celt_bitrate));
+ }
+#endif
if (st->mode != st->prev_mode && st->prev_mode > 0)
{
unsigned char dummy[2];
@@ -2069,10 +2296,6 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
/* If false, we already busted the budget and we'll end up with a "PLC frame" */
if (ec_tell(&enc) <= 8*nb_compr_bytes)
{
- /* Set the bitrate again if it was overridden in the redundancy code above*/
- if (redundancy && celt_to_silk && st->mode==MODE_HYBRID && st->use_vbr)
- celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps-st->silk_mode.bitRate));
- celt_encoder_ctl(celt_enc, OPUS_SET_VBR(st->use_vbr));
ret = celt_encode_with_ec(celt_enc, pcm_buf, frame_size, NULL, nb_compr_bytes, &enc);
if (ret < 0)
{
@@ -2080,10 +2303,10 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
return OPUS_INTERNAL_ERROR;
}
/* Put CELT->SILK redundancy data in the right place. */
- if (redundancy && celt_to_silk && st->mode==MODE_HYBRID && st->use_vbr)
+ if (redundancy && celt_to_silk && st->mode==MODE_HYBRID && nb_compr_bytes != ret)
{
OPUS_MOVE(data+ret, data+nb_compr_bytes, redundancy_bytes);
- nb_compr_bytes = nb_compr_bytes+redundancy_bytes;
+ nb_compr_bytes = ret+redundancy_bytes;
}
}
}
@@ -2140,7 +2363,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
/* DTX decision */
#ifndef DISABLE_FLOAT_API
- if (st->use_dtx && (analysis_info.valid || is_silence))
+ if (st->use_dtx && (analysis_info->valid || is_silence))
{
if (decide_dtx_mode(activity, &st->nb_no_activity_ms_Q1, 2*1000*frame_size/st->Fs))
{
@@ -2178,7 +2401,51 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
}
/* Count ToC and redundancy */
ret += 1+redundancy_bytes;
- if (!st->use_vbr)
+ apply_padding = !st->use_vbr;
+#ifdef ENABLE_DRED
+ if (st->dred_duration > 0 && st->dred_encoder.loaded && first_frame) {
+ opus_extension_data extension;
+ unsigned char buf[DRED_MAX_DATA_SIZE];
+ int dred_chunks;
+ int dred_bytes_left;
+ dred_chunks = IMIN((st->dred_duration+5)/4, DRED_NUM_REDUNDANCY_FRAMES/2);
+ if (st->use_vbr) dred_chunks = IMIN(dred_chunks, st->dred_target_chunks);
+ /* Remaining space for DRED, accounting for cost the 3 extra bytes for code 3, padding length, and extension number. */
+ dred_bytes_left = IMIN(DRED_MAX_DATA_SIZE, max_data_bytes-ret-3);
+ /* Account for the extra bytes required to signal large padding length. */
+ dred_bytes_left -= (dred_bytes_left+1+DRED_EXPERIMENTAL_BYTES)/255;
+ /* Check whether we actually have something to encode. */
+ if (dred_chunks >= 1 && dred_bytes_left >= DRED_MIN_BYTES+DRED_EXPERIMENTAL_BYTES) {
+ int dred_bytes;
+#ifdef DRED_EXPERIMENTAL_VERSION
+ /* Add temporary extension type and version.
+ These bytes will be removed once extension is finalized. */
+ buf[0] = 'D';
+ buf[1] = DRED_EXPERIMENTAL_VERSION;
+#endif
+ dred_bytes = dred_encode_silk_frame(&st->dred_encoder, buf+DRED_EXPERIMENTAL_BYTES, dred_chunks, dred_bytes_left-DRED_EXPERIMENTAL_BYTES,
+ st->dred_q0, st->dred_dQ, st->dred_qmax, st->activity_mem, st->arch);
+ if (dred_bytes > 0) {
+ dred_bytes += DRED_EXPERIMENTAL_BYTES;
+ celt_assert(dred_bytes <= dred_bytes_left);
+ extension.id = DRED_EXTENSION_ID;
+ extension.frame = 0;
+ extension.data = buf;
+ extension.len = dred_bytes;
+ ret = opus_packet_pad_impl(data, ret, max_data_bytes, !st->use_vbr, &extension, 1);
+ if (ret < 0)
+ {
+ RESTORE_STACK;
+ return OPUS_INTERNAL_ERROR;
+ }
+ apply_padding = 0;
+ }
+ }
+ }
+#else
+ (void)first_frame; /* Avoids a warning about first_frame being unused. */
+#endif
+ if (apply_padding)
{
if (opus_packet_pad(data, ret, max_data_bytes) != OPUS_OK)
{
@@ -2677,6 +2944,29 @@ int opus_encoder_ctl(OpusEncoder *st, int request, ...)
celt_encoder_ctl(celt_enc, OPUS_GET_PHASE_INVERSION_DISABLED(value));
}
break;
+#ifdef ENABLE_DRED
+ case OPUS_SET_DRED_DURATION_REQUEST:
+ {
+ opus_int32 value = va_arg(ap, opus_int32);
+ if(value<0 || value>DRED_MAX_FRAMES)
+ {
+ goto bad_arg;
+ }
+ st->dred_duration = value;
+ st->silk_mode.useDRED = !!value;
+ }
+ break;
+ case OPUS_GET_DRED_DURATION_REQUEST:
+ {
+ opus_int32 *value = va_arg(ap, opus_int32*);
+ if (!value)
+ {
+ goto bad_arg;
+ }
+ *value = st->dred_duration;
+ }
+ break;
+#endif
case OPUS_RESET_STATE:
{
void *silk_enc;
@@ -2692,6 +2982,10 @@ int opus_encoder_ctl(OpusEncoder *st, int request, ...)
celt_encoder_ctl(celt_enc, OPUS_RESET_STATE);
silk_InitEncoder( silk_enc, st->arch, &dummy );
+#ifdef ENABLE_DRED
+ /* Initialize DRED Encoder */
+ dred_encoder_reset( &st->dred_encoder );
+#endif
st->stream_channels = st->channels;
st->hybrid_stereo_width_Q14 = 1 << 14;
st->prev_HB_gain = Q15ONE;
@@ -2752,6 +3046,21 @@ int opus_encoder_ctl(OpusEncoder *st, int request, ...)
}
}
break;
+#ifdef USE_WEIGHTS_FILE
+ case OPUS_SET_DNN_BLOB_REQUEST:
+ {
+ const unsigned char *data = va_arg(ap, const unsigned char *);
+ opus_int32 len = va_arg(ap, opus_int32);
+ if(len<0 || data == NULL)
+ {
+ goto bad_arg;
+ }
+#ifdef ENABLE_DRED
+ ret = dred_encoder_load_model(&st->dred_encoder, data, len);
+#endif
+ }
+ break;
+#endif
case CELT_GET_MODE_REQUEST:
{
const CELTMode ** value = va_arg(ap, const CELTMode**);
diff --git a/src/opus_multistream_decoder.c b/src/opus_multistream_decoder.c
index a2837c35..4ae877a7 100644
--- a/src/opus_multistream_decoder.c
+++ b/src/opus_multistream_decoder.c
@@ -162,7 +162,7 @@ static int opus_multistream_packet_validate(const unsigned char *data,
if (len<=0)
return OPUS_INVALID_PACKET;
count = opus_packet_parse_impl(data, len, s!=nb_streams-1, &toc, NULL,
- size, NULL, &packet_offset);
+ size, NULL, &packet_offset, NULL, NULL);
if (count<0)
return count;
tmp_samples = opus_packet_get_nb_samples(data, packet_offset, Fs);
@@ -250,7 +250,7 @@ int opus_multistream_decode_native(
return OPUS_INTERNAL_ERROR;
}
packet_offset = 0;
- ret = opus_decode_native(dec, data, len, buf, frame_size, decode_fec, s!=st->layout.nb_streams-1, &packet_offset, soft_clip);
+ ret = opus_decode_native(dec, data, len, buf, frame_size, decode_fec, s!=st->layout.nb_streams-1, &packet_offset, soft_clip, NULL, 0);
if (!do_plc)
{
data += packet_offset;
diff --git a/src/opus_multistream_encoder.c b/src/opus_multistream_encoder.c
index 213e3eb2..1725ade7 100644
--- a/src/opus_multistream_encoder.c
+++ b/src/opus_multistream_encoder.c
@@ -1003,7 +1003,7 @@ int opus_multistream_encode_native
return OPUS_INTERNAL_ERROR;
}
len = opus_repacketizer_out_range_impl(&rp, 0, opus_repacketizer_get_nb_frames(&rp),
- data, max_data_bytes-tot_size, s != st->layout.nb_streams-1, !vbr && s == st->layout.nb_streams-1);
+ data, max_data_bytes-tot_size, s != st->layout.nb_streams-1, !vbr && s == st->layout.nb_streams-1, NULL, 0);
data += len;
tot_size += len;
}
diff --git a/src/opus_private.h b/src/opus_private.h
index 5e2463f5..364c21ce 100644
--- a/src/opus_private.h
+++ b/src/opus_private.h
@@ -42,8 +42,17 @@ struct OpusRepacketizer {
const unsigned char *frames[48];
opus_int16 len[48];
int framesize;
+ const unsigned char *paddings[48];
+ opus_int32 padding_len[48];
};
+typedef struct {
+ int id;
+ int frame;
+ const unsigned char *data;
+ opus_int32 len;
+} opus_extension_data;
+
typedef struct ChannelLayout {
int nb_channels;
int nb_streams;
@@ -148,7 +157,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_
int opus_decode_native(OpusDecoder *st, const unsigned char *data, opus_int32 len,
opus_val16 *pcm, int frame_size, int decode_fec, int self_delimited,
- opus_int32 *packet_offset, int soft_clip);
+ opus_int32 *packet_offset, int soft_clip, const OpusDRED *dred, opus_int32 dred_offset);
/* Make sure everything is properly aligned. */
static OPUS_INLINE int align(int i)
@@ -162,13 +171,18 @@ static OPUS_INLINE int align(int i)
return ((i + alignment - 1) / alignment) * alignment;
}
+/* More than that is ridiculous for now (3 * max frames per packet)*/
+opus_int32 skip_extension(const unsigned char **data, opus_int32 len, opus_int32 *header_size);
+
int opus_packet_parse_impl(const unsigned char *data, opus_int32 len,
int self_delimited, unsigned char *out_toc,
const unsigned char *frames[48], opus_int16 size[48],
- int *payload_offset, opus_int32 *packet_offset);
+ int *payload_offset, opus_int32 *packet_offset,
+ const unsigned char **padding, opus_int32 *padding_len);
opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int end,
- unsigned char *data, opus_int32 maxlen, int self_delimited, int pad);
+ unsigned char *data, opus_int32 maxlen, int self_delimited, int pad,
+ const opus_extension_data *extensions, int nb_extensions);
int pad_frame(unsigned char *data, opus_int32 len, opus_int32 new_len);
@@ -198,4 +212,12 @@ int opus_multistream_decode_native(
void *user_data
);
+opus_int32 opus_packet_extensions_parse(const unsigned char *data, opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions);
+
+opus_int32 opus_packet_extensions_generate(unsigned char *data, opus_int32 len, const opus_extension_data *extensions, int nb_extensions, int pad);
+
+opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len);
+
+opus_int32 opus_packet_pad_impl(unsigned char *data, opus_int32 len, opus_int32 new_len, int pad, const opus_extension_data *extensions, int nb_extensions);
+
#endif /* OPUS_PRIVATE_H */
diff --git a/src/opus_projection_encoder.c b/src/opus_projection_encoder.c
index 06fb2d25..92813ad0 100644
--- a/src/opus_projection_encoder.c
+++ b/src/opus_projection_encoder.c
@@ -177,6 +177,20 @@ opus_int32 opus_projection_ambisonics_encoder_get_size(int channels,
demixing_matrix_rows = mapping_matrix_toa_demixing.rows;
demixing_matrix_cols = mapping_matrix_toa_demixing.cols;
}
+ else if (order_plus_one == 5)
+ {
+ mixing_matrix_rows = mapping_matrix_fourthoa_mixing.rows;
+ mixing_matrix_cols = mapping_matrix_fourthoa_mixing.cols;
+ demixing_matrix_rows = mapping_matrix_fourthoa_demixing.rows;
+ demixing_matrix_cols = mapping_matrix_fourthoa_demixing.cols;
+ }
+ else if (order_plus_one == 6)
+ {
+ mixing_matrix_rows = mapping_matrix_fifthoa_mixing.rows;
+ mixing_matrix_cols = mapping_matrix_fifthoa_mixing.cols;
+ demixing_matrix_rows = mapping_matrix_fifthoa_demixing.rows;
+ demixing_matrix_cols = mapping_matrix_fifthoa_demixing.cols;
+ }
else
return 0;
@@ -245,6 +259,20 @@ int opus_projection_ambisonics_encoder_init(OpusProjectionEncoder *st, opus_int3
mapping_matrix_toa_mixing_data,
sizeof(mapping_matrix_toa_mixing_data));
}
+ else if (order_plus_one == 5)
+ {
+ mapping_matrix_init(mixing_matrix, mapping_matrix_fourthoa_mixing.rows,
+ mapping_matrix_fourthoa_mixing.cols, mapping_matrix_fourthoa_mixing.gain,
+ mapping_matrix_fourthoa_mixing_data,
+ sizeof(mapping_matrix_fourthoa_mixing_data));
+ }
+ else if (order_plus_one == 6)
+ {
+ mapping_matrix_init(mixing_matrix, mapping_matrix_fifthoa_mixing.rows,
+ mapping_matrix_fifthoa_mixing.cols, mapping_matrix_fifthoa_mixing.gain,
+ mapping_matrix_fifthoa_mixing_data,
+ sizeof(mapping_matrix_fifthoa_mixing_data));
+ }
else
return OPUS_BAD_ARG;
@@ -276,6 +304,20 @@ int opus_projection_ambisonics_encoder_init(OpusProjectionEncoder *st, opus_int3
mapping_matrix_toa_demixing_data,
sizeof(mapping_matrix_toa_demixing_data));
}
+ else if (order_plus_one == 5)
+ {
+ mapping_matrix_init(demixing_matrix, mapping_matrix_fourthoa_demixing.rows,
+ mapping_matrix_fourthoa_demixing.cols, mapping_matrix_fourthoa_demixing.gain,
+ mapping_matrix_fourthoa_demixing_data,
+ sizeof(mapping_matrix_fourthoa_demixing_data));
+ }
+ else if (order_plus_one == 6)
+ {
+ mapping_matrix_init(demixing_matrix, mapping_matrix_fifthoa_demixing.rows,
+ mapping_matrix_fifthoa_demixing.cols, mapping_matrix_fifthoa_demixing.gain,
+ mapping_matrix_fifthoa_demixing_data,
+ sizeof(mapping_matrix_fifthoa_demixing_data));
+ }
else
return OPUS_BAD_ARG;
diff --git a/src/repacketizer.c b/src/repacketizer.c
index bda44a14..6a7a8b3d 100644
--- a/src/repacketizer.c
+++ b/src/repacketizer.c
@@ -32,6 +32,7 @@
#include "opus.h"
#include "opus_private.h"
#include "os_support.h"
+#include "stack_alloc.h"
int opus_repacketizer_get_size(void)
@@ -82,10 +83,19 @@ static int opus_repacketizer_cat_impl(OpusRepacketizer *rp, const unsigned char
return OPUS_INVALID_PACKET;
}
- ret=opus_packet_parse_impl(data, len, self_delimited, &tmp_toc, &rp->frames[rp->nb_frames], &rp->len[rp->nb_frames], NULL, NULL);
+ ret=opus_packet_parse_impl(data, len, self_delimited, &tmp_toc, &rp->frames[rp->nb_frames], &rp->len[rp->nb_frames],
+ NULL, NULL, &rp->paddings[rp->nb_frames], &rp->padding_len[rp->nb_frames]);
if(ret<1)return ret;
- rp->nb_frames += curr_nb_frames;
+ /* set padding length to zero for all but the first frame */
+ while (curr_nb_frames > 1)
+ {
+ rp->nb_frames++;
+ rp->padding_len[rp->nb_frames] = 0;
+ rp->paddings[rp->nb_frames] = NULL;
+ curr_nb_frames--;
+ }
+ rp->nb_frames++;
return OPUS_OK;
}
@@ -100,17 +110,23 @@ int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp)
}
opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int end,
- unsigned char *data, opus_int32 maxlen, int self_delimited, int pad)
+ unsigned char *data, opus_int32 maxlen, int self_delimited, int pad, const opus_extension_data *extensions, int nb_extensions)
{
int i, count;
opus_int32 tot_size;
opus_int16 *len;
const unsigned char **frames;
unsigned char * ptr;
+ int ones_begin=0, ones_end=0;
+ int ext_begin=0, ext_len=0;
+ int ext_count, total_ext_count;
+ VARDECL(opus_extension_data, all_extensions);
+ SAVE_STACK;
if (begin<0 || begin>=end || end>rp->nb_frames)
{
/*fprintf(stderr, "%d %d %d\n", begin, end, rp->nb_frames);*/
+ RESTORE_STACK;
return OPUS_BAD_ARG;
}
count = end-begin;
@@ -122,13 +138,50 @@ opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int
else
tot_size = 0;
+ /* figure out total number of extensions */
+ total_ext_count = nb_extensions;
+ for (i=begin;i<end;i++)
+ {
+ int n = opus_packet_extensions_count(rp->paddings[i], rp->padding_len[i]);
+ if (n > 0) total_ext_count += n;
+ }
+ ALLOC(all_extensions, total_ext_count ? total_ext_count : ALLOC_NONE, opus_extension_data);
+ /* copy over any extensions that were passed in */
+ for (ext_count=0;ext_count<nb_extensions;ext_count++)
+ {
+ all_extensions[ext_count] = extensions[ext_count];
+ }
+
+ /* incorporate any extensions from the repacketizer padding */
+ for (i=begin;i<end;i++)
+ {
+ int frame_ext_count, j;
+ frame_ext_count = total_ext_count - ext_count;
+ int ret = opus_packet_extensions_parse(rp->paddings[i], rp->padding_len[i],
+ &all_extensions[ext_count], &frame_ext_count);
+ if (ret<0)
+ {
+ RESTORE_STACK;
+ return OPUS_INTERNAL_ERROR;
+ }
+ /* renumber the extension frame numbers */
+ for (j=0;j<frame_ext_count;j++)
+ {
+ all_extensions[ext_count+j].frame += i-begin;
+ }
+ ext_count += frame_ext_count;
+ }
+
ptr = data;
if (count==1)
{
/* Code 0 */
tot_size += len[0]+1;
if (tot_size > maxlen)
+ {
+ RESTORE_STACK;
return OPUS_BUFFER_TOO_SMALL;
+ }
*ptr++ = rp->toc&0xFC;
} else if (count==2)
{
@@ -137,18 +190,24 @@ opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int
/* Code 1 */
tot_size += 2*len[0]+1;
if (tot_size > maxlen)
+ {
+ RESTORE_STACK;
return OPUS_BUFFER_TOO_SMALL;
+ }
*ptr++ = (rp->toc&0xFC) | 0x1;
} else {
/* Code 2 */
tot_size += len[0]+len[1]+2+(len[0]>=252);
if (tot_size > maxlen)
+ {
+ RESTORE_STACK;
return OPUS_BUFFER_TOO_SMALL;
+ }
*ptr++ = (rp->toc&0xFC) | 0x2;
ptr += encode_size(len[0], ptr);
}
}
- if (count > 2 || (pad && tot_size < maxlen))
+ if (count > 2 || (pad && tot_size < maxlen) || ext_count > 0)
{
/* Code 3 */
int vbr;
@@ -177,22 +236,45 @@ opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int
tot_size += len[count-1];
if (tot_size > maxlen)
+ {
+ RESTORE_STACK;
return OPUS_BUFFER_TOO_SMALL;
+ }
*ptr++ = (rp->toc&0xFC) | 0x3;
*ptr++ = count | 0x80;
} else {
tot_size += count*len[0]+2;
if (tot_size > maxlen)
+ {
+ RESTORE_STACK;
return OPUS_BUFFER_TOO_SMALL;
+ }
*ptr++ = (rp->toc&0xFC) | 0x3;
*ptr++ = count;
}
pad_amount = pad ? (maxlen-tot_size) : 0;
+ if (ext_count>0)
+ {
+ /* figure out how much space we need for the extensions */
+ ext_len = opus_packet_extensions_generate(NULL, maxlen-tot_size, all_extensions, ext_count, 0);
+ if (ext_len < 0) return ext_len;
+ if (!pad)
+ pad_amount = ext_len + ext_len/254 + 1;
+ }
if (pad_amount != 0)
{
int nb_255s;
data[1] |= 0x40;
nb_255s = (pad_amount-1)/255;
+ if (tot_size + ext_len + nb_255s + 1 > maxlen)
+ {
+ RESTORE_STACK;
+ return OPUS_BUFFER_TOO_SMALL;
+ }
+ ext_begin = tot_size+pad_amount-ext_len;
+ /* Prepend 0x01 padding */
+ ones_begin = tot_size+nb_255s+1;
+ ones_end = tot_size+pad_amount-ext_len;
for (i=0;i<nb_255s;i++)
*ptr++ = 255;
*ptr++ = pad_amount-255*nb_255s-1;
@@ -218,42 +300,62 @@ opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int
OPUS_MOVE(ptr, frames[i], len[i]);
ptr += len[i];
}
- if (pad)
+ if (ext_len > 0) {
+ int ret = opus_packet_extensions_generate(&data[ext_begin], ext_len, all_extensions, ext_count, 0);
+ celt_assert(ret == ext_len);
+ }
+ for (i=ones_begin;i<ones_end;i++)
+ data[i] = 0x01;
+ if (pad && ext_count==0)
{
/* Fill padding with zeros. */
while (ptr<data+maxlen)
*ptr++=0;
}
+ RESTORE_STACK;
return tot_size;
}
opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen)
{
- return opus_repacketizer_out_range_impl(rp, begin, end, data, maxlen, 0, 0);
+ return opus_repacketizer_out_range_impl(rp, begin, end, data, maxlen, 0, 0, NULL, 0);
}
opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen)
{
- return opus_repacketizer_out_range_impl(rp, 0, rp->nb_frames, data, maxlen, 0, 0);
+ return opus_repacketizer_out_range_impl(rp, 0, rp->nb_frames, data, maxlen, 0, 0, NULL, 0);
}
-int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len)
+opus_int32 opus_packet_pad_impl(unsigned char *data, opus_int32 len, opus_int32 new_len, int pad, const opus_extension_data *extensions, int nb_extensions)
{
OpusRepacketizer rp;
opus_int32 ret;
+ VARDECL(unsigned char, copy);
+ SAVE_STACK;
if (len < 1)
return OPUS_BAD_ARG;
if (len==new_len)
return OPUS_OK;
else if (len > new_len)
return OPUS_BAD_ARG;
+ ALLOC(copy, len, unsigned char);
opus_repacketizer_init(&rp);
/* Moving payload to the end of the packet so we can do in-place padding */
- OPUS_MOVE(data+new_len-len, data, len);
- ret = opus_repacketizer_cat(&rp, data+new_len-len, len);
+ OPUS_COPY(copy, data, len);
+ ret = opus_repacketizer_cat(&rp, copy, len);
if (ret != OPUS_OK)
return ret;
- ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, data, new_len, 0, 1);
+ ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, data, new_len, 0, pad, extensions, nb_extensions);
+ RESTORE_STACK;
+ return ret;
+}
+
+int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len)
+{
+ opus_int32 ret;
+ ALLOC_STACK;
+ ret = opus_packet_pad_impl(data, len, new_len, 1, NULL, 0);
+ RESTORE_STACK;
if (ret > 0)
return OPUS_OK;
else
@@ -264,13 +366,19 @@ opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len)
{
OpusRepacketizer rp;
opus_int32 ret;
+ int i;
if (len < 1)
return OPUS_BAD_ARG;
opus_repacketizer_init(&rp);
ret = opus_repacketizer_cat(&rp, data, len);
if (ret < 0)
return ret;
- ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, data, len, 0, 0);
+ /* Discard all padding and extensions. */
+ for (i=0;i<rp.nb_frames;i++) {
+ rp.padding_len[i] = 0;
+ rp.paddings[i] = NULL;
+ }
+ ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, data, len, 0, 0, NULL, 0);
celt_assert(ret > 0 && ret <= len);
return ret;
}
@@ -297,7 +405,7 @@ int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32
if (len<=0)
return OPUS_INVALID_PACKET;
count = opus_packet_parse_impl(data, len, 1, &toc, NULL,
- size, NULL, &packet_offset);
+ size, NULL, &packet_offset, NULL, NULL);
if (count<0)
return count;
data += packet_offset;
@@ -324,18 +432,24 @@ opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, in
for (s=0;s<nb_streams;s++)
{
opus_int32 ret;
+ int i;
int self_delimited = s!=nb_streams-1;
if (len<=0)
return OPUS_INVALID_PACKET;
opus_repacketizer_init(&rp);
ret = opus_packet_parse_impl(data, len, self_delimited, &toc, NULL,
- size, NULL, &packet_offset);
+ size, NULL, &packet_offset, NULL, NULL);
if (ret<0)
return ret;
ret = opus_repacketizer_cat_impl(&rp, data, packet_offset, self_delimited);
if (ret < 0)
return ret;
- ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, dst, len, self_delimited, 0);
+ /* Discard all padding and extensions. */
+ for (i=0;i<rp.nb_frames;i++) {
+ rp.padding_len[i] = 0;
+ rp.paddings[i] = NULL;
+ }
+ ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, dst, len, self_delimited, 0, NULL, 0);
if (ret < 0)
return ret;
else
diff --git a/tests/meson.build b/tests/meson.build
index 5f3ac9df..1c1ddf07 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,8 +1,9 @@
# Tests that link to libopus
opus_tests = [
['test_opus_api'],
- ['test_opus_decode', [], 60],
- ['test_opus_encode', 'opus_encode_regressions.c', 120],
+ ['test_opus_decode', [], 120],
+ ['test_opus_encode', 'opus_encode_regressions.c', 240],
+ ['test_opus_extensions', [], 120],
['test_opus_padding'],
['test_opus_projection'],
]
@@ -18,9 +19,9 @@ foreach t : opus_tests
exe_kwargs = {}
# This test uses private symbols
- if test_name == 'test_opus_projection'
+ if test_name == 'test_opus_projection' or test_name == 'test_opus_extensions'
exe_kwargs = {
- 'link_with': [celt_lib, silk_lib],
+ 'link_with': [celt_lib, silk_lib, dnn_lib],
'objects': opus_lib.extract_all_objects(),
}
endif
diff --git a/tests/random_config.sh b/tests/random_config.sh
index 0cdd855f..cdcfcda2 100755
--- a/tests/random_config.sh
+++ b/tests/random_config.sh
@@ -35,12 +35,11 @@ CFLAGS='-g'
opt=`echo -e "-O1\n-O2\n-O3" | shuf -n1`
-#arch=-march=`echo -e "core2\nsandybridge\nbroadwell\nskylake" | shuf -n1`
-arch=`echo -e "\n-march=core2\n-march=sandybridge\n-march=broadwell\n-march=skylake\n-march=native" | shuf -n1`
+arch=`echo -e "\n-march=core2\n-march=sandybridge\n-march=broadwell\n-march=skylake" | shuf -n1`
footprint=`echo -e "\n-DSMALL_FOOTPRINT" | shuf -n1`
std=`echo -e "\n-std=c90\n-std=c99\n-std=c11\n-std=c17" | shuf -n1`
-sanitize=`echo -e "\n-fsanitize=address -fno-sanitize-recover=all\n-fsanitize=undefined -fno-sanitize-recover=all -fsanitize-recover=signed-integer-overflow" | shuf -n1`
+sanitize=`echo -e "\n-fsanitize=address -fno-sanitize-recover=all\n-fsanitize=undefined -fno-sanitize-recover=all -fsanitize-recover=signed-integer-overflow -DDISABLE_PTR_CHECK" | shuf -n1`
CFLAGS="$CFLAGS $std $opt $arch $footprint $math $sanitize"
@@ -49,7 +48,7 @@ echo "CFLAGS=$CFLAGS" > "$config"
lib=`echo -e "\n--disable-static\n--disable-shared" | shuf -n1`
-arithmetic=`echo -e "\n--enable-fixed-point\n--enable-fixed-point --enable-fixed-point-debug\n--enable-fixed-point --disable-float-api\n--enable-fixed-point --enable-fixed-point-debug --disable-float-api" | shuf -n1`
+arithmetic=`echo -e "\n--enable-deep-plc\n--enable-dred\n--enable-osce\n--enable-dred --enable-osce\n--enable-fixed-point\n--enable-fixed-point --enable-fixed-point-debug\n--enable-fixed-point --disable-float-api\n--enable-fixed-point --enable-fixed-point-debug --disable-float-api" | shuf -n1`
custom=`echo -e "\n--enable-custom-modes" | shuf -n1`
@@ -61,6 +60,7 @@ harden=`echo -e "\n--enable-hardening" | shuf -n1`
fuzz=`echo -e "\n--enable-fuzzing" | shuf -n1`
checkasm=`echo -e "\n--enable-check-asm" | shuf -n1`
rfc8251=`echo -e "\n--disable-rfc8251" | shuf -n1`
+lossgen=`echo -e "\n--enable-lossgen" | shuf -n1`
if [ "$rfc8251" = --disable-rfc8251 ]
then
@@ -71,7 +71,7 @@ fi
echo using testvectors at "$vectors" >> "$config"
-config_opt="$lib $arithmetic $custom $asm $assert $harden $fuzz $checkasm $rfc8251 $approx"
+config_opt="$lib $arithmetic $custom $asm $assert $harden $fuzz $checkasm $rfc8251 $approx $lossgen"
echo configure $config_opt >> "$config"
diff --git a/tests/test_opus_api.c b/tests/test_opus_api.c
index 0e7ed2cc..9500d407 100644
--- a/tests/test_opus_api.c
+++ b/tests/test_opus_api.c
@@ -103,7 +103,7 @@ opus_int32 test_dec_api(void)
for(c=0;c<4;c++)
{
i=opus_decoder_get_size(c);
- if(((c==1||c==2)&&(i<=2048||i>1<<16))||((c!=1&&c!=2)&&i!=0))test_failed();
+ if(((c==1||c==2)&&(i<=2048||i>1<<18))||((c!=1&&c!=2)&&i!=0))test_failed();
fprintf(stdout," opus_decoder_get_size(%d)=%d ...............%s OK.\n",c,i,i>0?"":"....");
cfgs++;
}
@@ -367,7 +367,7 @@ opus_int32 test_msdec_api(void)
for(b=-1;b<4;b++)
{
i=opus_multistream_decoder_get_size(a,b);
- if(((a>0&&b<=a&&b>=0)&&(i<=2048||i>((1<<16)*a)))||((a<1||b>a||b<0)&&i!=0))test_failed();
+ if(((a>0&&b<=a&&b>=0)&&(i<=2048||i>((1<<18)*a)))||((a<1||b>a||b<0)&&i!=0))test_failed();
fprintf(stdout," opus_multistream_decoder_get_size(%2d,%2d)=%d %sOK.\n",a,b,i,i>0?"":"... ");
cfgs++;
}
@@ -1081,7 +1081,7 @@ opus_int32 test_enc_api(void)
for(c=0;c<4;c++)
{
i=opus_encoder_get_size(c);
- if(((c==1||c==2)&&(i<=2048||i>1<<17))||((c!=1&&c!=2)&&i!=0))test_failed();
+ if(((c==1||c==2)&&(i<=2048||i>1<<18))||((c!=1&&c!=2)&&i!=0))test_failed();
fprintf(stdout," opus_encoder_get_size(%d)=%d ...............%s OK.\n",c,i,i>0?"":"....");
cfgs++;
}
diff --git a/tests/test_opus_common.h b/tests/test_opus_common.h
index 5fb924f4..a03733a9 100644
--- a/tests/test_opus_common.h
+++ b/tests/test_opus_common.h
@@ -82,4 +82,5 @@ static OPUS_INLINE void _test_failed(const char *file, int line)
}
#define test_failed() _test_failed(__FILE__, __LINE__);
#define opus_test_assert(cond) {if (!(cond)) {test_failed();}}
+#define expect_true(cond, msg) {if (!(cond)) {fprintf(stderr, "FAIL - %s\n", msg); test_failed();}}
void regression_test(void);
diff --git a/tests/test_opus_decode.c b/tests/test_opus_decode.c
index 2d1e2d41..8725fa49 100644
--- a/tests/test_opus_decode.c
+++ b/tests/test_opus_decode.c
@@ -105,6 +105,9 @@ int test_decoder_code0(int no_fuzz)
for(fec=0;fec<2;fec++)
{
opus_int32 dur;
+#if defined(ENABLE_OSCE) || defined(ENABLE_DEEP_PLC)
+ opus_decoder_ctl(dec[t], OPUS_SET_COMPLEXITY(fast_rand()%11));
+#endif
/*Test PLC on a fresh decoder*/
out_samples = opus_decode(dec[t], 0, 0, outbuf, 120/factor, fec);
if(out_samples!=120/factor)test_failed();
@@ -185,6 +188,9 @@ int test_decoder_code0(int no_fuzz)
packet[1]=j;
for(t=0;t<5*2;t++)
{
+#if defined(ENABLE_OSCE) || defined(ENABLE_DEEP_PLC)
+ opus_decoder_ctl(dec[t], OPUS_SET_COMPLEXITY(fast_rand()%11));
+#endif
out_samples = opus_decode(dec[t], packet, 3, outbuf, MAX_FRAME_SAMP, 0);
if(out_samples!=expected[t])test_failed();
if(opus_decoder_ctl(dec[t], OPUS_GET_LAST_PACKET_DURATION(&dur))!=OPUS_OK)test_failed();
@@ -200,6 +206,9 @@ int test_decoder_code0(int no_fuzz)
/* The PLC is run for 6 frames in order to get better PLC coverage. */
for(j=0;j<6;j++)
{
+#if defined(ENABLE_OSCE) || defined(ENABLE_DEEP_PLC)
+ opus_decoder_ctl(dec[t], OPUS_SET_COMPLEXITY(fast_rand()%11));
+#endif
out_samples = opus_decode(dec[t], 0, 0, outbuf, expected[t], 0);
if(out_samples!=expected[t])test_failed();
if(opus_decoder_ctl(dec[t], OPUS_GET_LAST_PACKET_DURATION(&dur))!=OPUS_OK)test_failed();
@@ -295,6 +304,9 @@ int test_decoder_code0(int no_fuzz)
for(jj=0;jj<j;jj++)packet[jj+1]=fast_rand()&255;
for(t=0;t<5*2;t++)
{
+#if defined(ENABLE_OSCE) || defined(ENABLE_DEEP_PLC)
+ opus_decoder_ctl(dec[t], OPUS_SET_COMPLEXITY(fast_rand()%11));
+#endif
out_samples = opus_decode(dec[t], packet, j+1, outbuf, MAX_FRAME_SAMP, 0);
if(out_samples!=expected[t])test_failed();
opus_decoder_ctl(dec[t], OPUS_GET_FINAL_RANGE(&dec_final_range1));
@@ -322,6 +334,9 @@ int test_decoder_code0(int no_fuzz)
for(t=0;t<5*2;t++)
{
opus_int32 dur;
+#if defined(ENABLE_OSCE) || defined(ENABLE_DEEP_PLC)
+ opus_decoder_ctl(dec[t], OPUS_SET_COMPLEXITY(fast_rand()%11));
+#endif
out_samples = opus_decode(dec[t], packet, plen+1, outbuf, MAX_FRAME_SAMP, 0);
if(out_samples!=expected[t])test_failed();
if(t==0)dec_final_range2=dec_final_range1;
@@ -341,6 +356,9 @@ int test_decoder_code0(int no_fuzz)
expected=opus_decoder_get_nb_samples(dec[t],packet,plen);
for(count=0;count<10;count++)
{
+#if defined(ENABLE_OSCE) || defined(ENABLE_DEEP_PLC)
+ opus_decoder_ctl(dec[t], OPUS_SET_COMPLEXITY(fast_rand()%11));
+#endif
for(j=0;j<plen;j++)packet[j+1]=(fast_rand()|fast_rand())&255;
out_samples = opus_decode(dec[t], packet, plen+1, outbuf, MAX_FRAME_SAMP, 0);
if(out_samples!=expected)test_failed();
diff --git a/tests/test_opus_dred.c b/tests/test_opus_dred.c
new file mode 100644
index 00000000..432b84a3
--- /dev/null
+++ b/tests/test_opus_dred.c
@@ -0,0 +1,107 @@
+/* Copyright (c) 2023 Amazon
+ Written by Michael Klingbeil */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#ifndef _WIN32
+#include <unistd.h>
+#else
+#include <process.h>
+#define getpid _getpid
+#endif
+
+/* including sources directly to test internal APIs */
+#define CELT_C /* to make celt_assert work */
+#include "opus.h"
+#include "test_opus_common.h"
+
+
+
+#define NB_RANDOM_EXTENSIONS 10000000
+#define MAX_EXTENSION_SIZE 200
+#define MAX_NB_EXTENSIONS 100
+
+void test_random_dred(void)
+{
+ int error;
+ int i;
+ OpusDREDDecoder *dred_dec;
+ OpusDRED *dred;
+ dred_dec = opus_dred_decoder_create(&error);
+ expect_true(error == OPUS_OK, "opus_dred_decoder_create() failed");
+ dred = opus_dred_alloc(&error);
+ expect_true(error == OPUS_OK, "opus_dred_create() failed");
+ for (i=0;i<NB_RANDOM_EXTENSIONS;i++)
+ {
+ unsigned char payload[MAX_EXTENSION_SIZE];
+ int len;
+ int j;
+ int res1, res2;
+ int dred_end;
+ len = fast_rand()%(MAX_EXTENSION_SIZE+1);
+ for (j=0;j<len;j++)
+ payload[j] = fast_rand()&0xFF;
+ res1 = opus_dred_parse(dred_dec, dred, payload, len, 48000, 48000, &dred_end, fast_rand()&0x1);
+ if (res1 > 0)
+ {
+ res2 = opus_dred_process(dred_dec, dred, dred);
+ expect_true(res2 == OPUS_OK, "process should succeed if parse succeeds");
+ expect_true(res1 >= dred_end, "end before beginning");
+ }
+ }
+ opus_dred_free(dred);
+ opus_dred_decoder_destroy(dred_dec);
+}
+
+int main(int argc, char **argv)
+{
+ int env_used;
+ char *env_seed;
+ env_used=0;
+ env_seed=getenv("SEED");
+ if(argc>1)iseed=atoi(argv[1]);
+ else if(env_seed)
+ {
+ iseed=atoi(env_seed);
+ env_used=1;
+ }
+ else iseed=(opus_uint32)time(NULL)^(((opus_uint32)getpid()&65535)<<16);
+ Rw=Rz=iseed;
+
+ fprintf(stderr,"Testing dred. Random seed: %u (%.4X)\n", iseed, fast_rand() % 65535);
+ if(env_used)fprintf(stderr," Random seed set from the environment (SEED=%s).\n", env_seed);
+
+ test_random_dred();
+ fprintf(stderr,"Tests completed successfully.\n");
+ return 0;
+}
diff --git a/tests/test_opus_encode.c b/tests/test_opus_encode.c
index d6e8e2d3..489cb284 100644
--- a/tests/test_opus_encode.c
+++ b/tests/test_opus_encode.c
@@ -149,7 +149,15 @@ int test_encode(OpusEncoder *enc, int channels, int frame_size, OpusDecoder *dec
opus_int16 *outbuf;
int out_samples;
int ret = 0;
-
+#ifdef ENABLE_DRED
+ OpusDREDDecoder *dred_dec;
+ OpusDRED *dred;
+ int dred_end;
+ int dred_amount;
+ int dred_ret;
+ dred_dec = opus_dred_decoder_create(&dred_ret);
+ dred = opus_dred_alloc(&dred_ret);
+#endif
/* Generate input data */
inbuf = (opus_int16*)malloc(sizeof(*inbuf)*SSAMPLES);
generate_music(inbuf, SSAMPLES/2);
@@ -165,7 +173,22 @@ int test_encode(OpusEncoder *enc, int channels, int frame_size, OpusDecoder *dec
ret = -1;
break;
}
-
+#ifdef ENABLE_DRED
+ dred_amount = opus_dred_parse(dred_dec, dred, packet, len, 48000, 48000, &dred_end, 0);
+ if(dred_amount<0) {
+ fprintf(stderr,"opus_dred_parse() returned %d\n",dred_amount);
+ ret = -1;
+ break;
+ }
+ if (dred_amount >= frame_size && (fast_rand()&1)) {
+ dred_ret = opus_decoder_dred_decode(dec, dred, frame_size, outbuf, frame_size);
+ if(dred_ret<0) {
+ fprintf(stderr,"opus_decoder_dred_decode() returned %d\n",dred_ret);
+ ret = -1;
+ break;
+ }
+ }
+#endif
out_samples = opus_decode(dec, packet, len, outbuf, MAX_FRAME_SAMP, 0);
if(out_samples!=frame_size) {
fprintf(stderr,"opus_decode() returned %d\n",out_samples);
@@ -175,7 +198,10 @@ int test_encode(OpusEncoder *enc, int channels, int frame_size, OpusDecoder *dec
samp_count += frame_size;
} while (samp_count < ((SSAMPLES/2)-MAX_FRAME_SAMP));
-
+#ifdef ENABLE_DRED
+ opus_dred_decoder_destroy(dred_dec);
+ opus_dred_free(dred);
+#endif
/* Clean up */
free(inbuf);
free(outbuf);
@@ -250,7 +276,9 @@ void fuzz_encoder_settings(const int num_encoders, const int num_setting_changes
if(opus_encoder_ctl(enc, OPUS_SET_PREDICTION_DISABLED(pred_disabled)) != OPUS_OK) test_failed();
if(opus_encoder_ctl(enc, OPUS_SET_DTX(dtx)) != OPUS_OK) test_failed();
if(opus_encoder_ctl(enc, OPUS_SET_EXPERT_FRAME_DURATION(frame_size_enum)) != OPUS_OK) test_failed();
-
+#ifdef ENABLE_DRED
+ if(opus_encoder_ctl(enc, OPUS_SET_DRED_DURATION(fast_rand()%101)) != OPUS_OK) test_failed();
+#endif
if(test_encode(enc, num_channels, frame_size, dec)) {
fprintf(stderr,
"fuzz_encoder_settings: %d kHz, %d ch, application: %d, "
@@ -395,6 +423,9 @@ int run_test1(int no_fuzz)
if(opus_encoder_ctl(enc, OPUS_SET_VBR_CONSTRAINT(rc==1))!=OPUS_OK)test_failed();
if(opus_encoder_ctl(enc, OPUS_SET_VBR_CONSTRAINT(rc==1))!=OPUS_OK)test_failed();
if(opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(rc==0))!=OPUS_OK)test_failed();
+#ifdef ENABLE_DRED
+ if(opus_encoder_ctl(enc, OPUS_SET_DRED_DURATION(fast_rand()%101)) != OPUS_OK) test_failed();
+#endif
for(j=0;j<13;j++)
{
int rate;
diff --git a/tests/test_opus_extensions.c b/tests/test_opus_extensions.c
new file mode 100644
index 00000000..4d89b0c0
--- /dev/null
+++ b/tests/test_opus_extensions.c
@@ -0,0 +1,450 @@
+/* Copyright (c) 2023 Amazon
+ Written by Michael Klingbeil */
+/*
+ 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 OWNER
+ 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#ifndef _WIN32
+#include <unistd.h>
+#else
+#include <process.h>
+#define getpid _getpid
+#endif
+
+#include "../src/opus_private.h"
+#include "test_opus_common.h"
+
+void test_extensions_generate_success(void)
+{
+ static const opus_extension_data ext[] = {
+ {2, 0, (const unsigned char *)"a", 1},
+ {32, 10, (const unsigned char *)"DRED", 4},
+ {33, 1, (const unsigned char *)"NOT DRED", 8},
+ {3, 4, (const unsigned char *)NULL, 0}
+ };
+
+ int result;
+ unsigned char packet[32];
+ const unsigned char *p = packet;
+ result = opus_packet_extensions_generate(packet, 23+4, ext, 4, 1);
+ expect_true(result == 23+4, "expected length 23+4");
+
+ /* expect padding */
+ expect_true(p[0] == 1 && p[1] == 1 && p[2] == 1 && p[3] == 1, "expected padding");
+ p += 4;
+
+ /* extension ID=2 */
+ expect_true((p[0] >> 1) == 2, "expected extension id 2");
+ /* For extension IDs 1 through 31, L=0 means that no data follows the
+ extension, whereas L=1 means that exactly one byte of extension data follows. */
+ expect_true((p[0] & 0x01) == 1, "expected L-bit set");
+ /* content */
+ expect_true(p[1] == 'a', "expected extension content");
+ p += 2;
+
+ /* next byte should increment the frame count, ID=1, L=0 */
+ expect_true(p[0] == 0x02, "bad frame separator");
+ p += 1;
+ /* extension ID=33 */
+ expect_true((p[0] >> 1) == 33, "expected extension id 33");
+ /* For IDs 32 to 127, L=0 signals that the extension data takes up the
+ rest of the padding, and L=1 signals that a length indicator follows. */
+ expect_true((p[0] & 0x01) == 1, "expected L-bit set");
+ /* content */
+ expect_true(p[1] == ext[2].len, "expected length");
+ p += 2;
+ expect_true(0 == memcmp(p, ext[2].data, ext[2].len), "expected extension content");
+ p += ext[2].len;
+
+ /* advance to frame 4, increment by 3 */
+ /* next byte should increment the frame count, ID=1, L=1 */
+ expect_true(p[0] == 0x03, "bad frame separator");
+ expect_true(p[1] == 0x03, "bad frame increment");
+ p += 2;
+ /* extension ID=3 */
+ expect_true((p[0] >> 1) == 3, "expected extension id 3");
+ /* For extension IDs 1 through 31, L=0 means that no data follows the
+ extension, whereas L=1 means that exactly one byte of extension data follows. */
+ expect_true((p[0] & 0x01) == 0, "expected L-bit unset");
+ p += 1;
+
+ /* advance to frame 10, increment by 6 */
+ /* next byte should increment the frame count, ID=1, L=1 */
+ expect_true(p[0] == 0x03, "bad frame separator");
+ expect_true(p[1] == 0x06, "bad frame increment");
+ p += 2;
+ /* extension ID=32 */
+ expect_true((p[0] >> 1) == 32, "expected extension id 32");
+ /* For IDs 32 to 127, L=0 signals that the extension data takes up the
+ rest of the padding */
+ expect_true((p[0] & 0x01) == 0, "expected L-bit unset");
+ p += 1;
+ expect_true(0 == memcmp(p, ext[1].data, ext[1].len), "expected extension content");
+}
+
+void test_extensions_generate_zero(void)
+{
+ int result;
+ unsigned char packet[32];
+
+ /* zero length packet, zero extensions */
+ result = opus_packet_extensions_generate(packet, 0, NULL, 0, 1);
+ expect_true(result == 0, "expected length 0");
+}
+
+void test_extensions_generate_no_padding(void)
+{
+ static const opus_extension_data ext[] = {
+ {2, 0, (const unsigned char *)"a", 1},
+ {32, 10, (const unsigned char *)"DRED", 4},
+ {33, 1, (const unsigned char *)"NOT DRED", 8},
+ {3, 4, (const unsigned char *)NULL, 0}
+ };
+
+ int result;
+ unsigned char packet[32];
+ result = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+ expect_true(result == 23, "expected length 23");
+}
+
+void test_extensions_generate_fail(void)
+{
+ static const opus_extension_data ext[] = {
+ {2, 0, (const unsigned char *)"a", 1},
+ {32, 10, (const unsigned char *)"DRED", 4},
+ {33, 1, (const unsigned char *)"NOT DRED", 8},
+ {3, 4, (const unsigned char *)NULL, 0}
+ };
+
+ int result;
+ unsigned char packet[100];
+
+ /* buffer too small */
+ result = opus_packet_extensions_generate(packet, 4, ext, 4, 1);
+ expect_true(result == OPUS_BUFFER_TOO_SMALL, "expected OPUS_BUFFER_TOO_SMALL");
+
+ /* invalid id */
+ {
+ static const opus_extension_data id_too_big[] = {
+ {256, 0, (const unsigned char *)"a", 1},
+ };
+ result = opus_packet_extensions_generate(packet, sizeof(packet), id_too_big, 1, 1);
+ expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+ }
+
+ /* invalid id */
+ {
+ static const opus_extension_data id_too_small[] = {
+ {1, 0, (const unsigned char *)"a", 1},
+ };
+ result = opus_packet_extensions_generate(packet, sizeof(packet), id_too_small, 1, 1);
+ expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+ }
+
+ /* frame index too big */
+ {
+ static const opus_extension_data frame_too_big[] = {
+ {33, 48, (const unsigned char *)"a", 1},
+ };
+ result = opus_packet_extensions_generate(packet, sizeof(packet), frame_too_big, 1, 1);
+ expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+ }
+
+ /* size too big for extension IDs 1 through 31 */
+ {
+ static const opus_extension_data size_too_big[] = {
+ {2, 0, (const unsigned char *)"abcd", 4},
+ };
+ result = opus_packet_extensions_generate(packet, sizeof(packet), size_too_big, 1, 1);
+ expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+ }
+
+ /* negative size for extension IDs 1 through 31 */
+ {
+ static const opus_extension_data neg_size[] = {
+ {2, 0, NULL, -4},
+ };
+ result = opus_packet_extensions_generate(packet, sizeof(packet), neg_size, 1, 1);
+ expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+ }
+
+ /* negative size for extension IDs 32 through 127 */
+ {
+ static const opus_extension_data neg_size_33[] = {
+ {33, 0, NULL, -4},
+ };
+ result = opus_packet_extensions_generate(packet, sizeof(packet), neg_size_33, 1, 1);
+ expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+ }
+}
+
+void test_extensions_parse_success(void)
+{
+ static const opus_extension_data ext[] = {
+ {2, 0, (const unsigned char *)"a", 1},
+ {32, 10, (const unsigned char *)"DRED", 4},
+ {33, 1, (const unsigned char *)"NOT DRED", 8},
+ {3, 4, (const unsigned char *)NULL, 0}
+ };
+ opus_extension_data ext_out[10];
+ int nb_ext;
+ int len, result;
+ unsigned char packet[32];
+
+ nb_ext = 10;
+ len = opus_packet_extensions_generate(packet, 32, ext, 4, 1);
+ expect_true(len == 32, "expected length 32");
+ result = opus_packet_extensions_count(packet, len);
+ expect_true(result == 4, "expected opus_packet_extensions_count 4");
+ result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+ expect_true(nb_ext == 4, "expected 4 extensions");
+
+ expect_true(ext_out[0].id == 2, "expected id 2");
+ expect_true(ext_out[0].frame == 0, "expected frame 0");
+ expect_true(ext_out[0].len == 1, "expected len 1");
+ expect_true(0 == memcmp(ext_out[0].data, ext[0].data, 1), "expected data");
+
+ expect_true(ext_out[1].id == 33, "expected id 33");
+ expect_true(ext_out[1].frame == 1, "expected frame 1");
+ expect_true(ext_out[1].len == 8, "expected len 8");
+ expect_true(0 == memcmp(ext_out[1].data, ext[2].data, 8), "expected data");
+
+ expect_true(ext_out[2].id == 3, "expected id 3");
+ expect_true(ext_out[2].frame == 4, "expected frame 4");
+ expect_true(ext_out[2].len == 0, "expected len 0");
+
+ expect_true(ext_out[3].id == 32, "expected id 32");
+ expect_true(ext_out[3].frame == 10, "expected frame 10");
+ expect_true(ext_out[3].len == 4, "expected len 4");
+ expect_true(0 == memcmp(ext_out[3].data, ext[1].data, 4), "expected data");
+}
+
+void test_extensions_parse_zero(void)
+{
+ static const opus_extension_data ext[] = {
+ {32, 1, (const unsigned char *)"DRED", 4},
+ };
+ int nb_ext;
+ int len, result;
+ unsigned char packet[32];
+
+ len = opus_packet_extensions_generate(packet, 32, ext, 1, 1);
+ expect_true(len == 32, "expected length 32");
+
+ nb_ext = 0;
+ result = opus_packet_extensions_parse(packet, len, NULL, &nb_ext);
+ expect_true(result == OPUS_BUFFER_TOO_SMALL, "expected OPUS_BUFFER_TOO_SMALL");
+}
+
+void test_extensions_parse_fail(void)
+{
+ static const opus_extension_data ext[] = {
+ {2, 0, (const unsigned char *)"a", 1},
+ {33, 1, (const unsigned char *)"NOT DRED", 8},
+ {3, 4, (const unsigned char *)NULL, 0},
+ {32, 10, (const unsigned char *)"DRED", 4}
+ };
+ opus_extension_data ext_out[10];
+ int nb_ext;
+ int len, result;
+ unsigned char packet[32];
+
+ /* create invalid length */
+ len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+ packet[4] = 255;
+ nb_ext = 10;
+ result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+ expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET");
+ result = opus_packet_extensions_count(packet, len);
+ expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET");
+
+ /* create invalid frame increment */
+ nb_ext = 10;
+ len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+ packet[14] = 255;
+ result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+ expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET");
+ /* note, opus_packet_extensions_count does not read the invalid frame increment
+ and tells us that we have 4 extensions */
+ result = opus_packet_extensions_count(packet, len);
+ expect_true(result == 4, "expected opus_packet_extensions_count to return 4");
+
+ /* not enough space */
+ nb_ext = 1;
+ len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+ result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+ expect_true(result == OPUS_BUFFER_TOO_SMALL, "expected OPUS_BUFFER_TOO_SMALL");
+}
+
+#define NB_RANDOM_EXTENSIONS 100000000
+#define MAX_EXTENSION_SIZE 200
+#define MAX_NB_EXTENSIONS 100
+
+void test_random_extensions_parse(void)
+{
+ int i;
+ for (i=0;i<NB_RANDOM_EXTENSIONS;i++)
+ {
+ opus_extension_data ext_out[MAX_NB_EXTENSIONS];
+ int nb_ext;
+ unsigned char payload[MAX_EXTENSION_SIZE];
+ int len;
+ int j;
+ int result;
+ len = fast_rand()%(MAX_EXTENSION_SIZE+1);
+ for (j=0;j<len;j++)
+ payload[j] = fast_rand()&0xFF;
+ nb_ext = fast_rand()%(MAX_NB_EXTENSIONS+1);
+ result = opus_packet_extensions_parse(payload, len, ext_out, &nb_ext);
+ expect_true(result == OPUS_OK || result == OPUS_BUFFER_TOO_SMALL || result == OPUS_INVALID_PACKET, "expected OPUS_OK, OPUS_BUFFER_TOO_SMALL or OPUS_INVALID_PACKET");
+ /* Even if parsing fails, check that the extensions that got extracted make sense. */
+ for (j=0;j<nb_ext;j++)
+ {
+ expect_true(ext_out[j].frame >= 0 && ext_out[j].frame < 48, "expected frame between 0 and 47");
+ expect_true(ext_out[j].id >= 2 && ext_out[j].id <= 127, "expected id between 2 and 127");
+ expect_true(ext_out[j].data >= payload && ext_out[j].data+ext_out[j].len <= payload+len, "expected data to be within packet");
+ }
+ }
+}
+
+void test_opus_repacketizer_out_range_impl(void)
+{
+ OpusRepacketizer rp;
+ unsigned char packet[1024];
+ unsigned char packet_out[1024];
+ opus_int16 size[48];
+ const unsigned char *padding;
+ opus_int32 padding_len;
+ opus_extension_data ext_out[10];
+ int i;
+ int nb_ext;
+ int res, len;
+ int first_count = 0, second_count = 0;
+ static const opus_extension_data ext[] = {
+ {33, 0, (const unsigned char *)"abcdefg", 7},
+ {100, 0, (const unsigned char *)"uvwxyz", 6},
+ };
+
+ opus_repacketizer_init(&rp);
+
+ memset(packet, 0, sizeof(packet));
+ /* Hybrid Packet with 20 msec frames, Code 3 */
+ packet[0] = (15 << 3) | 3;
+ /* Code 3, padding bit set, 1 frame */
+ packet[1] = 1 << 6 | 1;
+ packet[2] = 0;
+ packet[3] = 0;
+
+ /* generate 2 extensions, id 33 and 100 */
+ len = opus_packet_extensions_generate(&packet[4], sizeof(packet)-4, ext, 2, 0);
+ /* update the padding length */
+ packet[2] = len;
+
+ /* concatenate 3 frames */
+ res = opus_repacketizer_cat(&rp, packet, 4+len);
+ /* for the middle frame, no padding, no extensions */
+ packet[1] = 1;
+ res = opus_repacketizer_cat(&rp, packet, 4);
+ /* switch back to extensions for the last frame extensions */
+ packet[1] = 1 << 6 | 1;
+ res = opus_repacketizer_cat(&rp, packet, 4+len);
+
+ expect_true(rp.nb_frames == 3, "Expected 3 frames");
+ res = opus_repacketizer_out_range_impl(&rp,
+ 0, 3, /* begin, end */
+ packet_out, /* unsigned char *data */
+ sizeof(packet_out), /* opus_int32 maxlen */
+ 0, /*int self_delimited */
+ 0, /* int pad */
+ NULL, /* const opus_extension_data *extensions */
+ 0 /* int nb_extensions */);
+ expect_true(res > 0, "expected valid packet length");
+
+ /* now verify that we have the expected extensions */
+ res = opus_packet_parse_impl(packet_out, res, 0, NULL, NULL, size,
+ NULL, NULL, &padding, &padding_len);
+ nb_ext = 10;
+ res = opus_packet_extensions_parse(padding, padding_len, ext_out, &nb_ext);
+ expect_true(nb_ext == 4, "Expected 4 extensions");
+ for (i = 0 ; i < nb_ext; i++)
+ {
+ if (ext_out[i].id == 33)
+ {
+ opus_test_assert(ext_out[i].len == ext[0].len);
+ opus_test_assert(0 == memcmp(ext_out[i].data, ext[0].data, ext[0].len));
+ first_count++;
+ }
+ else if (ext_out[i].id == 100)
+ {
+ opus_test_assert(ext_out[i].len == ext[1].len);
+ opus_test_assert(0 == memcmp(ext_out[i].data, ext[1].data, ext[1].len));
+ second_count++;
+ }
+ if (i < 2)
+ opus_test_assert(ext_out[i].frame == 0)
+ else
+ opus_test_assert(ext_out[i].frame == 2)
+ }
+ opus_test_assert(first_count == 2);
+ opus_test_assert(second_count == 2);
+}
+
+int main(int argc, char **argv)
+{
+ int env_used;
+ char *env_seed;
+ env_used=0;
+ env_seed=getenv("SEED");
+ if(argc>1)iseed=atoi(argv[1]);
+ else if(env_seed)
+ {
+ iseed=atoi(env_seed);
+ env_used=1;
+ }
+ else iseed=(opus_uint32)time(NULL)^(((opus_uint32)getpid()&65535)<<16);
+ Rw=Rz=iseed;
+
+ fprintf(stderr,"Testing extensions. Random seed: %u (%.4X)\n", iseed, fast_rand() % 65535);
+ if(env_used)fprintf(stderr," Random seed set from the environment (SEED=%s).\n", env_seed);
+
+ test_extensions_generate_success();
+ test_extensions_generate_zero();
+ test_extensions_generate_no_padding();
+ test_extensions_generate_fail();
+ test_extensions_parse_success();
+ test_extensions_parse_zero();
+ test_extensions_parse_fail();
+ test_random_extensions_parse();
+ test_opus_repacketizer_out_range_impl();
+ fprintf(stderr,"Tests completed successfully.\n");
+ return 0;
+}
diff --git a/tests/test_opus_padding.c b/tests/test_opus_padding.c
index c9ef7375..1851b02b 100644
--- a/tests/test_opus_padding.c
+++ b/tests/test_opus_padding.c
@@ -29,6 +29,10 @@
* http://lists.xiph.org/pipermail/opus/2012-November/001834.html
*/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/tests/test_opus_projection.c b/tests/test_opus_projection.c
index 4e06613e..9bdf86bb 100644
--- a/tests/test_opus_projection.c
+++ b/tests/test_opus_projection.c
@@ -231,7 +231,7 @@ void test_creation_arguments(const int channels, const int mapping_family)
opus_free(matrix);
}
- is_channels_valid = (order_plus_one >= 2 && order_plus_one <= 4) &&
+ is_channels_valid = (order_plus_one >= 2 && order_plus_one <= 6) &&
(nondiegetic_channels == 0 || nondiegetic_channels == 2);
is_projection_valid = (enc_error == OPUS_OK && dec_error == OPUS_OK);
if (is_channels_valid ^ is_projection_valid)
diff --git a/win32/.gitignore b/win32/.gitignore
deleted file mode 100644
index c17feab7..00000000
--- a/win32/.gitignore
+++ /dev/null
@@ -1,26 +0,0 @@
-# Visual Studio ignores
-[Dd]ebug/
-[Dd]ebugDLL/
-[Dd]ebugDLL_fixed/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleaseDLL/
-[Rr]eleaseDLL_fixed/
-[Rr]eleases/
-*.manifest
-*.lastbuildstate
-*.lib
-*.log
-*.idb
-*.ipdb
-*.ilk
-*.iobj
-*.obj
-*.opensdf
-*.pdb
-*.sdf
-*.suo
-*.tlog
-*.vcxproj.user
-*.vc.db
-*.vc.opendb
diff --git a/win32/VS2015/common.props b/win32/VS2015/common.props
deleted file mode 100644
index f6e920b6..00000000
--- a/win32/VS2015/common.props
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ImportGroup Label="PropertySheets" />
- <PropertyGroup Label="UserMacros" />
- <PropertyGroup>
- <OutDir>$(Platform)\$(Configuration)\</OutDir>
- <IntDir>$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
- <CharacterSet Condition="'$(ConfigurationType)'=='Application'">Unicode</CharacterSet>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)'=='Debug' or '$(Configuration)'=='DebugDLL' or '$(Configuration)'=='DebugDLL_fixed'">
- <LinkIncremental>true</LinkIncremental>
- <UseDebugLibraries>true</UseDebugLibraries>
- <WholeProgramOptimization>false</WholeProgramOptimization>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)'=='Release' or '$(Configuration)'=='ReleaseDLL' or '$(Configuration)'=='ReleaseDLL_fixed'">
- <LinkIncremental>false</LinkIncremental>
- <UseDebugLibraries>false</UseDebugLibraries>
- <WholeProgramOptimization>true</WholeProgramOptimization>
- </PropertyGroup>
- <ItemDefinitionGroup>
- <ClCompile>
- <WarningLevel>Level3</WarningLevel>
- <CompileAsManaged>false</CompileAsManaged>
- <CompileAsWinRT>false</CompileAsWinRT>
- <AdditionalIncludeDirectories>..\..;..\..\include;..\..\silk;..\..\celt;..\..\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions>HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <RuntimeTypeInfo>false</RuntimeTypeInfo>
- <OpenMPSupport>false</OpenMPSupport>
- </ClCompile>
- <Lib>
- <SubSystem>Console</SubSystem>
- </Lib>
- <Link>
- <LargeAddressAware>true</LargeAddressAware>
- <SubSystem>Console</SubSystem>
- </Link>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug' or '$(Configuration)'=='DebugDLL' or '$(Configuration)'=='DebugDLL_fixed'">
- <ClCompile>
- <ControlFlowGuard>Guard</ControlFlowGuard>
- <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
- <EnableEnhancedInstructionSet Condition="'$(Platform)'=='Win32'">NoExtensions</EnableEnhancedInstructionSet>
- <EnableParallelCodeGeneration>false</EnableParallelCodeGeneration>
- <FloatingPointExceptions>true</FloatingPointExceptions>
- <FunctionLevelLinking>false</FunctionLevelLinking>
- <InlineFunctionExpansion>Disabled</InlineFunctionExpansion>
- <MultiProcessorCompilation>false</MultiProcessorCompilation>
- <OmitFramePointers>false</OmitFramePointers>
- <Optimization>Disabled</Optimization>
- <RuntimeLibrary Condition="'$(Configuration)'=='Debug'">MultiThreadedDebug</RuntimeLibrary>
- <RuntimeLibrary Condition="'$(Configuration)'!='Debug'">MultiThreadedDebugDLL</RuntimeLibrary>
- <SDLCheck>true</SDLCheck>
- <StringPooling>false</StringPooling>
- </ClCompile>
- <Link>
- <GenerateDebugInformation>true</GenerateDebugInformation>
- </Link>
- </ItemDefinitionGroup>
- <ItemDefinitionGroup Condition="'$(Configuration)'=='Release' or '$(Configuration)'=='ReleaseDLL' or '$(Configuration)'=='ReleaseDLL_fixed'">
- <ClCompile>
- <ControlFlowGuard>false</ControlFlowGuard>
- <DebugInformationFormat>None</DebugInformationFormat>
- <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
- <EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
- <ExceptionHandling>false</ExceptionHandling>
- <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
- <FloatingPointModel Condition="'$(Configuration)'=='Release'">Fast</FloatingPointModel>
- <FloatingPointModel Condition="'$(Configuration)'!='Release'">Precise</FloatingPointModel>
- <FunctionLevelLinking>true</FunctionLevelLinking>
- <IntrinsicFunctions>true</IntrinsicFunctions>
- <MultiProcessorCompilation>true</MultiProcessorCompilation>
- <Optimization>MaxSpeed</Optimization>
- <RuntimeLibrary Condition="'$(Configuration)'=='Release'">MultiThreaded</RuntimeLibrary>
- <RuntimeLibrary Condition="'$(Configuration)'!='Release'">MultiThreadedDLL</RuntimeLibrary>
- <StructMemberAlignment>16Bytes</StructMemberAlignment>
- </ClCompile>
- <Link>
- <GenerateDebugInformation>false</GenerateDebugInformation>
- </Link>
- </ItemDefinitionGroup>
- <ItemGroup />
-</Project> \ No newline at end of file
diff --git a/win32/VS2015/opus.sln b/win32/VS2015/opus.sln
deleted file mode 100644
index 7b678e7f..00000000
--- a/win32/VS2015/opus.sln
+++ /dev/null
@@ -1,168 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.25420.1
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "opus", "opus.vcxproj", "{219EC965-228A-1824-174D-96449D05F88A}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "opus_demo", "opus_demo.vcxproj", "{016C739D-6389-43BF-8D88-24B2BF6F620F}"
- ProjectSection(ProjectDependencies) = postProject
- {219EC965-228A-1824-174D-96449D05F88A} = {219EC965-228A-1824-174D-96449D05F88A}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_opus_api", "test_opus_api.vcxproj", "{1D257A17-D254-42E5-82D6-1C87A6EC775A}"
- ProjectSection(ProjectDependencies) = postProject
- {219EC965-228A-1824-174D-96449D05F88A} = {219EC965-228A-1824-174D-96449D05F88A}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_opus_decode", "test_opus_decode.vcxproj", "{8578322A-1883-486B-B6FA-E0094B65C9F2}"
- ProjectSection(ProjectDependencies) = postProject
- {219EC965-228A-1824-174D-96449D05F88A} = {219EC965-228A-1824-174D-96449D05F88A}
- EndProjectSection
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_opus_encode", "test_opus_encode.vcxproj", "{84DAA768-1A38-4312-BB61-4C78BB59E5B8}"
- ProjectSection(ProjectDependencies) = postProject
- {219EC965-228A-1824-174D-96449D05F88A} = {219EC965-228A-1824-174D-96449D05F88A}
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Win32 = Debug|Win32
- Debug|x64 = Debug|x64
- DebugDLL_fixed|Win32 = DebugDLL_fixed|Win32
- DebugDLL_fixed|x64 = DebugDLL_fixed|x64
- DebugDLL|Win32 = DebugDLL|Win32
- DebugDLL|x64 = DebugDLL|x64
- Release|Win32 = Release|Win32
- Release|x64 = Release|x64
- ReleaseDLL_fixed|Win32 = ReleaseDLL_fixed|Win32
- ReleaseDLL_fixed|x64 = ReleaseDLL_fixed|x64
- ReleaseDLL|Win32 = ReleaseDLL|Win32
- ReleaseDLL|x64 = ReleaseDLL|x64
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {219EC965-228A-1824-174D-96449D05F88A}.Debug|Win32.ActiveCfg = Debug|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.Debug|Win32.Build.0 = Debug|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.Debug|x64.ActiveCfg = Debug|x64
- {219EC965-228A-1824-174D-96449D05F88A}.Debug|x64.Build.0 = Debug|x64
- {219EC965-228A-1824-174D-96449D05F88A}.DebugDLL_fixed|Win32.ActiveCfg = DebugDLL_fixed|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.DebugDLL_fixed|Win32.Build.0 = DebugDLL_fixed|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.DebugDLL_fixed|x64.ActiveCfg = DebugDLL_fixed|x64
- {219EC965-228A-1824-174D-96449D05F88A}.DebugDLL_fixed|x64.Build.0 = DebugDLL_fixed|x64
- {219EC965-228A-1824-174D-96449D05F88A}.DebugDLL|Win32.ActiveCfg = DebugDLL|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.DebugDLL|Win32.Build.0 = DebugDLL|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.DebugDLL|x64.ActiveCfg = DebugDLL|x64
- {219EC965-228A-1824-174D-96449D05F88A}.DebugDLL|x64.Build.0 = DebugDLL|x64
- {219EC965-228A-1824-174D-96449D05F88A}.Release|Win32.ActiveCfg = Release|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.Release|Win32.Build.0 = Release|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.Release|x64.ActiveCfg = Release|x64
- {219EC965-228A-1824-174D-96449D05F88A}.Release|x64.Build.0 = Release|x64
- {219EC965-228A-1824-174D-96449D05F88A}.ReleaseDLL_fixed|Win32.ActiveCfg = ReleaseDLL_fixed|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.ReleaseDLL_fixed|Win32.Build.0 = ReleaseDLL_fixed|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.ReleaseDLL_fixed|x64.ActiveCfg = ReleaseDLL_fixed|x64
- {219EC965-228A-1824-174D-96449D05F88A}.ReleaseDLL_fixed|x64.Build.0 = ReleaseDLL_fixed|x64
- {219EC965-228A-1824-174D-96449D05F88A}.ReleaseDLL|Win32.ActiveCfg = ReleaseDLL|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.ReleaseDLL|Win32.Build.0 = ReleaseDLL|Win32
- {219EC965-228A-1824-174D-96449D05F88A}.ReleaseDLL|x64.ActiveCfg = ReleaseDLL|x64
- {219EC965-228A-1824-174D-96449D05F88A}.ReleaseDLL|x64.Build.0 = ReleaseDLL|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.Debug|Win32.ActiveCfg = Debug|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.Debug|Win32.Build.0 = Debug|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.Debug|x64.ActiveCfg = Debug|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.Debug|x64.Build.0 = Debug|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.DebugDLL_fixed|Win32.ActiveCfg = DebugDLL_fixed|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.DebugDLL_fixed|Win32.Build.0 = DebugDLL_fixed|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.DebugDLL_fixed|x64.ActiveCfg = DebugDLL_fixed|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.DebugDLL_fixed|x64.Build.0 = DebugDLL_fixed|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.DebugDLL|Win32.ActiveCfg = DebugDLL|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.DebugDLL|Win32.Build.0 = DebugDLL|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.DebugDLL|x64.ActiveCfg = DebugDLL|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.DebugDLL|x64.Build.0 = DebugDLL|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.Release|Win32.ActiveCfg = Release|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.Release|Win32.Build.0 = Release|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.Release|x64.ActiveCfg = Release|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.Release|x64.Build.0 = Release|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.ReleaseDLL_fixed|Win32.ActiveCfg = ReleaseDLL_fixed|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.ReleaseDLL_fixed|Win32.Build.0 = ReleaseDLL_fixed|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.ReleaseDLL_fixed|x64.ActiveCfg = ReleaseDLL_fixed|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.ReleaseDLL_fixed|x64.Build.0 = ReleaseDLL_fixed|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.ReleaseDLL|Win32.ActiveCfg = ReleaseDLL|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.ReleaseDLL|Win32.Build.0 = ReleaseDLL|Win32
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.ReleaseDLL|x64.ActiveCfg = ReleaseDLL|x64
- {016C739D-6389-43BF-8D88-24B2BF6F620F}.ReleaseDLL|x64.Build.0 = ReleaseDLL|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.Debug|Win32.ActiveCfg = Debug|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.Debug|Win32.Build.0 = Debug|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.Debug|x64.ActiveCfg = Debug|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.Debug|x64.Build.0 = Debug|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.DebugDLL_fixed|Win32.ActiveCfg = DebugDLL_fixed|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.DebugDLL_fixed|Win32.Build.0 = DebugDLL_fixed|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.DebugDLL_fixed|x64.ActiveCfg = DebugDLL_fixed|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.DebugDLL_fixed|x64.Build.0 = DebugDLL_fixed|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.DebugDLL|Win32.ActiveCfg = DebugDLL|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.DebugDLL|Win32.Build.0 = DebugDLL|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.DebugDLL|x64.ActiveCfg = DebugDLL|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.DebugDLL|x64.Build.0 = DebugDLL|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.Release|Win32.ActiveCfg = Release|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.Release|Win32.Build.0 = Release|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.Release|x64.ActiveCfg = Release|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.Release|x64.Build.0 = Release|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.ReleaseDLL_fixed|Win32.ActiveCfg = ReleaseDLL_fixed|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.ReleaseDLL_fixed|Win32.Build.0 = ReleaseDLL_fixed|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.ReleaseDLL_fixed|x64.ActiveCfg = ReleaseDLL_fixed|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.ReleaseDLL_fixed|x64.Build.0 = ReleaseDLL_fixed|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.ReleaseDLL|Win32.ActiveCfg = ReleaseDLL|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.ReleaseDLL|Win32.Build.0 = ReleaseDLL|Win32
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.ReleaseDLL|x64.ActiveCfg = ReleaseDLL|x64
- {1D257A17-D254-42E5-82D6-1C87A6EC775A}.ReleaseDLL|x64.Build.0 = ReleaseDLL|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.Debug|Win32.ActiveCfg = Debug|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.Debug|Win32.Build.0 = Debug|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.Debug|x64.ActiveCfg = Debug|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.Debug|x64.Build.0 = Debug|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.DebugDLL_fixed|Win32.ActiveCfg = DebugDLL_fixed|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.DebugDLL_fixed|Win32.Build.0 = DebugDLL_fixed|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.DebugDLL_fixed|x64.ActiveCfg = DebugDLL_fixed|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.DebugDLL_fixed|x64.Build.0 = DebugDLL_fixed|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.DebugDLL|Win32.ActiveCfg = DebugDLL|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.DebugDLL|Win32.Build.0 = DebugDLL|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.DebugDLL|x64.ActiveCfg = DebugDLL|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.DebugDLL|x64.Build.0 = DebugDLL|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.Release|Win32.ActiveCfg = Release|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.Release|Win32.Build.0 = Release|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.Release|x64.ActiveCfg = Release|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.Release|x64.Build.0 = Release|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.ReleaseDLL_fixed|Win32.ActiveCfg = ReleaseDLL_fixed|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.ReleaseDLL_fixed|Win32.Build.0 = ReleaseDLL_fixed|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.ReleaseDLL_fixed|x64.ActiveCfg = ReleaseDLL_fixed|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.ReleaseDLL_fixed|x64.Build.0 = ReleaseDLL_fixed|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.ReleaseDLL|Win32.ActiveCfg = ReleaseDLL|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.ReleaseDLL|Win32.Build.0 = ReleaseDLL|Win32
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.ReleaseDLL|x64.ActiveCfg = ReleaseDLL|x64
- {8578322A-1883-486B-B6FA-E0094B65C9F2}.ReleaseDLL|x64.Build.0 = ReleaseDLL|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.Debug|Win32.ActiveCfg = Debug|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.Debug|Win32.Build.0 = Debug|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.Debug|x64.ActiveCfg = Debug|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.Debug|x64.Build.0 = Debug|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.DebugDLL_fixed|Win32.ActiveCfg = DebugDLL_fixed|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.DebugDLL_fixed|Win32.Build.0 = DebugDLL_fixed|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.DebugDLL_fixed|x64.ActiveCfg = DebugDLL_fixed|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.DebugDLL_fixed|x64.Build.0 = DebugDLL_fixed|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.DebugDLL|Win32.ActiveCfg = DebugDLL|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.DebugDLL|Win32.Build.0 = DebugDLL|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.DebugDLL|x64.ActiveCfg = DebugDLL|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.DebugDLL|x64.Build.0 = DebugDLL|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.Release|Win32.ActiveCfg = Release|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.Release|Win32.Build.0 = Release|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.Release|x64.ActiveCfg = Release|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.Release|x64.Build.0 = Release|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.ReleaseDLL_fixed|Win32.ActiveCfg = ReleaseDLL_fixed|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.ReleaseDLL_fixed|Win32.Build.0 = ReleaseDLL_fixed|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.ReleaseDLL_fixed|x64.ActiveCfg = ReleaseDLL_fixed|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.ReleaseDLL_fixed|x64.Build.0 = ReleaseDLL_fixed|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.ReleaseDLL|Win32.ActiveCfg = ReleaseDLL|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.ReleaseDLL|Win32.Build.0 = ReleaseDLL|Win32
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.ReleaseDLL|x64.ActiveCfg = ReleaseDLL|x64
- {84DAA768-1A38-4312-BB61-4C78BB59E5B8}.ReleaseDLL|x64.Build.0 = ReleaseDLL|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
diff --git a/win32/VS2015/opus.vcxproj b/win32/VS2015/opus.vcxproj
deleted file mode 100644
index 34b1233c..00000000
--- a/win32/VS2015/opus.vcxproj
+++ /dev/null
@@ -1,399 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="DebugDLL_fixed|Win32">
- <Configuration>DebugDLL_fixed</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL_fixed|x64">
- <Configuration>DebugDLL_fixed</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL|Win32">
- <Configuration>DebugDLL</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL|x64">
- <Configuration>DebugDLL</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL_fixed|Win32">
- <Configuration>ReleaseDLL_fixed</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL_fixed|x64">
- <Configuration>ReleaseDLL_fixed</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL|Win32">
- <Configuration>ReleaseDLL</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL|x64">
- <Configuration>ReleaseDLL</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <Keyword>Win32Proj</Keyword>
- <ProjectName>opus</ProjectName>
- <ProjectGuid>{219EC965-228A-1824-174D-96449D05F88A}</ProjectGuid>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|Win32'" Label="Configuration">
- <ConfigurationType>DynamicLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|Win32'" Label="Configuration">
- <ConfigurationType>DynamicLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|x64'" Label="Configuration">
- <ConfigurationType>DynamicLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|x64'" Label="Configuration">
- <ConfigurationType>DynamicLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|Win32'" Label="Configuration">
- <ConfigurationType>DynamicLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|Win32'" Label="Configuration">
- <ConfigurationType>DynamicLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>StaticLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|x64'" Label="Configuration">
- <ConfigurationType>DynamicLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|x64'" Label="Configuration">
- <ConfigurationType>DynamicLibrary</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <PropertyGroup Label="UserMacros" />
- <ItemDefinitionGroup>
- <ClCompile>
- <AdditionalIncludeDirectories>..\..\silk\fixed;..\..\silk\float;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
- <PreprocessorDefinitions Condition="'$(ConfigurationType)'=='DynamicLibrary'">DLL_EXPORT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <PreprocessorDefinitions Condition="'$(Configuration)'=='DebugDLL_fixed' or '$(Configuration)'=='ReleaseDLL_fixed'">FIXED_POINT;%(PreprocessorDefinitions)</PreprocessorDefinitions>
- <AdditionalOptions Condition="'$(Platform)'=='Win32'">/arch:IA32 %(AdditionalOptions)</AdditionalOptions>
- </ClCompile>
- <Lib>
- <AdditionalOptions>/ignore:4221 %(AdditionalOptions)</AdditionalOptions>
- </Lib>
- <PreBuildEvent>
- <Command>"$(ProjectDir)..\..\win32\genversion.bat" "$(ProjectDir)..\..\win32\version.h" PACKAGE_VERSION</Command>
- <Message>Generating version.h</Message>
- </PreBuildEvent>
- </ItemDefinitionGroup>
- <ItemGroup>
- <ClInclude Include="..\..\celt\arch.h" />
- <ClInclude Include="..\..\celt\bands.h" />
- <ClInclude Include="..\..\celt\celt.h" />
- <ClInclude Include="..\..\celt\celt_lpc.h" />
- <ClInclude Include="..\..\celt\cwrs.h" />
- <ClInclude Include="..\..\celt\ecintrin.h" />
- <ClInclude Include="..\..\celt\entcode.h" />
- <ClInclude Include="..\..\celt\entdec.h" />
- <ClInclude Include="..\..\celt\entenc.h" />
- <ClInclude Include="..\..\celt\fixed_c5x.h" />
- <ClInclude Include="..\..\celt\fixed_c6x.h" />
- <ClInclude Include="..\..\celt\fixed_debug.h" />
- <ClInclude Include="..\..\celt\fixed_generic.h" />
- <ClInclude Include="..\..\celt\float_cast.h" />
- <ClInclude Include="..\..\celt\kiss_fft.h" />
- <ClInclude Include="..\..\celt\laplace.h" />
- <ClInclude Include="..\..\celt\mathops.h" />
- <ClInclude Include="..\..\celt\mdct.h" />
- <ClInclude Include="..\..\celt\mfrngcod.h" />
- <ClInclude Include="..\..\celt\modes.h" />
- <ClInclude Include="..\..\celt\os_support.h" />
- <ClInclude Include="..\..\celt\pitch.h" />
- <ClInclude Include="..\..\celt\quant_bands.h" />
- <ClInclude Include="..\..\celt\rate.h" />
- <ClInclude Include="..\..\celt\stack_alloc.h" />
- <ClInclude Include="..\..\celt\static_modes_fixed.h" />
- <ClInclude Include="..\..\celt\static_modes_float.h" />
- <ClInclude Include="..\..\celt\vq.h" />
- <ClInclude Include="..\..\celt\x86\celt_lpc_sse.h" />
- <ClInclude Include="..\..\celt\x86\pitch_sse.h" />
- <ClInclude Include="..\..\celt\x86\vq_sse.h" />
- <ClInclude Include="..\..\celt\x86\x86cpu.h" />
- <ClInclude Include="..\..\celt\_kiss_fft_guts.h" />
- <ClInclude Include="..\..\include\opus.h" />
- <ClInclude Include="..\..\include\opus_defines.h" />
- <ClInclude Include="..\..\include\opus_types.h" />
- <ClInclude Include="..\..\include\opus_multistream.h" />
- <ClInclude Include="..\..\include\opus_projection.h" />
- <ClInclude Include="..\..\silk\API.h" />
- <ClInclude Include="..\..\silk\control.h" />
- <ClInclude Include="..\..\silk\debug.h" />
- <ClInclude Include="..\..\silk\define.h" />
- <ClInclude Include="..\..\silk\errors.h" />
- <ClInclude Include="..\..\silk\float\main_FLP.h" />
- <ClInclude Include="..\..\silk\float\SigProc_FLP.h" />
- <ClInclude Include="..\..\silk\float\structs_FLP.h" />
- <ClInclude Include="..\..\silk\Inlines.h" />
- <ClInclude Include="..\..\silk\MacroCount.h" />
- <ClInclude Include="..\..\silk\MacroDebug.h" />
- <ClInclude Include="..\..\silk\macros.h" />
- <ClInclude Include="..\..\silk\main.h" />
- <ClInclude Include="..\..\silk\pitch_est_defines.h" />
- <ClInclude Include="..\..\silk\PLC.h" />
- <ClInclude Include="..\..\silk\resampler_private.h" />
- <ClInclude Include="..\..\silk\resampler_rom.h" />
- <ClInclude Include="..\..\silk\resampler_structs.h" />
- <ClInclude Include="..\..\silk\structs.h" />
- <ClInclude Include="..\..\silk\tables.h" />
- <ClInclude Include="..\..\silk\tuning_parameters.h" />
- <ClInclude Include="..\..\silk\typedef.h" />
- <ClInclude Include="..\..\silk\x86\main_sse.h" />
- <ClInclude Include="..\..\win32\config.h" />
- <ClInclude Include="..\..\src\analysis.h" />
- <ClInclude Include="..\..\src\mapping_matrix.h" />
- <ClInclude Include="..\..\src\mlp.h" />
- <ClInclude Include="..\..\src\opus_private.h" />
- <ClInclude Include="..\..\src\tansig_table.h" />
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="..\..\celt\bands.c" />
- <ClCompile Include="..\..\celt\celt.c" />
- <ClCompile Include="..\..\celt\celt_decoder.c" />
- <ClCompile Include="..\..\celt\celt_encoder.c" />
- <ClCompile Include="..\..\celt\celt_lpc.c" />
- <ClCompile Include="..\..\celt\cwrs.c" />
- <ClCompile Include="..\..\celt\entcode.c" />
- <ClCompile Include="..\..\celt\entdec.c" />
- <ClCompile Include="..\..\celt\entenc.c" />
- <ClCompile Include="..\..\celt\kiss_fft.c" />
- <ClCompile Include="..\..\celt\laplace.c" />
- <ClCompile Include="..\..\celt\mathops.c" />
- <ClCompile Include="..\..\celt\mdct.c" />
- <ClCompile Include="..\..\celt\modes.c" />
- <ClCompile Include="..\..\celt\pitch.c" />
- <ClCompile Include="..\..\celt\quant_bands.c" />
- <ClCompile Include="..\..\celt\rate.c" />
- <ClCompile Include="..\..\celt\vq.c" />
- <ClCompile Include="..\..\celt\x86\celt_lpc_sse4_1.c" />
- <ClCompile Include="..\..\celt\x86\pitch_sse.c" />
- <ClCompile Include="..\..\celt\x86\pitch_sse2.c" />
- <ClCompile Include="..\..\celt\x86\pitch_sse4_1.c" />
- <ClCompile Include="..\..\celt\x86\vq_sse2.c" />
- <ClCompile Include="..\..\celt\x86\x86cpu.c" />
- <ClCompile Include="..\..\celt\x86\x86_celt_map.c" />
- <ClCompile Include="..\..\silk\A2NLSF.c" />
- <ClCompile Include="..\..\silk\ana_filt_bank_1.c" />
- <ClCompile Include="..\..\silk\biquad_alt.c" />
- <ClCompile Include="..\..\silk\bwexpander.c" />
- <ClCompile Include="..\..\silk\bwexpander_32.c" />
- <ClCompile Include="..\..\silk\check_control_input.c" />
- <ClCompile Include="..\..\silk\CNG.c" />
- <ClCompile Include="..\..\silk\code_signs.c" />
- <ClCompile Include="..\..\silk\control_audio_bandwidth.c" />
- <ClCompile Include="..\..\silk\control_codec.c" />
- <ClCompile Include="..\..\silk\control_SNR.c" />
- <ClCompile Include="..\..\silk\debug.c" />
- <ClCompile Include="..\..\silk\decoder_set_fs.c" />
- <ClCompile Include="..\..\silk\decode_core.c" />
- <ClCompile Include="..\..\silk\decode_frame.c" />
- <ClCompile Include="..\..\silk\decode_indices.c" />
- <ClCompile Include="..\..\silk\decode_parameters.c" />
- <ClCompile Include="..\..\silk\decode_pitch.c" />
- <ClCompile Include="..\..\silk\decode_pulses.c" />
- <ClCompile Include="..\..\silk\dec_API.c" />
- <ClCompile Include="..\..\silk\encode_indices.c" />
- <ClCompile Include="..\..\silk\encode_pulses.c" />
- <ClCompile Include="..\..\silk\enc_API.c" />
- <ClCompile Include="..\..\silk\gain_quant.c" />
- <ClCompile Include="..\..\silk\HP_variable_cutoff.c" />
- <ClCompile Include="..\..\silk\init_decoder.c" />
- <ClCompile Include="..\..\silk\init_encoder.c" />
- <ClCompile Include="..\..\silk\inner_prod_aligned.c" />
- <ClCompile Include="..\..\silk\interpolate.c" />
- <ClCompile Include="..\..\silk\lin2log.c" />
- <ClCompile Include="..\..\silk\log2lin.c" />
- <ClCompile Include="..\..\silk\LPC_analysis_filter.c" />
- <ClCompile Include="..\..\silk\LPC_fit.c" />
- <ClCompile Include="..\..\silk\LPC_inv_pred_gain.c" />
- <ClCompile Include="..\..\silk\LP_variable_cutoff.c" />
- <ClCompile Include="..\..\silk\NLSF2A.c" />
- <ClCompile Include="..\..\silk\NLSF_decode.c" />
- <ClCompile Include="..\..\silk\NLSF_del_dec_quant.c" />
- <ClCompile Include="..\..\silk\NLSF_encode.c" />
- <ClCompile Include="..\..\silk\NLSF_stabilize.c" />
- <ClCompile Include="..\..\silk\NLSF_unpack.c" />
- <ClCompile Include="..\..\silk\NLSF_VQ.c" />
- <ClCompile Include="..\..\silk\NLSF_VQ_weights_laroia.c" />
- <ClCompile Include="..\..\silk\NSQ.c" />
- <ClCompile Include="..\..\silk\NSQ_del_dec.c" />
- <ClCompile Include="..\..\silk\pitch_est_tables.c" />
- <ClCompile Include="..\..\silk\PLC.c" />
- <ClCompile Include="..\..\silk\process_NLSFs.c" />
- <ClCompile Include="..\..\silk\quant_LTP_gains.c" />
- <ClCompile Include="..\..\silk\resampler.c" />
- <ClCompile Include="..\..\silk\resampler_down2.c" />
- <ClCompile Include="..\..\silk\resampler_down2_3.c" />
- <ClCompile Include="..\..\silk\resampler_private_AR2.c" />
- <ClCompile Include="..\..\silk\resampler_private_down_FIR.c" />
- <ClCompile Include="..\..\silk\resampler_private_IIR_FIR.c" />
- <ClCompile Include="..\..\silk\resampler_private_up2_HQ.c" />
- <ClCompile Include="..\..\silk\resampler_rom.c" />
- <ClCompile Include="..\..\silk\shell_coder.c" />
- <ClCompile Include="..\..\silk\sigm_Q15.c" />
- <ClCompile Include="..\..\silk\sort.c" />
- <ClCompile Include="..\..\silk\stereo_decode_pred.c" />
- <ClCompile Include="..\..\silk\stereo_encode_pred.c" />
- <ClCompile Include="..\..\silk\stereo_find_predictor.c" />
- <ClCompile Include="..\..\silk\stereo_LR_to_MS.c" />
- <ClCompile Include="..\..\silk\stereo_MS_to_LR.c" />
- <ClCompile Include="..\..\silk\stereo_quant_pred.c" />
- <ClCompile Include="..\..\silk\sum_sqr_shift.c" />
- <ClCompile Include="..\..\silk\tables_gain.c" />
- <ClCompile Include="..\..\silk\tables_LTP.c" />
- <ClCompile Include="..\..\silk\tables_NLSF_CB_NB_MB.c" />
- <ClCompile Include="..\..\silk\tables_NLSF_CB_WB.c" />
- <ClCompile Include="..\..\silk\tables_other.c" />
- <ClCompile Include="..\..\silk\tables_pitch_lag.c" />
- <ClCompile Include="..\..\silk\tables_pulses_per_block.c" />
- <ClCompile Include="..\..\silk\table_LSF_cos.c" />
- <ClCompile Include="..\..\silk\VAD.c" />
- <ClCompile Include="..\..\silk\VQ_WMat_EC.c" />
- <ClCompile Include="..\..\silk\x86\NSQ_del_dec_sse4_1.c" />
- <ClCompile Include="..\..\silk\x86\NSQ_sse4_1.c" />
- <ClCompile Include="..\..\silk\x86\VAD_sse4_1.c" />
- <ClCompile Include="..\..\silk\x86\VQ_WMat_EC_sse4_1.c" />
- <ClCompile Include="..\..\silk\x86\x86_silk_map.c" />
- <ClCompile Include="..\..\src\analysis.c" />
- <ClCompile Include="..\..\src\mapping_matrix.c" />
- <ClCompile Include="..\..\src\mlp.c" />
- <ClCompile Include="..\..\src\mlp_data.c" />
- <ClCompile Include="..\..\src\opus.c" />
- <ClCompile Include="..\..\src\opus_compare.c">
- <DisableSpecificWarnings>4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
- </ClCompile>
- <ClCompile Include="..\..\src\opus_decoder.c" />
- <ClCompile Include="..\..\src\opus_encoder.c" />
- <ClCompile Include="..\..\src\opus_multistream.c" />
- <ClCompile Include="..\..\src\opus_multistream_decoder.c" />
- <ClCompile Include="..\..\src\opus_multistream_encoder.c" />
- <ClCompile Include="..\..\src\opus_projection_decoder.c" />
- <ClCompile Include="..\..\src\opus_projection_encoder.c" />
- <ClCompile Include="..\..\src\repacketizer.c" />
- </ItemGroup>
- <Choose>
- <When Condition="'$(Configuration)'=='DebugDLL_fixed' or '$(Configuration)'=='ReleaseDLL_fixed' or $(PreprocessorDefinitions.Contains('FIXED_POINT'))">
- <ItemGroup>
- <ClCompile Include="..\..\silk\fixed\*.c">
- <ExcludedFromBuild>false</ExcludedFromBuild>
- </ClCompile>
- <ClCompile Include="..\..\silk\fixed\x86\*.c">
- <ExcludedFromBuild>false</ExcludedFromBuild>
- </ClCompile>
- <ClCompile Include="..\..\silk\float\*.c">
- <ExcludedFromBuild>true</ExcludedFromBuild>
- </ClCompile>
- </ItemGroup>
- </When>
- <Otherwise>
- <ItemGroup>
- <ClCompile Include="..\..\silk\fixed\*.c">
- <ExcludedFromBuild>true</ExcludedFromBuild>
- </ClCompile>
- <ClCompile Include="..\..\silk\fixed\x86\*.c">
- <ExcludedFromBuild>true</ExcludedFromBuild>
- </ClCompile>
- <ClCompile Include="..\..\silk\float\*.c">
- <ExcludedFromBuild>false</ExcludedFromBuild>
- </ClCompile>
- </ItemGroup>
- </Otherwise>
- </Choose>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
-</Project>
diff --git a/win32/VS2015/opus.vcxproj.filters b/win32/VS2015/opus.vcxproj.filters
deleted file mode 100644
index 8c61d6a0..00000000
--- a/win32/VS2015/opus.vcxproj.filters
+++ /dev/null
@@ -1,585 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup>
- <Filter Include="Source Files">
- <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
- <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
- </Filter>
- <Filter Include="Header Files">
- <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
- <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
- </Filter>
- <Filter Include="Resource Files">
- <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
- <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
- </Filter>
- </ItemGroup>
- <ItemGroup>
- <ClInclude Include="..\..\celt\arch.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\celt.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\entdec.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\entenc.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\float_cast.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\os_support.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\stack_alloc.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\include\opus.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\include\opus_defines.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\include\opus_types.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\include\opus_multistream.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\include\opus_projection.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\win32\config.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\src\analysis.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\src\mapping_matrix.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\src\mlp.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\src\opus_private.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\src\tansig_table.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\x86\x86cpu.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\_kiss_fft_guts.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\bands.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\celt_lpc.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\x86\celt_lpc_sse.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\cwrs.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\ecintrin.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\entcode.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\fixed_c5x.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\fixed_c6x.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\fixed_debug.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\fixed_generic.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\kiss_fft.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\laplace.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\mathops.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\mdct.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\mfrngcod.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\modes.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\pitch.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\x86\pitch_sse.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\quant_bands.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\rate.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\static_modes_fixed.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\static_modes_float.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\vq.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\typedef.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\API.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\control.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\debug.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\define.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\errors.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\Inlines.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\MacroCount.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\MacroDebug.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\macros.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\main.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\x86\main_sse.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\pitch_est_defines.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\PLC.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\resampler_private.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\resampler_rom.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\resampler_structs.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\structs.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\tables.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\tuning_parameters.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\float\main_FLP.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\float\SigProc_FLP.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\silk\float\structs_FLP.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- <ClInclude Include="..\..\celt\x86\vq_sse.h">
- <Filter>Header Files</Filter>
- </ClInclude>
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="..\..\src\analysis.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\bands.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\celt.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\celt_decoder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\celt_encoder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\celt_lpc.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\x86\celt_lpc_sse4_1.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\cwrs.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\entcode.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\entdec.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\entenc.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\kiss_fft.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\laplace.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\mapping_matrix.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\mathops.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\mdct.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\mlp.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\mlp_data.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\modes.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\opus.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\opus_compare.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\opus_decoder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\opus_encoder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\opus_multistream.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\opus_multistream_decoder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\opus_multistream_encoder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\opus_projection_decoder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\opus_projection_encoder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\pitch.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\x86\pitch_sse.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\x86\pitch_sse2.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\x86\pitch_sse4_1.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\quant_bands.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\rate.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\src\repacketizer.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\vq.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\x86\x86_celt_map.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\x86\x86cpu.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\A2NLSF.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\ana_filt_bank_1.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\biquad_alt.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\bwexpander.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\bwexpander_32.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\check_control_input.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\CNG.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\code_signs.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\control_audio_bandwidth.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\control_codec.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\control_SNR.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\debug.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\dec_API.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\decode_core.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\decode_frame.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\decode_indices.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\decode_parameters.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\decode_pitch.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\decode_pulses.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\decoder_set_fs.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\enc_API.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\encode_indices.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\encode_pulses.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\gain_quant.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\HP_variable_cutoff.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\init_decoder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\init_encoder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\inner_prod_aligned.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\interpolate.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\lin2log.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\log2lin.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\LP_variable_cutoff.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\LPC_analysis_filter.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\LPC_inv_pred_gain.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\NLSF_decode.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\NLSF_del_dec_quant.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\NLSF_encode.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\NLSF_stabilize.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\NLSF_unpack.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\NLSF_VQ.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\NLSF_VQ_weights_laroia.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\NLSF2A.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\NSQ.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\NSQ_del_dec.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\x86\NSQ_del_dec_sse4_1.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\x86\NSQ_sse4_1.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\pitch_est_tables.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\PLC.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\process_NLSFs.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\quant_LTP_gains.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\resampler.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\resampler_down2.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\resampler_down2_3.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\resampler_private_AR2.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\resampler_private_down_FIR.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\resampler_private_IIR_FIR.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\resampler_private_up2_HQ.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\resampler_rom.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\shell_coder.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\sigm_Q15.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\sort.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\stereo_decode_pred.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\stereo_encode_pred.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\stereo_find_predictor.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\stereo_LR_to_MS.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\stereo_MS_to_LR.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\stereo_quant_pred.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\sum_sqr_shift.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\table_LSF_cos.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\tables_gain.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\tables_LTP.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\tables_NLSF_CB_NB_MB.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\tables_NLSF_CB_WB.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\tables_other.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\tables_pitch_lag.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\tables_pulses_per_block.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\VAD.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\x86\VAD_sse4_1.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\VQ_WMat_EC.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\x86\VQ_WMat_EC_sse4_1.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\x86\x86_silk_map.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\silk\LPC_fit.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\celt\x86\vq_sse2.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- </ItemGroup>
-</Project>
diff --git a/win32/VS2015/opus_demo.vcxproj b/win32/VS2015/opus_demo.vcxproj
deleted file mode 100644
index f4344e53..00000000
--- a/win32/VS2015/opus_demo.vcxproj
+++ /dev/null
@@ -1,171 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="DebugDLL_fixed|Win32">
- <Configuration>DebugDLL_fixed</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL_fixed|x64">
- <Configuration>DebugDLL_fixed</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL|Win32">
- <Configuration>DebugDLL</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL|x64">
- <Configuration>DebugDLL</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL_fixed|Win32">
- <Configuration>ReleaseDLL_fixed</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL_fixed|x64">
- <Configuration>ReleaseDLL_fixed</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL|Win32">
- <Configuration>ReleaseDLL</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL|x64">
- <Configuration>ReleaseDLL</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="opus.vcxproj">
- <Project>{219ec965-228a-1824-174d-96449d05f88a}</Project>
- </ProjectReference>
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="..\..\src\opus_demo.c" />
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <ProjectGuid>{016C739D-6389-43BF-8D88-24B2BF6F620F}</ProjectGuid>
- <Keyword>Win32Proj</Keyword>
- <RootNamespace>opus_demo</RootNamespace>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <PropertyGroup Label="UserMacros" />
- <ItemDefinitionGroup />
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
-</Project> \ No newline at end of file
diff --git a/win32/VS2015/opus_demo.vcxproj.filters b/win32/VS2015/opus_demo.vcxproj.filters
deleted file mode 100644
index 2eb113ac..00000000
--- a/win32/VS2015/opus_demo.vcxproj.filters
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup>
- <Filter Include="Source Files">
- <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
- <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
- </Filter>
- <Filter Include="Header Files">
- <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
- <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
- </Filter>
- <Filter Include="Resource Files">
- <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
- <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
- </Filter>
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="..\..\src\opus_demo.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- </ItemGroup>
-</Project> \ No newline at end of file
diff --git a/win32/VS2015/test_opus_api.vcxproj b/win32/VS2015/test_opus_api.vcxproj
deleted file mode 100644
index 7cae131b..00000000
--- a/win32/VS2015/test_opus_api.vcxproj
+++ /dev/null
@@ -1,171 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="DebugDLL_fixed|Win32">
- <Configuration>DebugDLL_fixed</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL_fixed|x64">
- <Configuration>DebugDLL_fixed</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL|Win32">
- <Configuration>DebugDLL</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL|x64">
- <Configuration>DebugDLL</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL_fixed|Win32">
- <Configuration>ReleaseDLL_fixed</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL_fixed|x64">
- <Configuration>ReleaseDLL_fixed</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL|Win32">
- <Configuration>ReleaseDLL</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL|x64">
- <Configuration>ReleaseDLL</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="..\..\tests\test_opus_api.c" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="opus.vcxproj">
- <Project>{219ec965-228a-1824-174d-96449d05f88a}</Project>
- </ProjectReference>
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <ProjectGuid>{1D257A17-D254-42E5-82D6-1C87A6EC775A}</ProjectGuid>
- <Keyword>Win32Proj</Keyword>
- <RootNamespace>test_opus_api</RootNamespace>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <PropertyGroup Label="UserMacros" />
- <ItemDefinitionGroup />
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
-</Project> \ No newline at end of file
diff --git a/win32/VS2015/test_opus_api.vcxproj.filters b/win32/VS2015/test_opus_api.vcxproj.filters
deleted file mode 100644
index 383d19f7..00000000
--- a/win32/VS2015/test_opus_api.vcxproj.filters
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup>
- <Filter Include="Source Files">
- <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
- <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
- </Filter>
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="..\..\tests\test_opus_api.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- </ItemGroup>
-</Project> \ No newline at end of file
diff --git a/win32/VS2015/test_opus_decode.vcxproj b/win32/VS2015/test_opus_decode.vcxproj
deleted file mode 100644
index df01dca1..00000000
--- a/win32/VS2015/test_opus_decode.vcxproj
+++ /dev/null
@@ -1,171 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="DebugDLL_fixed|Win32">
- <Configuration>DebugDLL_fixed</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL_fixed|x64">
- <Configuration>DebugDLL_fixed</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL|Win32">
- <Configuration>DebugDLL</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL|x64">
- <Configuration>DebugDLL</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL_fixed|Win32">
- <Configuration>ReleaseDLL_fixed</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL_fixed|x64">
- <Configuration>ReleaseDLL_fixed</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL|Win32">
- <Configuration>ReleaseDLL</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL|x64">
- <Configuration>ReleaseDLL</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="..\..\tests\test_opus_decode.c" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="opus.vcxproj">
- <Project>{219ec965-228a-1824-174d-96449d05f88a}</Project>
- </ProjectReference>
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <ProjectGuid>{8578322A-1883-486B-B6FA-E0094B65C9F2}</ProjectGuid>
- <Keyword>Win32Proj</Keyword>
- <RootNamespace>test_opus_api</RootNamespace>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <PropertyGroup Label="UserMacros" />
- <ItemDefinitionGroup />
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
-</Project> \ No newline at end of file
diff --git a/win32/VS2015/test_opus_decode.vcxproj.filters b/win32/VS2015/test_opus_decode.vcxproj.filters
deleted file mode 100644
index 3036a4e7..00000000
--- a/win32/VS2015/test_opus_decode.vcxproj.filters
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup>
- <Filter Include="Source Files">
- <UniqueIdentifier>{4a0dd677-931f-4728-afe5-b761149fc7eb}</UniqueIdentifier>
- <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
- </Filter>
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="..\..\tests\test_opus_decode.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- </ItemGroup>
-</Project> \ No newline at end of file
diff --git a/win32/VS2015/test_opus_encode.vcxproj b/win32/VS2015/test_opus_encode.vcxproj
deleted file mode 100644
index 405efee9..00000000
--- a/win32/VS2015/test_opus_encode.vcxproj
+++ /dev/null
@@ -1,172 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup Label="ProjectConfigurations">
- <ProjectConfiguration Include="DebugDLL_fixed|Win32">
- <Configuration>DebugDLL_fixed</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL_fixed|x64">
- <Configuration>DebugDLL_fixed</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL|Win32">
- <Configuration>DebugDLL</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="DebugDLL|x64">
- <Configuration>DebugDLL</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|Win32">
- <Configuration>Debug</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Debug|x64">
- <Configuration>Debug</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL_fixed|Win32">
- <Configuration>ReleaseDLL_fixed</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL_fixed|x64">
- <Configuration>ReleaseDLL_fixed</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL|Win32">
- <Configuration>ReleaseDLL</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="ReleaseDLL|x64">
- <Configuration>ReleaseDLL</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|Win32">
- <Configuration>Release</Configuration>
- <Platform>Win32</Platform>
- </ProjectConfiguration>
- <ProjectConfiguration Include="Release|x64">
- <Configuration>Release</Configuration>
- <Platform>x64</Platform>
- </ProjectConfiguration>
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="..\..\tests\opus_encode_regressions.c" />
- <ClCompile Include="..\..\tests\test_opus_encode.c" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="opus.vcxproj">
- <Project>{219ec965-228a-1824-174d-96449d05f88a}</Project>
- </ProjectReference>
- </ItemGroup>
- <PropertyGroup Label="Globals">
- <ProjectGuid>{84DAA768-1A38-4312-BB61-4C78BB59E5B8}</ProjectGuid>
- <Keyword>Win32Proj</Keyword>
- <RootNamespace>test_opus_api</RootNamespace>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|Win32'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|x64'" Label="Configuration">
- <ConfigurationType>Application</ConfigurationType>
- <PlatformToolset>v140</PlatformToolset>
- </PropertyGroup>
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
- <ImportGroup Label="ExtensionSettings">
- </ImportGroup>
- <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugDLL_fixed|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|Win32'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLL_fixed|x64'" Label="PropertySheets">
- <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
- <Import Project="common.props" />
- </ImportGroup>
- <PropertyGroup Label="UserMacros" />
- <ItemDefinitionGroup />
- <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
- <ImportGroup Label="ExtensionTargets">
- </ImportGroup>
-</Project> \ No newline at end of file
diff --git a/win32/VS2015/test_opus_encode.vcxproj.filters b/win32/VS2015/test_opus_encode.vcxproj.filters
deleted file mode 100644
index 4ed3bb9e..00000000
--- a/win32/VS2015/test_opus_encode.vcxproj.filters
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <ItemGroup>
- <Filter Include="Source Files">
- <UniqueIdentifier>{546c8d9a-103e-4f78-972b-b44e8d3c8aba}</UniqueIdentifier>
- <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
- </Filter>
- </ItemGroup>
- <ItemGroup>
- <ClCompile Include="..\..\tests\test_opus_encode.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- <ClCompile Include="..\..\tests\opus_encode_regressions.c">
- <Filter>Source Files</Filter>
- </ClCompile>
- </ItemGroup>
-</Project> \ No newline at end of file
diff --git a/win32/genversion.bat b/win32/genversion.bat
deleted file mode 100644
index 1def7460..00000000
--- a/win32/genversion.bat
+++ /dev/null
@@ -1,37 +0,0 @@
-@echo off
-
-setlocal enableextensions enabledelayedexpansion
-
-for /f %%v in ('cd "%~dp0.." ^&^& git status ^>NUL 2^>NUL ^&^& git describe --tags --match "v*" --dirty 2^>NUL') do set version=%%v
-
-if not "%version%"=="" set version=!version:~1! && goto :gotversion
-
-if exist "%~dp0..\package_version" goto :getversion
-
-echo Git cannot be found, nor can package_version. Generating unknown version.
-
-set version=unknown
-
-goto :gotversion
-
-:getversion
-
-for /f "delims== tokens=2" %%v in (%~dps0..\package_version) do set version=%%v
-set version=!version:"=!
-
-:gotversion
-
-set version=!version: =!
-set version_out=#define %~2 "%version%"
-
-echo %version_out%> "%~1_temp"
-
-echo n | comp "%~1_temp" "%~1" > NUL 2> NUL
-
-if not errorlevel 1 goto exit
-
-copy /y "%~1_temp" "%~1"
-
-:exit
-
-del "%~1_temp"