teamml-audio-augment 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. teamml_audio_augment-0.1.0/LICENSE +7 -0
  2. teamml_audio_augment-0.1.0/PKG-INFO +58 -0
  3. teamml_audio_augment-0.1.0/README.md +33 -0
  4. teamml_audio_augment-0.1.0/pyproject.toml +41 -0
  5. teamml_audio_augment-0.1.0/setup.cfg +4 -0
  6. teamml_audio_augment-0.1.0/teamml_audio_augment/__init__.py +17 -0
  7. teamml_audio_augment-0.1.0/teamml_audio_augment/advanced/__init__.py +4 -0
  8. teamml_audio_augment-0.1.0/teamml_audio_augment/advanced/esc_background_noise.py +70 -0
  9. teamml_audio_augment-0.1.0/teamml_audio_augment/advanced/location_based_rir.py +55 -0
  10. teamml_audio_augment-0.1.0/teamml_audio_augment/advanced/mad_background_noise.py +103 -0
  11. teamml_audio_augment-0.1.0/teamml_audio_augment/advanced/realistic_background_noise.py +55 -0
  12. teamml_audio_augment-0.1.0/teamml_audio_augment/augmentations/add_background_noise.py +133 -0
  13. teamml_audio_augment-0.1.0/teamml_audio_augment/augmentations/add_gaussian_snr.py +80 -0
  14. teamml_audio_augment-0.1.0/teamml_audio_augment/augmentations/air_absorption.py +243 -0
  15. teamml_audio_augment-0.1.0/teamml_audio_augment/augmentations/apply_impulse_response.py +118 -0
  16. teamml_audio_augment-0.1.0/teamml_audio_augment/augmentations/gain.py +73 -0
  17. teamml_audio_augment-0.1.0/teamml_audio_augment/augmentations/high_pass_filter.py +167 -0
  18. teamml_audio_augment-0.1.0/teamml_audio_augment/augmentations/infrasound.py +162 -0
  19. teamml_audio_augment-0.1.0/teamml_audio_augment/augmentations/limiter.py +165 -0
  20. teamml_audio_augment-0.1.0/teamml_audio_augment/augmentations/normalize.py +62 -0
  21. teamml_audio_augment-0.1.0/teamml_audio_augment/augmentations/shift.py +194 -0
  22. teamml_audio_augment-0.1.0/teamml_audio_augment/core/__init__.py +0 -0
  23. teamml_audio_augment-0.1.0/teamml_audio_augment/core/_advanced.py +122 -0
  24. teamml_audio_augment-0.1.0/teamml_audio_augment/core/composition.py +249 -0
  25. teamml_audio_augment-0.1.0/teamml_audio_augment/core/transforms_interface.py +106 -0
  26. teamml_audio_augment-0.1.0/teamml_audio_augment/core/utils.py +150 -0
  27. teamml_audio_augment-0.1.0/teamml_audio_augment.egg-info/PKG-INFO +58 -0
  28. teamml_audio_augment-0.1.0/teamml_audio_augment.egg-info/SOURCES.txt +29 -0
  29. teamml_audio_augment-0.1.0/teamml_audio_augment.egg-info/dependency_links.txt +1 -0
  30. teamml_audio_augment-0.1.0/teamml_audio_augment.egg-info/requires.txt +9 -0
  31. teamml_audio_augment-0.1.0/teamml_audio_augment.egg-info/top_level.txt +1 -0
@@ -0,0 +1,7 @@
1
+ Copyright © 2026 Certus Innovations
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: teamml_audio_augment
3
+ Version: 0.1.0
4
+ Summary: Audio augmentations for TEAM-ML models and data
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/Stonewall-Defense/audio-augment
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Natural Language :: English
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.12
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: audiomlspectools>=0.11.2
17
+ Requires-Dist: numpy>=2.5.1
18
+ Requires-Dist: numpy-audio-limiter>=0.2.0
19
+ Requires-Dist: scipy>=1.18.0
20
+ Requires-Dist: tinytag>=2.2.1
21
+ Requires-Dist: torch>=2.10.0
22
+ Provides-Extra: demo
23
+ Requires-Dist: streamlit>=1.59.2; extra == "demo"
24
+ Dynamic: license-file
25
+
26
+ # Audio Augmentations
27
+
28
+ New takes on audio augmentations for TEAM-ML models and data. If you are just stumbling across this package, you should probably use [Audiomentations](https://github.com/iver56/audiomentations) or [torch-audiomentations](https://github.com/iver56/torch-audiomentations).
29
+
30
+ ## Prerequisites
31
+
32
+ - Python 3.14 runtime
33
+ - Pip for package installation
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ # Pip is the classic
39
+ pip install teamml_audio_augment
40
+
41
+ # Should also work ith `uv`
42
+ uv add teamml_audio_augment
43
+ ```
44
+
45
+ ## Credit Where Credit is Due
46
+
47
+ - In the [`AirAbsorption` class](teamml_audio_augment/augmentations/air_absorption.py), the `_fft_freq` function is adapted from [`librosa`](https://librosa.org/doc/latest/generated/librosa.fft_frequencies.html)
48
+ - In the [`Infrasound` class](teamml_audio_augment/augmentations/infrasound.py), the `_oscillator_bank` function is adapted from [`torchaudio`](https://docs.pytorch.org/audio/2.7.0/generated/torchaudio.prototype.functional.oscillator_bank.html)
49
+ - In [_advanced.py](teamml_audio_augment/core/_advanced.py), the functions are adapted from several advanced and/or compiled libraries:
50
+ - The `rms` function is adapted from [`numpy-rms`](https://pypi.org/project/numpy-rms/)
51
+ - The `minmax` function is adapted from [`numpy-minmax`](https://pypi.org/project/numpy-minmax/)
52
+ - The `_next_fast_len` function is adapted from [`scipy.signal`](https://docs.scipy.org/doc/scipy/reference/signal.html)
53
+
54
+ Our contributions include everything in [advanced](teamml_audio_augment/advanced/) and the `Infrasound` class.
55
+
56
+ ## License
57
+
58
+ MIT.
@@ -0,0 +1,33 @@
1
+ # Audio Augmentations
2
+
3
+ New takes on audio augmentations for TEAM-ML models and data. If you are just stumbling across this package, you should probably use [Audiomentations](https://github.com/iver56/audiomentations) or [torch-audiomentations](https://github.com/iver56/torch-audiomentations).
4
+
5
+ ## Prerequisites
6
+
7
+ - Python 3.14 runtime
8
+ - Pip for package installation
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ # Pip is the classic
14
+ pip install teamml_audio_augment
15
+
16
+ # Should also work ith `uv`
17
+ uv add teamml_audio_augment
18
+ ```
19
+
20
+ ## Credit Where Credit is Due
21
+
22
+ - In the [`AirAbsorption` class](teamml_audio_augment/augmentations/air_absorption.py), the `_fft_freq` function is adapted from [`librosa`](https://librosa.org/doc/latest/generated/librosa.fft_frequencies.html)
23
+ - In the [`Infrasound` class](teamml_audio_augment/augmentations/infrasound.py), the `_oscillator_bank` function is adapted from [`torchaudio`](https://docs.pytorch.org/audio/2.7.0/generated/torchaudio.prototype.functional.oscillator_bank.html)
24
+ - In [_advanced.py](teamml_audio_augment/core/_advanced.py), the functions are adapted from several advanced and/or compiled libraries:
25
+ - The `rms` function is adapted from [`numpy-rms`](https://pypi.org/project/numpy-rms/)
26
+ - The `minmax` function is adapted from [`numpy-minmax`](https://pypi.org/project/numpy-minmax/)
27
+ - The `_next_fast_len` function is adapted from [`scipy.signal`](https://docs.scipy.org/doc/scipy/reference/signal.html)
28
+
29
+ Our contributions include everything in [advanced](teamml_audio_augment/advanced/) and the `Infrasound` class.
30
+
31
+ ## License
32
+
33
+ MIT.
@@ -0,0 +1,41 @@
1
+ [project]
2
+ name = "teamml_audio_augment"
3
+ version = "0.1.0"
4
+
5
+ description = "Audio augmentations for TEAM-ML models and data"
6
+ readme = "README.md"
7
+ requires-python = ">=3.12"
8
+ classifiers = [
9
+ "Development Status :: 3 - Alpha",
10
+ "Environment :: Console",
11
+ "Intended Audience :: Developers",
12
+ "Natural Language :: English",
13
+ "Operating System :: OS Independent",
14
+ "Programming Language :: Python :: 3",
15
+ ]
16
+ license = "MIT"
17
+ license-files = ["LICENSE*"]
18
+
19
+ dependencies = [
20
+ "audiomlspectools>=0.11.2",
21
+ "numpy>=2.5.1",
22
+ "numpy-audio-limiter>=0.2.0",
23
+ "scipy>=1.18.0",
24
+ "tinytag>=2.2.1",
25
+ "torch>=2.10.0",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ demo = [
30
+ "streamlit>=1.59.2",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/Stonewall-Defense/audio-augment"
35
+
36
+ [build-system]
37
+ requires = ["setuptools"]
38
+ build-backend = "setuptools.build_meta"
39
+
40
+ [tool.setuptools.packages.find]
41
+ include = ["teamml_audio_augment*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,17 @@
1
+ from .augmentations.add_background_noise import AddBackgroundNoise
2
+ from .augmentations.add_gaussian_snr import AddGaussianSNR
3
+ from .augmentations.air_absorption import AirAbsorption
4
+ from .augmentations.apply_impulse_response import ApplyImpulseResponse
5
+ from .augmentations.gain import Gain
6
+ from .augmentations.high_pass_filter import HighPassFilter
7
+ from .augmentations.infrasound import Infrasound
8
+ from .augmentations.limiter import Limiter
9
+ from .augmentations.normalize import Normalize
10
+ from .augmentations.shift import Shift
11
+
12
+ from .core.composition import Compose, OneOf, SomeOf
13
+
14
+ from .advanced.esc_background_noise import create_esc_augment, EscClass
15
+ from .advanced.location_based_rir import make_location_rir, RirLocation, RIR_LOC
16
+ from .advanced.mad_background_noise import make_mad_bg_single_class, make_mad_bg_multi_class, make_mad_bg_exclude, MadClass, MAD_CLASS
17
+ from .advanced.realistic_background_noise import make_background_noise_source, BackgroundNoise, BG_NOISE_TYPE
@@ -0,0 +1,4 @@
1
+ from .esc_background_noise import create_esc_augment
2
+ from .location_based_rir import make_location_rir, RirLocation
3
+ from .mad_background_noise import make_mad_bg_exclude, make_mad_bg_multi_class, make_mad_bg_single_class, MadClass
4
+ from .realistic_background_noise import make_background_noise_source, BackgroundNoise
@@ -0,0 +1,70 @@
1
+ ###############################################################################
2
+ # Global Imports
3
+ ###############################################################################
4
+ from enum import Enum
5
+ import os
6
+ import random
7
+ import re
8
+ from typing import Optional
9
+
10
+ ###############################################################################
11
+ # Local Imports
12
+ ###############################################################################
13
+ from teamml_audio_augment.augmentations.add_background_noise import AddBackgroundNoise
14
+
15
+
16
+ ###############################################################################
17
+ # Enums
18
+ ###############################################################################
19
+ class EscClass(Enum):
20
+ ANIMALS = 0
21
+ NATURAL_SOUNDSCAPES = 10
22
+ HUMAN_NONSPEECH = 20
23
+ INTERIOR_DOMESTIC = 30
24
+ EXTERIOR_URBAN = 40
25
+
26
+
27
+ ###############################################################################
28
+ # Constants
29
+ ###############################################################################
30
+ ESC_REGEX = re.compile(r'^\d+-\w+-[A-Z]-(?P<class_id>\d+)\.wav$')
31
+
32
+
33
+ ###############################################################################
34
+ # Helpers
35
+ ###############################################################################
36
+ def _file_matches_classes(filename: str, classes: list[EscClass]) -> bool:
37
+ matches = re.match(ESC_REGEX, filename)
38
+ if not matches:
39
+ return False
40
+
41
+ class_id = int(matches.group("class_id"))
42
+
43
+ for c in classes:
44
+ min_val = c.value
45
+ max_val = min_val + 9
46
+ if min_val <= class_id <= max_val:
47
+ return True
48
+
49
+ return False
50
+
51
+
52
+ ###############################################################################
53
+ # ! EXPORTS
54
+ ###############################################################################
55
+ def create_esc_augment(path_to_esc50: str,
56
+ classes: list[EscClass],
57
+ *,
58
+ sample_rate: Optional[int] = None,
59
+ min_snr_db: float = 3.0,
60
+ max_snr_db: float = 30.0,
61
+ p: float = 0.5,
62
+ max_samples: int = 1_000,
63
+ ) -> AddBackgroundNoise:
64
+ classes = classes or [val for val in EscClass]
65
+
66
+ audio_files = [os.path.join(path_to_esc50, f) for f in os.listdir(path_to_esc50) if f.endswith(".wav") if _file_matches_classes(f, classes)]
67
+ random.shuffle(audio_files)
68
+ audio_files = audio_files[:max_samples]
69
+
70
+ return AddBackgroundNoise(audio_files, sample_rate=sample_rate, min_snr_db=min_snr_db, max_snr_db=max_snr_db, p=p)
@@ -0,0 +1,55 @@
1
+ ###############################################################################
2
+ # Global Imports
3
+ ###############################################################################
4
+ from enum import Enum
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Literal, Optional
8
+
9
+ ###############################################################################
10
+ # 3PP Imports
11
+ ###############################################################################
12
+ import pandas as pd
13
+
14
+ ###############################################################################
15
+ # Local Imports
16
+ ###############################################################################
17
+ from teamml_audio_augment.augmentations.apply_impulse_response import ApplyImpulseResponse
18
+
19
+
20
+ ###############################################################################
21
+ # Enums
22
+ ###############################################################################
23
+ class RirLocation(Enum):
24
+ INDOOR = "indoor"
25
+ OUTDOOR = "outdoor"
26
+
27
+
28
+ RIR_LOC = Literal[
29
+ "indoor",
30
+ "outdoor",
31
+ ]
32
+
33
+
34
+ ###############################################################################
35
+ # Helpers
36
+ ###############################################################################
37
+ def _filter(metadata: pd.DataFrame, location: RirLocation | RIR_LOC):
38
+ loc = location.value if isinstance(location, RirLocation) else location
39
+ return [f for f in list(metadata.loc[metadata["class_name"] == loc]["filename"])]
40
+
41
+
42
+ ###############################################################################
43
+ # ! EXPORTS
44
+ ###############################################################################
45
+ def make_location_rir(path_to_dataset: str | Path,
46
+ rir_location: RirLocation | RIR_LOC,
47
+ *,
48
+ sample_rate: Optional[int] = None,
49
+ p: float = 0.5,
50
+ cahce_size: int = 128,
51
+ ):
52
+ fq_metadata_file = os.path.join(path_to_dataset, "metadata.csv")
53
+ metadata = pd.read_csv(fq_metadata_file)
54
+ filtered = [os.path.join(path_to_dataset, "data", f) for f in _filter(metadata, rir_location)]
55
+ return ApplyImpulseResponse(filtered, sample_rate=sample_rate, p=p, lru_cache_size=cahce_size)
@@ -0,0 +1,103 @@
1
+ ###############################################################################
2
+ # Global Imports
3
+ ###############################################################################
4
+ from enum import Enum
5
+ import os
6
+ from pathlib import Path
7
+ import random
8
+ import re
9
+ from typing import Literal, Optional
10
+
11
+ ###############################################################################
12
+ # Local Imports
13
+ ###############################################################################
14
+ from teamml_audio_augment.augmentations.add_background_noise import AddBackgroundNoise
15
+
16
+
17
+ ###############################################################################
18
+ # Enums
19
+ ###############################################################################
20
+ class MadClass(Enum):
21
+ COMMUNICATION = "communications"
22
+ GUNSHOT = "gunshot"
23
+ FOOTSTEPS = "footsteps"
24
+ SHELLING = "shelling"
25
+ VEHICLE = "vehicle"
26
+ HELICOPTER = "helicopter"
27
+ FIGHTER = "fighter"
28
+
29
+
30
+ MAD_CLASS = Literal[
31
+ "communications",
32
+ "gunshot",
33
+ "footsteps",
34
+ "shelling",
35
+ "vehicle",
36
+ "helicopter",
37
+ "fighter",
38
+ ]
39
+
40
+
41
+ ###############################################################################
42
+ # Helpers
43
+ ###############################################################################
44
+ def _make_mad_bg_common(mad_files: list[str],
45
+ sample_rate: Optional[int],
46
+ min_snr_db: float,
47
+ max_snr_db: float,
48
+ p: float,
49
+ max_samples: int,
50
+ ):
51
+ random.shuffle(mad_files)
52
+ mad_files = mad_files[:max_samples]
53
+
54
+ return AddBackgroundNoise(mad_files, sample_rate=sample_rate, min_snr_db=min_snr_db, max_snr_db=max_snr_db, p=p)
55
+
56
+
57
+ ###############################################################################
58
+ # ! EXPORTS
59
+ ###############################################################################
60
+ def make_mad_bg_single_class(path_to_mad: str | Path,
61
+ mad_class: MadClass | MAD_CLASS,
62
+ *,
63
+ sample_rate: Optional[int] = None,
64
+ min_snr_db: float = 3.0,
65
+ max_snr_db: float = 30.0,
66
+ p: float = 0.5,
67
+ max_samples: int = 10_000,
68
+ ):
69
+ mc = mad_class.value if isinstance(mad_class, MadClass) else mad_class
70
+
71
+ mad_files = [os.path.join(path_to_mad, f) for f in os.listdir(path_to_mad) if f.endswith(".wav") and mc in f]
72
+ return _make_mad_bg_common(mad_files, sample_rate, min_snr_db, max_snr_db, p, max_samples)
73
+
74
+
75
+ def make_mad_bg_exclude(path_to_mad: str | Path,
76
+ class_to_exclude: MadClass | MAD_CLASS,
77
+ *,
78
+ sample_rate: Optional[int] = None,
79
+ min_snr_db: float = 3.0,
80
+ max_snr_db: float = 30.0,
81
+ p: float = 0.5,
82
+ max_samples: int = 10_000,
83
+ ):
84
+ cte = class_to_exclude.value if isinstance(class_to_exclude, MadClass) else class_to_exclude
85
+
86
+ mad_files = [os.path.join(path_to_mad, f) for f in os.listdir(path_to_mad) if f.endswith(".wav") and cte not in f]
87
+ return _make_mad_bg_common(mad_files, sample_rate, min_snr_db, max_snr_db, p, max_samples)
88
+
89
+
90
+ def make_mad_bg_multi_class(path_to_mad: str | Path,
91
+ mad_classes: list[MadClass] | list[MAD_CLASS],
92
+ *,
93
+ sample_rate: Optional[int] = None,
94
+ min_snr_db: float = 3.0,
95
+ max_snr_db: float = 30.0,
96
+ p: float = 0.5,
97
+ max_samples: int = 10_000,
98
+ ):
99
+ mc = [mad.value if isinstance(mad, MadClass) else mad for mad in mad_classes]
100
+
101
+ pattern = re.compile(f"(?:{'|'.join(c[:3] for c in mc)}).+.wav")
102
+ mad_files = [os.path.join(path_to_mad, f) for f in os.listdir(path_to_mad) if re.search(pattern, f)]
103
+ return _make_mad_bg_common(mad_files, sample_rate, min_snr_db, max_snr_db, p, max_samples)
@@ -0,0 +1,55 @@
1
+ ###############################################################################
2
+ # Global Imports
3
+ ###############################################################################
4
+ from enum import Enum
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Literal, Optional
8
+
9
+ ###############################################################################
10
+ # 3PP Imports
11
+ ###############################################################################
12
+ import pandas as pd
13
+
14
+ ###############################################################################
15
+ # Local Imports
16
+ ###############################################################################
17
+ from teamml_audio_augment.augmentations.add_background_noise import AddBackgroundNoise
18
+
19
+
20
+ ###############################################################################
21
+ # Enums
22
+ ###############################################################################
23
+ class BackgroundNoise(Enum):
24
+ AMBIENT = "ambient"
25
+ ANIMAL = "animal"
26
+ HUMAN = "human"
27
+ VEHICLE = "vehicle"
28
+ WEATHER = "weather"
29
+
30
+
31
+ BG_NOISE_TYPE = Literal[
32
+ "ambient",
33
+ "animal",
34
+ "human",
35
+ "vehicle",
36
+ "weather",
37
+ ]
38
+
39
+
40
+ ###############################################################################
41
+ # ! EXPORTS
42
+ ###############################################################################
43
+ def make_background_noise_source(path_to_dataset: str | Path,
44
+ sound_class: BackgroundNoise | BG_NOISE_TYPE,
45
+ *,
46
+ sample_rate: Optional[int] = None,
47
+ min_snr_db: float = 3.0,
48
+ max_snr_db: float = 20.0,
49
+ p: float = 0.5,
50
+ ):
51
+ sc = sound_class.value if isinstance(sound_class, BackgroundNoise) else sound_class
52
+ fq_metadata_file = os.path.join(path_to_dataset, "metadata.csv")
53
+ metadata = pd.read_csv(fq_metadata_file)
54
+ filtered = [os.path.join(path_to_dataset, "data", f) for f in list(metadata.loc[metadata["class_name"] == sc]["filename"])]
55
+ return AddBackgroundNoise(filtered, sample_rate=sample_rate, min_snr_db=min_snr_db, max_snr_db=max_snr_db, p=p)
@@ -0,0 +1,133 @@
1
+ ###############################################################################
2
+ # Global Imports
3
+ ###############################################################################
4
+ import logging
5
+ import random
6
+ import warnings
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ ###############################################################################
11
+ # 3PP Imports
12
+ ###############################################################################
13
+ from tinytag import TinyTag
14
+ import torch
15
+
16
+ ###############################################################################
17
+ # Certus Imports
18
+ ###############################################################################
19
+ from AudioMlSpecTools import WavReader
20
+
21
+ ###############################################################################
22
+ # Local Imports
23
+ ###############################################################################
24
+ from teamml_audio_augment.core.transforms_interface import BaseWaveformTransform
25
+ from teamml_audio_augment.core.utils import (
26
+ calculate_desired_noise_rms,
27
+ calculate_rms,
28
+ find_audio_files_in_paths,
29
+ )
30
+
31
+
32
+ ###############################################################################
33
+ # Config
34
+ ###############################################################################
35
+ LOGGER = logging.getLogger("teamMl")
36
+
37
+
38
+ ###############################################################################
39
+ # Exports
40
+ ###############################################################################
41
+ class AddBackgroundNoise(BaseWaveformTransform):
42
+ def __init__(
43
+ self,
44
+ sounds_path: list[Path] | list[str] | Path | str,
45
+ *,
46
+ sample_rate: Optional[int] = None,
47
+ min_snr_db: float = 3.0,
48
+ max_snr_db: float = 30.0,
49
+ p: float = 0.5,
50
+ lru_cache_size: int | None = None,
51
+ ):
52
+ super().__init__(sample_rate, p=p)
53
+
54
+ # Boundary parameters
55
+ self.sounds_path = sounds_path
56
+ self.sound_file_paths = find_audio_files_in_paths(self.sounds_path)
57
+ self.sound_file_paths = [str(p) for p in self.sound_file_paths]
58
+
59
+ assert len(self.sound_file_paths) > 0
60
+
61
+ if min_snr_db > max_snr_db:
62
+ raise ValueError("min_snr_db must not be greater than max_snr_db")
63
+ self.min_snr_db = min_snr_db
64
+ self.max_snr_db = max_snr_db
65
+
66
+ # Changing parameters
67
+ self.snr_db = self.min_snr_db
68
+ self.noise_file_path = self.sound_file_paths[0]
69
+ self.offset = 0.0
70
+ self.duration = 0.0
71
+
72
+ # Helper parameters
73
+ if lru_cache_size is not None:
74
+ raise ValueError(
75
+ "Passing lru_cache_size is no longer supported, as the cache has been removed (since v0.43.0)."
76
+ )
77
+ self.time_info_arr = torch.full((len(self.sound_file_paths),), -1.0)
78
+
79
+ def randomize_parameters(self, samples: torch.Tensor):
80
+ super().randomize_parameters(samples)
81
+
82
+ if self.should_apply:
83
+ self.snr_db = random.uniform(self.min_snr_db, self.max_snr_db)
84
+
85
+ file_idx = random.randint(0, len(self.sound_file_paths) - 1)
86
+ self.noise_file_path = self.sound_file_paths[file_idx]
87
+
88
+ if self.time_info_arr[file_idx] == -1.0:
89
+ duration = TinyTag.get(self.noise_file_path).duration or 0.0
90
+ if duration == 0.0:
91
+ warnings.warn(f"Empty noise file: {self.noise_file_path}")
92
+ self.time_info_arr[file_idx] = duration
93
+
94
+ noise_duration = float(self.time_info_arr[file_idx])
95
+ signal_duration = samples.shape[-1] / self.sample_rate
96
+
97
+ min_noise_offset = 0.0
98
+ max_noise_offset = max(0.0, noise_duration - signal_duration)
99
+
100
+ self.offset = random.uniform(min_noise_offset, max_noise_offset)
101
+ self.duration = signal_duration
102
+
103
+ def apply(self, samples: torch.Tensor) -> torch.Tensor:
104
+ end_sec = self.offset + self.duration
105
+ noise_sound = WavReader(target_sr=self.sample_rate).read(self.noise_file_path, start_sec=self.offset, end_sec=end_sec)
106
+
107
+ noise_rms = calculate_rms(noise_sound)
108
+ if noise_rms < 1e-9:
109
+ warnings.warn(
110
+ "The file {} is too silent to be added as noise. Returning the input"
111
+ " unchanged.".format(self.noise_file_path)
112
+ )
113
+ return samples
114
+
115
+ clean_rms = calculate_rms(samples)
116
+
117
+ desired_noise_rms = calculate_desired_noise_rms(clean_rms, self.snr_db)
118
+
119
+ # Adjust the noise to match the desired noise RMS
120
+ noise_sound = noise_sound * (desired_noise_rms / noise_rms)
121
+
122
+ # Repeat the sound if it shorter than the input sound
123
+ num_samples = samples.shape[-1]
124
+ while noise_sound.shape[-1] < num_samples:
125
+ noise_sound = torch.concatenate((noise_sound, noise_sound), dim=-1)
126
+
127
+ if noise_sound.shape[-1] > num_samples:
128
+ noise_sound = noise_sound[..., 0:num_samples]
129
+
130
+ LOGGER.debug(f"Appied background noise @ {self.snr_db:.03f} dB")
131
+
132
+ # Return a mix of the input sound and the background noise sound
133
+ return samples + noise_sound
@@ -0,0 +1,80 @@
1
+ ###############################################################################
2
+ # Global Imports
3
+ ###############################################################################
4
+ import logging
5
+ import random
6
+ from typing import Optional
7
+
8
+ ###############################################################################
9
+ # 3PP Imports
10
+ ###############################################################################
11
+ import torch
12
+
13
+ ###############################################################################
14
+ # Local Imports
15
+ ###############################################################################
16
+ from teamml_audio_augment.core.transforms_interface import BaseWaveformTransform
17
+ from teamml_audio_augment.core.utils import (
18
+ calculate_desired_noise_rms,
19
+ calculate_rms,
20
+ )
21
+
22
+
23
+ ###############################################################################
24
+ # Config
25
+ ###############################################################################
26
+ LOGGER = logging.getLogger("teamMl")
27
+
28
+
29
+ ###############################################################################
30
+ # Exports
31
+ ###############################################################################
32
+ class AddGaussianSNR(BaseWaveformTransform):
33
+ """
34
+ Add gaussian noise to the input. A random Signal to Noise Ratio (SNR) will be picked
35
+ uniformly in the Decibel scale. This aligns with human hearing, which is more
36
+ logarithmic than linear.
37
+ """
38
+
39
+ supports_multichannel = True
40
+
41
+ def __init__(
42
+ self,
43
+ *,
44
+ sample_rate: Optional[int] = None,
45
+ min_snr_db: float = 5.0,
46
+ max_snr_db: float = 40.0,
47
+ p: float = 0.5,
48
+ ):
49
+ """
50
+ :param min_snr_db: Minimum signal-to-noise ratio in dB. A lower number means more noise.
51
+ :param max_snr_db: Maximum signal-to-noise ratio in dB. A greater number means less noise.
52
+ :param p: The probability of applying this transform
53
+ """
54
+ super().__init__(sample_rate, p=p)
55
+
56
+ if min_snr_db > max_snr_db:
57
+ raise ValueError("min_snr_db must not be greater than max_snr_db")
58
+ self.min_snr_db = min_snr_db
59
+ self.max_snr_db = max_snr_db
60
+
61
+ self.noise_std = 0.0
62
+
63
+ def randomize_parameters(self, samples: torch.Tensor):
64
+ super().randomize_parameters(samples)
65
+ if self.should_apply:
66
+ # Pick SNR in decibel scale
67
+ snr = random.uniform(self.min_snr_db, self.max_snr_db)
68
+
69
+ clean_rms = calculate_rms(samples)
70
+ noise_rms = calculate_desired_noise_rms(clean_rms=clean_rms, snr=snr)
71
+
72
+ # In gaussian noise, the RMS gets roughly equal to the std
73
+ self.noise_std = float(noise_rms)
74
+
75
+ def apply(self, samples: torch.Tensor) -> torch.Tensor:
76
+ noise = torch.normal(0.0, self.noise_std, size=samples.shape)
77
+
78
+ LOGGER.debug(f"Appied Gaussian SNR @ {self.noise_std:.03f}")
79
+
80
+ return samples + noise