File size: 2,878 Bytes
76db545
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
"""
Field noise augmentation for West African farm environments.
Mixes clean speech with tractor, wind, and livestock audio samples.
Degrades gracefully to Gaussian noise when no .wav files are present.
"""
from __future__ import annotations

import logging
from pathlib import Path

import numpy as np

logger = logging.getLogger(__name__)


class FieldNoiseAugmenter:
    """
    Applies audiomentations transforms that simulate noisy field conditions.
    If the noise_dir has no .wav files, falls back to Gaussian noise only.
    """

    def __init__(self, noise_dir: str, config: dict) -> None:
        self.noise_dir = Path(noise_dir)
        self.config = config
        self._compose = None
        self._gaussian_only = False
        self._build_pipeline()

    def _build_pipeline(self) -> None:
        try:
            from audiomentations import (
                AddBackgroundNoise,
                AddGaussianNoise,
                Compose,
                RoomSimulator,
                TimeStretch,
            )
        except ImportError:
            logger.warning("audiomentations not installed — augmentation disabled.")
            self._compose = None
            return

        snr_range = self.config.get("audio", {}).get("noise_snr_db_range", [5, 20])
        prob = self.config.get("audio", {}).get("augmentation_prob", 0.6)

        wav_files = list(self.noise_dir.glob("*.wav")) if self.noise_dir.exists() else []

        transforms = []

        if wav_files:
            transforms.append(
                AddBackgroundNoise(
                    sounds_path=str(self.noise_dir),
                    min_snr_db=float(snr_range[0]),
                    max_snr_db=float(snr_range[1]),
                    p=prob,
                )
            )
            logger.info("FieldNoiseAugmenter: loaded %d noise files from %s", len(wav_files), self.noise_dir)
        else:
            logger.warning(
                "FieldNoiseAugmenter: no .wav files found in %s — using Gaussian noise only. "
                "Populate noise_samples/ for realistic field augmentation.",
                self.noise_dir,
            )
            self._gaussian_only = True

        transforms += [
            AddGaussianNoise(min_amplitude=0.001, max_amplitude=0.015, p=0.3),
            TimeStretch(min_rate=0.9, max_rate=1.1, leave_length_unchanged=True, p=0.2),
            RoomSimulator(p=0.3),
        ]

        self._compose = Compose(transforms)

    def augment(self, audio: np.ndarray, sr: int) -> np.ndarray:
        """Apply augmentation pipeline to a float32 audio array."""
        if self._compose is None:
            return audio
        return self._compose(samples=audio, sample_rate=sr)

    def is_ready(self) -> bool:
        """Returns True if augmentation is available (even Gaussian-only)."""
        return self._compose is not None