diff --git a/docs/source/functional.rst b/docs/source/functional.rst index 5f68666539..da5cae0516 100644 --- a/docs/source/functional.rst +++ b/docs/source/functional.rst @@ -113,6 +113,11 @@ Functions to perform common audio operations. .. autofunction:: treble_biquad +:hidden:`bass_biquad` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: bass_biquad + :hidden:`deemph_biquad` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/test/test_sox_compatibility.py b/test/test_sox_compatibility.py index 2c8394ff45..5239f56ae8 100644 --- a/test/test_sox_compatibility.py +++ b/test/test_sox_compatibility.py @@ -265,6 +265,28 @@ def test_treble(self): self.assertEqual(output_waveform, sox_output_waveform, atol=1e-4, rtol=1e-5) + @unittest.skipIf("sox" not in BACKENDS, "sox not available") + @AudioBackendScope("sox") + def test_bass(self): + """ + Test biquad bass filter, compare to SoX implementation + """ + + central_freq = 1000 + q = 0.707 + gain = 40 + + noise_filepath = common_utils.get_asset_path('whitenoise.wav') + E = torchaudio.sox_effects.SoxEffectsChain() + E.set_input_file(noise_filepath) + E.append_effect_to_chain("bass", [gain, central_freq, str(q) + 'q']) + sox_output_waveform, sr = E.sox_build_flow_effects() + + waveform, sample_rate = torchaudio.load(noise_filepath, normalization=True) + output_waveform = F.bass_biquad(waveform, sample_rate, gain, central_freq, q) + + self.assertEqual(output_waveform, sox_output_waveform, atol=1.5e-4, rtol=1e-5) + @unittest.skipIf("sox" not in BACKENDS, "sox not available") @AudioBackendScope("sox") def test_deemph(self): diff --git a/test/torchscript_consistency_impl.py b/test/torchscript_consistency_impl.py index 4ee9149843..b8967be41a 100644 --- a/test/torchscript_consistency_impl.py +++ b/test/torchscript_consistency_impl.py @@ -367,6 +367,21 @@ def func(tensor): self._assert_consistency(func, waveform) + def test_bass(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + gain = 40. + central_freq = 1000. + q = 0.707 + return F.bass_biquad(tensor, sample_rate, gain, central_freq, q) + + self._assert_consistency(func, waveform) + def test_deemph(self): if self.dtype == torch.float64: raise unittest.SkipTest("This test is known to fail for float64") diff --git a/torchaudio/functional.py b/torchaudio/functional.py index 94ff81526a..8491e6dafc 100644 --- a/torchaudio/functional.py +++ b/torchaudio/functional.py @@ -29,6 +29,7 @@ "equalizer_biquad", "band_biquad", "treble_biquad", + "bass_biquad", "deemph_biquad", "riaa_biquad", "biquad", @@ -983,6 +984,47 @@ def treble_biquad( return biquad(waveform, b0, b1, b2, a0, a1, a2) +def bass_biquad( + waveform: Tensor, + sample_rate: int, + gain: float, + central_freq: float = 100, + Q: float = 0.707 +) -> Tensor: + r"""Design a bass tone-control effect. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + gain (float): desired gain at the boost (or attenuation) in dB. + central_freq (float, optional): central frequency (in Hz). (Default: ``100``) + Q (float, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``). + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + References: + http://sox.sourceforge.net/sox.html + https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF + """ + w0 = 2 * math.pi * central_freq / sample_rate + alpha = math.sin(w0) / 2 / Q + A = math.exp(gain / 40 * math.log(10)) + + temp1 = 2 * math.sqrt(A) * alpha + temp2 = (A - 1) * math.cos(w0) + temp3 = (A + 1) * math.cos(w0) + + b0 = A * ((A + 1) - temp2 + temp1) + b1 = 2 * A * ((A - 1) - temp3) + b2 = A * ((A + 1) - temp2 - temp1) + a0 = (A + 1) + temp2 + temp1 + a1 = -2 * ((A - 1) + temp3) + a2 = (A + 1) + temp2 - temp1 + + return biquad(waveform, b0 / a0, b1 / a0, b2 / a0, a0 / a0, a1 / a0, a2 / a0) + + def deemph_biquad( waveform: Tensor, sample_rate: int