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