• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
2#
3# Use of this source code is governed by a BSD-style license
4# that can be found in the LICENSE file in the root of the source
5# tree. An additional intellectual property rights grant can be found
6# in the file PATENTS.  All contributing project authors may
7# be found in the AUTHORS file in the root of the source tree.
8"""Unit tests for the signal_processing module.
9"""
10
11import unittest
12
13import numpy as np
14import pydub
15
16from . import exceptions
17from . import signal_processing
18
19
20class TestSignalProcessing(unittest.TestCase):
21    """Unit tests for the signal_processing module.
22  """
23
24    def testMixSignals(self):
25        # Generate a template signal with which white noise can be generated.
26        silence = pydub.AudioSegment.silent(duration=1000, frame_rate=48000)
27
28        # Generate two distinct AudioSegment instances with 1 second of white noise.
29        signal = signal_processing.SignalProcessingUtils.GenerateWhiteNoise(
30            silence)
31        noise = signal_processing.SignalProcessingUtils.GenerateWhiteNoise(
32            silence)
33
34        # Extract samples.
35        signal_samples = signal.get_array_of_samples()
36        noise_samples = noise.get_array_of_samples()
37
38        # Test target SNR -Inf (noise expected).
39        mix_neg_inf = signal_processing.SignalProcessingUtils.MixSignals(
40            signal, noise, -np.Inf)
41        self.assertTrue(len(noise), len(mix_neg_inf))  # Check duration.
42        mix_neg_inf_samples = mix_neg_inf.get_array_of_samples()
43        self.assertTrue(  # Check samples.
44            all([x == y for x, y in zip(noise_samples, mix_neg_inf_samples)]))
45
46        # Test target SNR 0.0 (different data expected).
47        mix_0 = signal_processing.SignalProcessingUtils.MixSignals(
48            signal, noise, 0.0)
49        self.assertTrue(len(signal), len(mix_0))  # Check duration.
50        self.assertTrue(len(noise), len(mix_0))
51        mix_0_samples = mix_0.get_array_of_samples()
52        self.assertTrue(
53            any([x != y for x, y in zip(signal_samples, mix_0_samples)]))
54        self.assertTrue(
55            any([x != y for x, y in zip(noise_samples, mix_0_samples)]))
56
57        # Test target SNR +Inf (signal expected).
58        mix_pos_inf = signal_processing.SignalProcessingUtils.MixSignals(
59            signal, noise, np.Inf)
60        self.assertTrue(len(signal), len(mix_pos_inf))  # Check duration.
61        mix_pos_inf_samples = mix_pos_inf.get_array_of_samples()
62        self.assertTrue(  # Check samples.
63            all([x == y for x, y in zip(signal_samples, mix_pos_inf_samples)]))
64
65    def testMixSignalsMinInfPower(self):
66        silence = pydub.AudioSegment.silent(duration=1000, frame_rate=48000)
67        signal = signal_processing.SignalProcessingUtils.GenerateWhiteNoise(
68            silence)
69
70        with self.assertRaises(exceptions.SignalProcessingException):
71            _ = signal_processing.SignalProcessingUtils.MixSignals(
72                signal, silence, 0.0)
73
74        with self.assertRaises(exceptions.SignalProcessingException):
75            _ = signal_processing.SignalProcessingUtils.MixSignals(
76                silence, signal, 0.0)
77
78    def testMixSignalNoiseDifferentLengths(self):
79        # Test signals.
80        shorter = signal_processing.SignalProcessingUtils.GenerateWhiteNoise(
81            pydub.AudioSegment.silent(duration=1000, frame_rate=8000))
82        longer = signal_processing.SignalProcessingUtils.GenerateWhiteNoise(
83            pydub.AudioSegment.silent(duration=2000, frame_rate=8000))
84
85        # When the signal is shorter than the noise, the mix length always equals
86        # that of the signal regardless of whether padding is applied.
87        # No noise padding, length of signal less than that of noise.
88        mix = signal_processing.SignalProcessingUtils.MixSignals(
89            signal=shorter,
90            noise=longer,
91            pad_noise=signal_processing.SignalProcessingUtils.MixPadding.
92            NO_PADDING)
93        self.assertEqual(len(shorter), len(mix))
94        # With noise padding, length of signal less than that of noise.
95        mix = signal_processing.SignalProcessingUtils.MixSignals(
96            signal=shorter,
97            noise=longer,
98            pad_noise=signal_processing.SignalProcessingUtils.MixPadding.
99            ZERO_PADDING)
100        self.assertEqual(len(shorter), len(mix))
101
102        # When the signal is longer than the noise, the mix length depends on
103        # whether padding is applied.
104        # No noise padding, length of signal greater than that of noise.
105        mix = signal_processing.SignalProcessingUtils.MixSignals(
106            signal=longer,
107            noise=shorter,
108            pad_noise=signal_processing.SignalProcessingUtils.MixPadding.
109            NO_PADDING)
110        self.assertEqual(len(shorter), len(mix))
111        # With noise padding, length of signal greater than that of noise.
112        mix = signal_processing.SignalProcessingUtils.MixSignals(
113            signal=longer,
114            noise=shorter,
115            pad_noise=signal_processing.SignalProcessingUtils.MixPadding.
116            ZERO_PADDING)
117        self.assertEqual(len(longer), len(mix))
118
119    def testMixSignalNoisePaddingTypes(self):
120        # Test signals.
121        shorter = signal_processing.SignalProcessingUtils.GenerateWhiteNoise(
122            pydub.AudioSegment.silent(duration=1000, frame_rate=8000))
123        longer = signal_processing.SignalProcessingUtils.GeneratePureTone(
124            pydub.AudioSegment.silent(duration=2000, frame_rate=8000), 440.0)
125
126        # Zero padding: expect pure tone only in 1-2s.
127        mix_zero_pad = signal_processing.SignalProcessingUtils.MixSignals(
128            signal=longer,
129            noise=shorter,
130            target_snr=-6,
131            pad_noise=signal_processing.SignalProcessingUtils.MixPadding.
132            ZERO_PADDING)
133
134        # Loop: expect pure tone plus noise in 1-2s.
135        mix_loop = signal_processing.SignalProcessingUtils.MixSignals(
136            signal=longer,
137            noise=shorter,
138            target_snr=-6,
139            pad_noise=signal_processing.SignalProcessingUtils.MixPadding.LOOP)
140
141        def Energy(signal):
142            samples = signal_processing.SignalProcessingUtils.AudioSegmentToRawData(
143                signal).astype(np.float32)
144            return np.sum(samples * samples)
145
146        e_mix_zero_pad = Energy(mix_zero_pad[-1000:])
147        e_mix_loop = Energy(mix_loop[-1000:])
148        self.assertLess(0, e_mix_zero_pad)
149        self.assertLess(e_mix_zero_pad, e_mix_loop)
150
151    def testMixSignalSnr(self):
152        # Test signals.
153        tone_low = signal_processing.SignalProcessingUtils.GeneratePureTone(
154            pydub.AudioSegment.silent(duration=64, frame_rate=8000), 250.0)
155        tone_high = signal_processing.SignalProcessingUtils.GeneratePureTone(
156            pydub.AudioSegment.silent(duration=64, frame_rate=8000), 3000.0)
157
158        def ToneAmplitudes(mix):
159            """Returns the amplitude of the coefficients #16 and #192, which
160         correspond to the tones at 250 and 3k Hz respectively."""
161            mix_fft = np.absolute(
162                signal_processing.SignalProcessingUtils.Fft(mix))
163            return mix_fft[16], mix_fft[192]
164
165        mix = signal_processing.SignalProcessingUtils.MixSignals(
166            signal=tone_low, noise=tone_high, target_snr=-6)
167        ampl_low, ampl_high = ToneAmplitudes(mix)
168        self.assertLess(ampl_low, ampl_high)
169
170        mix = signal_processing.SignalProcessingUtils.MixSignals(
171            signal=tone_high, noise=tone_low, target_snr=-6)
172        ampl_low, ampl_high = ToneAmplitudes(mix)
173        self.assertLess(ampl_high, ampl_low)
174
175        mix = signal_processing.SignalProcessingUtils.MixSignals(
176            signal=tone_low, noise=tone_high, target_snr=6)
177        ampl_low, ampl_high = ToneAmplitudes(mix)
178        self.assertLess(ampl_high, ampl_low)
179
180        mix = signal_processing.SignalProcessingUtils.MixSignals(
181            signal=tone_high, noise=tone_low, target_snr=6)
182        ampl_low, ampl_high = ToneAmplitudes(mix)
183        self.assertLess(ampl_low, ampl_high)
184