def separate_channels(data, fmt, channels): """Create a list of arrays of audio samples (`array.array` objects), one for each channel. Parameters ---------- data : bytes multi-channel audio data to mix down. fmt : str format (single character) to pass to `array.array` to convert `data` into an array of samples. This should be "b" if audio data's sample width is 1, "h" if it's 2 and "i" if it's 4. channels : int number of channels of audio data. Returns ------- channels_arr : list list of audio channels, each as a standard `array.array`. """ all_channels = array_(fmt, data) mono_channels = [ array_(fmt, all_channels[ch::channels]) for ch in range(channels) ] return mono_channels
def compute_average_channel(data, fmt, channels): """ Compute and return average channel of multi-channel audio data. If the number of channels is 2, use :func:`compute_average_channel_stereo` (much faster). This function uses satandard `array` module to convert `bytes` data into an array of numeric values. Parameters ---------- data : bytes multi-channel audio data to mix down. fmt : str format (single character) to pass to `array.array` to convert `data` into an array of samples. This should be "b" if audio data's sample width is 1, "h" if it's 2 and "i" if it's 4. channels : int number of channels of audio data. Returns ------- mono_audio : bytes mixed down audio data. """ all_channels = array_(fmt, data) mono_channels = [ array_(fmt, all_channels[ch::channels]) for ch in range(channels) ] avg_arr = array_( fmt, (round(sum(samples) / channels) for samples in zip(*mono_channels)), ) return avg_arr
def test_make_channel_selector_one_channel(self, sample_width, channels, selected, expected): # force using signal functions with standard python implementation with patch("auditok.util.signal", signal_): selector = make_channel_selector(sample_width, channels, selected) result = selector(self.data) fmt = signal_.FORMAT[sample_width] expected = array_(fmt, expected) if channels == 1: expected = bytes(expected) self.assertEqual(result, expected) # Use signal functions with numpy implementation with patch("auditok.util.signal", signal_numpy): selector = make_channel_selector(sample_width, channels, selected) result_numpy = selector(self.data) expected = array_(fmt, expected) if channels == 1: expected = bytes(expected) self.assertEqual(result_numpy, expected) else: self.assertTrue(all(result_numpy == expected))
def test_to_array(self, sample_width, expected): if isinstance(expected[0], list): channels = len(expected) expected = [ array_(signal_.FORMAT[sample_width], xi) for xi in expected ] else: channels = 1 expected = array_(signal_.FORMAT[sample_width], expected) resutl = signal_.to_array(self.data, sample_width, channels) resutl_numpy = signal_numpy.to_array(self.data, sample_width, channels) self.assertEqual(resutl, expected) self.assertTrue((resutl_numpy == np.asarray(expected)).all()) self.assertEqual(resutl_numpy.dtype, np.float64)
def test_compute_average_channel(self, fmt, channels, expected): result = signal_.compute_average_channel(self.data, fmt, channels) expected = array_(fmt, expected) expected_numpy_fmt = self.numpy_fmt[fmt] self.assertEqual(result, expected) result_numpy = signal_numpy.compute_average_channel( self.data, self.numpy_fmt[fmt], channels) self.assertTrue(all(result_numpy == expected)) self.assertEqual(result_numpy.dtype, expected_numpy_fmt)
def test_extract_single_channel(self, fmt, channels, selected, expected): resutl = signal_.extract_single_channel(self.data, fmt, channels, selected) expected = array_(fmt, expected) expected_numpy_fmt = self.numpy_fmt[fmt] self.assertEqual(resutl, expected) resutl_numpy = signal_numpy.extract_single_channel( self.data, self.numpy_fmt[fmt], channels, selected) self.assertTrue(all(resutl_numpy == expected)) self.assertEqual(resutl_numpy.dtype, expected_numpy_fmt)
def test_calculate_energy_multichannel(self, x, sample_width, aggregation_fn, expected): x = [array_(signal_.FORMAT[sample_width], xi) for xi in x] energy = signal_.calculate_energy_multichannel(x, sample_width, aggregation_fn) self.assertEqual(energy, expected) energy = signal_numpy.calculate_energy_multichannel( x, sample_width, aggregation_fn) self.assertEqual(energy, expected)
def test_to_array(self, sample_width, expected): channels = len(expected) expected = [ array_(signal_.FORMAT[sample_width], xi) for xi in expected ] result = signal_.to_array(self.data, sample_width, channels) result_numpy = signal_numpy.to_array(self.data, sample_width, channels) self.assertEqual(result, expected) self.assertTrue((result_numpy == np.asarray(expected)).all()) self.assertEqual(result_numpy.dtype, np.float64)
def test_separate_channels(self, fmt, channels, expected): resutl = signal_.separate_channels(self.data, fmt, channels) expected = [array_(fmt, exp) for exp in expected] expected_numpy_fmt = self.numpy_fmt[fmt] self.assertEqual(resutl, expected) resutl_numpy = signal_numpy.separate_channels(self.data, self.numpy_fmt[fmt], channels) self.assertTrue((resutl_numpy == expected).all()) self.assertEqual(resutl_numpy.dtype, expected_numpy_fmt)
def test_audio_energy_validator(self, data, channels, use_channel, expected): data = array_("h", data) sample_width = 2 energy_threshold = 50 validator = AudioEnergyValidator(energy_threshold, sample_width, channels, use_channel) if expected: self.assertTrue(validator.is_valid(data)) else: self.assertFalse(validator.is_valid(data))
def compute_average_channel_stereo(data, sample_width): """Compute and return average channel of stereo audio data. This function should be used when the number of channels is exactly 2 because in that case we can use standard `audioop` module which *much* faster then calling :func:`compute_average_channel`. Parameters ---------- data : bytes 2-channel audio data to mix down. sample_width : int size in bytes of one audio sample (one channel considered). Returns ------- mono_audio : bytes mixed down audio data. """ fmt = FORMAT[sample_width] arr = array_(fmt, audioop.tomono(data, sample_width, 0.5, 0.5)) return arr
def to_array(data, sample_width, channels): """Extract individual channels of audio data and return a list of arrays of numeric samples. This will always return a list of `array.array` objects (one per channel) even if audio data is mono. Parameters ---------- data : bytes raw audio data. sample_width : int size in bytes of one audio sample (one channel considered). Returns ------- samples_arrays : list list of arrays of audio samples. """ fmt = FORMAT[sample_width] if channels == 1: return [array_(fmt, data)] return separate_channels(data, fmt, channels)
def _generate_pure_tone(frequency, duration_sec=1, sampling_rate=16000, sample_width=2, volume=1e4): """ Generates a pure tone with the given frequency. """ assert frequency <= sampling_rate / 2 max_value = (2**(sample_width * 8) // 2) - 1 if volume > max_value: volume = max_value fmt = signal_.FORMAT[sample_width] total_samples = int(sampling_rate * duration_sec) step = frequency / sampling_rate two_pi_step = 2 * math.pi * step data = array_( fmt, (int(math.sin(two_pi_step * i) * volume) for i in range(total_samples)), ) return data
def extract_single_channel(data, fmt, channels, selected): samples = array_(fmt, data) return samples[selected::channels]
def test_calculate_energy_single_channel(self, x, sample_width, expected): x = array_(signal_.FORMAT[sample_width], x) energy = signal_.calculate_energy_single_channel(x, sample_width) self.assertEqual(energy, expected) energy = signal_numpy.calculate_energy_single_channel(x, sample_width) self.assertEqual(energy, expected)
def test_compute_average_channel_stereo(self, sample_width, expected): result = signal_.compute_average_channel_stereo( self.data, sample_width) fmt = signal_.FORMAT[sample_width] expected = array_(fmt, expected) self.assertEqual(result, expected)