From 76804965316385d4e270d98f941a9eefd079d0ba Mon Sep 17 00:00:00 2001 From: Brian McFee Date: Tue, 20 Feb 2018 20:29:24 -0500 Subject: [PATCH 01/10] implemented PCEN. fixes #615. needs tests --- librosa/core/__init__.py | 2 + librosa/core/spectrum.py | 149 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/librosa/core/__init__.py b/librosa/core/__init__.py index 4a676cb2f9..d9b8ccffe5 100644 --- a/librosa/core/__init__.py +++ b/librosa/core/__init__.py @@ -51,6 +51,8 @@ perceptual_weighting A_weighting + pcen + Time and frequency conversion ----------------------------- .. autosummary:: diff --git a/librosa/core/spectrum.py b/librosa/core/spectrum.py index 2e746d1970..480c75d525 100644 --- a/librosa/core/spectrum.py +++ b/librosa/core/spectrum.py @@ -6,6 +6,7 @@ import numpy as np import scipy.fftpack as fft import scipy +import scipy.ndimage import scipy.signal import scipy.interpolate import six @@ -23,7 +24,7 @@ 'perceptual_weighting', 'power_to_db', 'db_to_power', 'amplitude_to_db', 'db_to_amplitude', - 'fmt'] + 'fmt', 'pcen'] @cache(level=20) @@ -1281,6 +1282,152 @@ def fmt(y, t_min=0.5, n_fmt=None, kind='cubic', beta=0.5, over_sample=1, axis=-1 return result[idx] * np.sqrt(n) / n_fmt +@cache(level=30) +def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, + time_constant=0.395, eps=1e-6, b=None, max_size=1): + '''Per-channel energy normalization (PCEN) [1]_ + + This function normalizes a time-frequency representation `S` by + performing automatic gain control, followed by non-linear compression: + + P = (S / (eps + M)**gain + bias)**power - bias**power + + where `M` is the low-pass filtered output of `S`: + + M[f, t] = (1 - b) M[f, t - 1] + b * S[f, t] + + and if `b` is not provided, it is calculated as: + + b = 1 - exp(-hop_length / (sr * time_constant)) + + This normalization is designed to suppress background noise, and + emphasize foreground signals, and can be used as an alternative to + decibel scaling (`amplitude_to_db`). + + This implementation also supports frequency-bin smoothing by specifying + `max_size > 1`. If this option is used, the filtered spectrogram `M` is + computed as + + M[f, t] = (1 - b) M[f, t - 1] + b * R[f, t] + + where `R` has been max-filtered along the frequency axis, similar to + the Superflux algorithm implemented in `onset.onset_strength`: + + R[f, t] = max(R[f - max_size//2: f + max_size//2, t]) + + This can be used to perform automatic gain control on signals that cross + or span multiple frequency bans, which may be desirable for spectrograms + with high frequency resolution. + + .. [1] Wang, Y., Getreuer, P., Hughes, T., Lyon, R. F., & Saurous, R. A. + (2017, March). Trainable frontend for robust and far-field keyword spotting. + In Acoustics, Speech and Signal Processing (ICASSP), 2017 + IEEE International Conference on (pp. 5670-5674). IEEE. + + Parameters + ---------- + S : np.ndarray (non-negative) [shape=(n, m)] + input spectrogram + + sr : number > 0 [scalar] + Sampling rate of audio + + hop_length : int > 0 [scalar] + Hop length of `S` + + gain : number > 0 [scalar] + Gain factor. Typical values should be slightly less than 1. + + bias : number >= 0 [scalar] + The bias point of the non-linear compression + + power : number > 0 [scalar] + The compression factor. Typical values should be between 0 and 1. + + time_constant : number > 0 [scalar] + The time constant for IIR filtering, measured in seconds. + + eps : number > 0 [scalar] + A small constant used to ensure numerical stability of the filter. + + b : number in [0, 1] [scalar] + The filter coefficient for the low-pass filter. + If not provided, it will be inferred from `time_constant`. + + max_size : int > 0 [scalar] + The size of the frequency axis max filter. + If left as `1`, no filtering is performed. + + Returns + ------- + P : np.ndarray, non-negative [shape=(n, m)] + The per-channel energy normalized version of `S`. + + See Also + -------- + amplitude_to_db + librosa.onset.onset_strength + + Examples + -------- + + Compare PCEN to log amplitude (dB) scaling on Mel spectra + + >>> import matplotlib.pyplot as plt + >>> y, sr = librosa.load(librosa.util.example_audio_file(), + ... offset=10, duration=10) + + >>> # We'll use power=1 to get a magnitude spectrum, + >>> # instead of a power spectrum + >>> S = librosa.feature.melspectrogram(y, sr=sr, power=1) + >>> log_S = librosa.amplitude_to_db(S, ref=np.max) + >>> pcen_S = librosa.pcen(S) + >>> plt.figure() + >>> plt.subplot(2,1,1) + >>> librosa.display.specshow(log_S, x_axis='time', y_axis='mel') + >>> plt.title('log amplitude (dB)') + >>> plt.colorbar() + >>> plt.subplot(2,1,2) + >>> librosa.display.specshow(pcen_S, x_axis='time', y_axis='mel') + >>> plt.title('Per-channel energy normalization') + >>> plt.colorbar() + >>> plt.tight_layout() + + Compare PCEN with and without max-filtering + + >>> pcen_max = librosa.pcen(S, max_size=5) + >>> plt.figure() + >>> plt.subplot(2,1,1) + >>> librosa.display.specshow(pcen_S, x_axis='time', y_axis='mel') + >>> plt.title('Per-channel energy normalization (no max-filter)') + >>> plt.colorbar() + >>> plt.subplot(2,1,2) + >>> librosa.display.specshow(pcen_max, x_axis='time', y_axis='mel') + >>> plt.title('Per-channel energy normalization (max_size=5)') + >>> plt.colorbar() + >>> plt.tight_layout() + + ''' + if np.issubdtype(S.dtype, np.complexfloating): + warnings.warn('pcen was called on complex input so phase ' + 'information will be discarded. To suppress this warning, ' + 'call pcen(magphase(D)[0]) instead.') + + S = np.abs(S) + + if max_size == 1: + ref_spec = S + else: + ref_spec = scipy.ndimage.maximum_filter1d(S, max_size, axis=0) + + if b is None: + b = 1 - np.exp(- float(hop_length) / (time_constant * sr)) + + smooth = (eps + scipy.signal.lfilter([b], [1, b - 1], ref_spec))**gain + + return (S / smooth + bias)**power - bias**power + + def _spectrogram(y=None, S=None, n_fft=2048, hop_length=512, power=1): '''Helper function to retrieve a magnitude spectrogram. From c31f10042e64da499c8c98aa82040934bc2a6a90 Mon Sep 17 00:00:00 2001 From: Brian McFee Date: Wed, 21 Feb 2018 08:10:28 -0500 Subject: [PATCH 02/10] updated pcen docs [ci skip] --- librosa/core/spectrum.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/librosa/core/spectrum.py b/librosa/core/spectrum.py index 480c75d525..bcf6e57f03 100644 --- a/librosa/core/spectrum.py +++ b/librosa/core/spectrum.py @@ -1292,26 +1292,27 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, P = (S / (eps + M)**gain + bias)**power - bias**power - where `M` is the low-pass filtered output of `S`: + where `M` is the result of applying a low-pass, temporal IIR filter + to `S`: - M[f, t] = (1 - b) M[f, t - 1] + b * S[f, t] + M[f, t] = (1 - b) * M[f, t - 1] + b * S[f, t] - and if `b` is not provided, it is calculated as: + If `b` is not provided, it is calculated as: b = 1 - exp(-hop_length / (sr * time_constant)) - This normalization is designed to suppress background noise, and + This normalization is designed to suppress background noise and emphasize foreground signals, and can be used as an alternative to decibel scaling (`amplitude_to_db`). - This implementation also supports frequency-bin smoothing by specifying - `max_size > 1`. If this option is used, the filtered spectrogram `M` is - computed as + This implementation also supports smoothing across frequency bins + by specifying `max_size > 1`. If this option is used, the filtered + spectrogram `M` is computed as + + M[f, t] = (1 - b) * M[f, t - 1] + b * R[f, t] - M[f, t] = (1 - b) M[f, t - 1] + b * R[f, t] - where `R` has been max-filtered along the frequency axis, similar to - the Superflux algorithm implemented in `onset.onset_strength`: + the SuperFlux algorithm implemented in `onset.onset_strength`: R[f, t] = max(R[f - max_size//2: f + max_size//2, t]) @@ -1327,22 +1328,23 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, Parameters ---------- S : np.ndarray (non-negative) [shape=(n, m)] - input spectrogram + The input (magnitude) spectrogram sr : number > 0 [scalar] - Sampling rate of audio + The audio sampling rate hop_length : int > 0 [scalar] - Hop length of `S` + The hop length of `S`, expressed in samples gain : number > 0 [scalar] - Gain factor. Typical values should be slightly less than 1. + The gain factor. Typical values should be slightly less than 1. bias : number >= 0 [scalar] - The bias point of the non-linear compression + The bias point of the nonlinear compression (default: 2) power : number > 0 [scalar] - The compression factor. Typical values should be between 0 and 1. + The compression exponent. Typical values should be between 0 and 1. + Smaller values of `power` result in stronger compression. time_constant : number > 0 [scalar] The time constant for IIR filtering, measured in seconds. @@ -1355,7 +1357,7 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, If not provided, it will be inferred from `time_constant`. max_size : int > 0 [scalar] - The size of the frequency axis max filter. + The width of the max filter applied to the frequency axis. If left as `1`, no filtering is performed. Returns @@ -1377,7 +1379,7 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, >>> y, sr = librosa.load(librosa.util.example_audio_file(), ... offset=10, duration=10) - >>> # We'll use power=1 to get a magnitude spectrum, + >>> # We'll use power=1 to get a magnitude spectrum >>> # instead of a power spectrum >>> S = librosa.feature.melspectrogram(y, sr=sr, power=1) >>> log_S = librosa.amplitude_to_db(S, ref=np.max) From aa0a8af76992961a55319d8f8c5befd6c17419c7 Mon Sep 17 00:00:00 2001 From: Brian McFee Date: Wed, 21 Feb 2018 08:14:20 -0500 Subject: [PATCH 03/10] updated pcen docs [ci skip] --- librosa/core/spectrum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/librosa/core/spectrum.py b/librosa/core/spectrum.py index bcf6e57f03..d32b5e0d02 100644 --- a/librosa/core/spectrum.py +++ b/librosa/core/spectrum.py @@ -1288,7 +1288,7 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, '''Per-channel energy normalization (PCEN) [1]_ This function normalizes a time-frequency representation `S` by - performing automatic gain control, followed by non-linear compression: + performing automatic gain control, followed by nonlinear compression: P = (S / (eps + M)**gain + bias)**power - bias**power From 78b45444cb80f22fa25972724906c785aa04a84e Mon Sep 17 00:00:00 2001 From: Brian McFee Date: Wed, 21 Feb 2018 08:18:33 -0500 Subject: [PATCH 04/10] updated pcen docs [ci skip] --- librosa/core/spectrum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/librosa/core/spectrum.py b/librosa/core/spectrum.py index d32b5e0d02..d054d0861c 100644 --- a/librosa/core/spectrum.py +++ b/librosa/core/spectrum.py @@ -1314,7 +1314,7 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, where `R` has been max-filtered along the frequency axis, similar to the SuperFlux algorithm implemented in `onset.onset_strength`: - R[f, t] = max(R[f - max_size//2: f + max_size//2, t]) + R[f, t] = max(S[f - max_size//2: f + max_size//2, t]) This can be used to perform automatic gain control on signals that cross or span multiple frequency bans, which may be desirable for spectrograms From bce9d596910846cf934ddca005204ece5866d308 Mon Sep 17 00:00:00 2001 From: Brian McFee Date: Wed, 21 Feb 2018 09:10:09 -0500 Subject: [PATCH 05/10] added parameter bound checks in pcen [ci skip] --- librosa/core/spectrum.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/librosa/core/spectrum.py b/librosa/core/spectrum.py index d054d0861c..b62b016d6e 100644 --- a/librosa/core/spectrum.py +++ b/librosa/core/spectrum.py @@ -1290,7 +1290,7 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, This function normalizes a time-frequency representation `S` by performing automatic gain control, followed by nonlinear compression: - P = (S / (eps + M)**gain + bias)**power - bias**power + P[f, t] = (S / (eps + M[f, t])**gain + bias)**power - bias**power where `M` is the result of applying a low-pass, temporal IIR filter to `S`: @@ -1336,7 +1336,7 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, hop_length : int > 0 [scalar] The hop length of `S`, expressed in samples - gain : number > 0 [scalar] + gain : number >= 0 [scalar] The gain factor. Typical values should be slightly less than 1. bias : number >= 0 [scalar] @@ -1397,7 +1397,7 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, Compare PCEN with and without max-filtering - >>> pcen_max = librosa.pcen(S, max_size=5) + >>> pcen_max = librosa.pcen(S, max_size=3) >>> plt.figure() >>> plt.subplot(2,1,1) >>> librosa.display.specshow(pcen_S, x_axis='time', y_axis='mel') @@ -1405,26 +1405,48 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, >>> plt.colorbar() >>> plt.subplot(2,1,2) >>> librosa.display.specshow(pcen_max, x_axis='time', y_axis='mel') - >>> plt.title('Per-channel energy normalization (max_size=5)') + >>> plt.title('Per-channel energy normalization (max_size=3)') >>> plt.colorbar() >>> plt.tight_layout() ''' + + if power <= 0: + raise ParameterError('power={} must be strictly positive'.format(power)) + + if gain < 0: + raise ParameterError('gain={} must be non-negative'.format(gain)) + + if bias < 0: + raise ParameterError('bias={} must be non-negative'.format(bias)) + + if eps <= 0: + raise ParameterError('eps={} must be strictly positive'.format(eps)) + + if time_constant <= 0: + raise ParameterError('time_constant={} must be strictly positive'.format(time_constant)) + + if max_size <= 0 or not isinstance(max_size, int): + raise ParameterError('max_size={} must be a positive integer'.format(max_size)) + + if b is None: + b = 1 - np.exp(- float(hop_length) / (time_constant * sr)) + + if not 0 <= b <= 1: + raise ParameterError('b={} must be between 0 and 1'.format(b)) + if np.issubdtype(S.dtype, np.complexfloating): warnings.warn('pcen was called on complex input so phase ' 'information will be discarded. To suppress this warning, ' 'call pcen(magphase(D)[0]) instead.') - S = np.abs(S) + S = np.abs(S) if max_size == 1: ref_spec = S else: ref_spec = scipy.ndimage.maximum_filter1d(S, max_size, axis=0) - if b is None: - b = 1 - np.exp(- float(hop_length) / (time_constant * sr)) - smooth = (eps + scipy.signal.lfilter([b], [1, b - 1], ref_spec))**gain return (S / smooth + bias)**power - bias**power From d5d6f79e59c8cac195708f5c02e73e21cd05f3ad Mon Sep 17 00:00:00 2001 From: Brian McFee Date: Wed, 21 Feb 2018 09:55:25 -0500 Subject: [PATCH 06/10] added unit tests for pcen --- librosa/core/spectrum.py | 2 +- tests/test_core.py | 55 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/librosa/core/spectrum.py b/librosa/core/spectrum.py index b62b016d6e..1ca2f8a28c 100644 --- a/librosa/core/spectrum.py +++ b/librosa/core/spectrum.py @@ -1426,7 +1426,7 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, if time_constant <= 0: raise ParameterError('time_constant={} must be strictly positive'.format(time_constant)) - if max_size <= 0 or not isinstance(max_size, int): + if max_size < 1 or not isinstance(max_size, int): raise ParameterError('max_size={} must be a positive integer'.format(max_size)) if b is None: diff --git a/tests/test_core.py b/tests/test_core.py index f4f9491889..4f0fee1cde 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1283,3 +1283,58 @@ def test_iirt(): mut = librosa.iirt(y, hop_length=2205, win_length=4410) assert np.allclose(mut, gt[23:108, :mut.shape[1]], atol=1.8) + + +def test_pcen(): + + def __test(gain, bias, power, b, time_constant, eps, ms, S, Pexp): + + P = librosa.pcen(S, gain=gain, bias=bias, power=power, + time_constant=time_constant, eps=eps, b=b, + max_size=ms) + + assert P.shape == S.shape + assert np.all(P >= 0) + assert np.all(np.isfinite(P)) + + assert np.allclose(P, Pexp) + + tf = raises(librosa.ParameterError)(__test) + + srand() + S = np.abs(np.random.randn(10, 50)) + + # Bounds tests (failures): + # gain < 0 + yield tf, -1, 1, 1, 0.5, 0.5, 1e-6, 1, S, S + + # bias < 0 + yield tf, 1, -1, 1, 0.5, 0.5, 1e-6, 1, S, S + + # power <= 0 + yield tf, 1, 1, 0, 0.5, 0.5, 1e-6, 1, S, S + + # b < 0 + yield tf, 1, 1, 1, -2, 0.5, 1e-6, 1, S, S + + # b > 1 + yield tf, 1, 1, 1, 2, 0.5, 1e-6, 1, S, S + + # time_constant <= 0 + yield tf, 1, 1, 1, 0.5, -2, 1e-6, 1, S, S + + # eps <= 0 + yield tf, 1, 1, 1, 0.5, 0.5, 0, 1, S, S + # max_size not int, < 1 + yield tf, 1, 1, 1, 0.5, 0.5, 0, 1.5, S, S + yield tf, 1, 1, 1, 0.5, 0.5, 0, 0, S, S + + # Edge cases: + # gain=0, bias=0, power=p, b=1 => S**p + for p in [0.5, 1, 2]: + yield __test, 0, 0, p, 1.0, 0.5, 1e-6, 1, S, S**p + + # gain=1, bias=0, power=1, b=1, eps=1e-20 => ones + yield __test, 1, 0, 1, 1.0, 0.5, 1e-20, 1, S, np.ones_like(S) + + From 2bf74f11ddeeb308a4b4b5c371563ed1b932590b Mon Sep 17 00:00:00 2001 From: Brian McFee Date: Wed, 21 Feb 2018 09:57:16 -0500 Subject: [PATCH 07/10] added unit tests for pcen --- tests/test_core.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 4f0fee1cde..6aebd67515 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1297,12 +1297,13 @@ def __test(gain, bias, power, b, time_constant, eps, ms, S, Pexp): assert np.all(P >= 0) assert np.all(np.isfinite(P)) - assert np.allclose(P, Pexp) + if Pexp is not None: + assert np.allclose(P, Pexp) tf = raises(librosa.ParameterError)(__test) srand() - S = np.abs(np.random.randn(10, 50)) + S = np.abs(np.random.randn(9, 30)) # Bounds tests (failures): # gain < 0 @@ -1337,4 +1338,7 @@ def __test(gain, bias, power, b, time_constant, eps, ms, S, Pexp): # gain=1, bias=0, power=1, b=1, eps=1e-20 => ones yield __test, 1, 0, 1, 1.0, 0.5, 1e-20, 1, S, np.ones_like(S) + # zeros to zeros + Z = np.zeros_like(S) + yield __test, 0.98, 2.0, 0.5, None, 0.395, 1e-6, 1, Z, Z From 561cac6bfc81e0e8e7e7a96689353efccfcb486d Mon Sep 17 00:00:00 2001 From: Brian McFee Date: Wed, 21 Feb 2018 10:26:11 -0500 Subject: [PATCH 08/10] improved test coverage on pcen --- tests/test_core.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 6aebd67515..8db8850c73 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1289,9 +1289,17 @@ def test_pcen(): def __test(gain, bias, power, b, time_constant, eps, ms, S, Pexp): - P = librosa.pcen(S, gain=gain, bias=bias, power=power, - time_constant=time_constant, eps=eps, b=b, - max_size=ms) + warnings.resetwarnings() + warnings.simplefilter('always') + with warnings.catch_warnings(record=True) as out: + + P = librosa.pcen(S, gain=gain, bias=bias, power=power, + time_constant=time_constant, eps=eps, b=b, + max_size=ms) + + if np.issubdtype(S.dtype, np.complexfloating): + assert len(out) > 0 + assert 'complex' in str(out[0].message).lower() assert P.shape == S.shape assert np.all(P >= 0) @@ -1326,9 +1334,10 @@ def __test(gain, bias, power, b, time_constant, eps, ms, S, Pexp): # eps <= 0 yield tf, 1, 1, 1, 0.5, 0.5, 0, 1, S, S + # max_size not int, < 1 - yield tf, 1, 1, 1, 0.5, 0.5, 0, 1.5, S, S - yield tf, 1, 1, 1, 0.5, 0.5, 0, 0, S, S + yield tf, 1, 1, 1, 0.5, 0.5, 1e-6, 1.5, S, S + yield tf, 1, 1, 1, 0.5, 0.5, 1e-6, 0, S, S # Edge cases: # gain=0, bias=0, power=p, b=1 => S**p @@ -1338,7 +1347,10 @@ def __test(gain, bias, power, b, time_constant, eps, ms, S, Pexp): # gain=1, bias=0, power=1, b=1, eps=1e-20 => ones yield __test, 1, 0, 1, 1.0, 0.5, 1e-20, 1, S, np.ones_like(S) + # Catch the complex warning + yield __test, 1, 0, 1, 1.0, 0.5, 1e-20, 1, S * 1.j, np.ones_like(S) + # zeros to zeros Z = np.zeros_like(S) yield __test, 0.98, 2.0, 0.5, None, 0.395, 1e-6, 1, Z, Z - + yield __test, 0.98, 2.0, 0.5, None, 0.395, 1e-6, 3, Z, Z From c961016d7519e920c067a3283caccf2e5b6a2f85 Mon Sep 17 00:00:00 2001 From: Brian McFee Date: Wed, 21 Feb 2018 10:58:51 -0500 Subject: [PATCH 09/10] speedup and stability for pcen --- librosa/core/spectrum.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/librosa/core/spectrum.py b/librosa/core/spectrum.py index 1ca2f8a28c..9053596018 100644 --- a/librosa/core/spectrum.py +++ b/librosa/core/spectrum.py @@ -1447,9 +1447,11 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, else: ref_spec = scipy.ndimage.maximum_filter1d(S, max_size, axis=0) - smooth = (eps + scipy.signal.lfilter([b], [1, b - 1], ref_spec))**gain + S_smooth = scipy.signal.lfilter([b], [1, b - 1], ref_spec) - return (S / smooth + bias)**power - bias**power + # Working in log-space gives us some stability, and a slight speedup + smooth = np.exp(-gain * (np.log(eps) + np.log1p(S_smooth / eps))) + return (S * smooth + bias)**power - bias**power def _spectrogram(y=None, S=None, n_fft=2048, hop_length=512, power=1): From 27781ab3922e7f5004d09b8ff4785795e130bd5f Mon Sep 17 00:00:00 2001 From: Brian McFee Date: Wed, 21 Feb 2018 11:30:02 -0500 Subject: [PATCH 10/10] fixed indent error in pcen [ci skip] --- librosa/core/spectrum.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/librosa/core/spectrum.py b/librosa/core/spectrum.py index 9053596018..a4d902c81e 100644 --- a/librosa/core/spectrum.py +++ b/librosa/core/spectrum.py @@ -1439,8 +1439,7 @@ def pcen(S, sr=22050, hop_length=512, gain=0.98, bias=2, power=0.5, warnings.warn('pcen was called on complex input so phase ' 'information will be discarded. To suppress this warning, ' 'call pcen(magphase(D)[0]) instead.') - - S = np.abs(S) + S = np.abs(S) if max_size == 1: ref_spec = S