diff --git a/docs/changes.rst b/docs/changes.rst index 77f9f42..169230a 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -2,8 +2,13 @@ Changelog --------- -1.5.0 -~~~~~ +v1.5.1 +~~~~~~ +- Fixes a bug with fade in and out lengths are set to 0. +- This is the last version to support Python 2.7 and 3.4. + +v1.5.0 +~~~~~~ - Scaper now returns the generated audio and annotations directly in memory, allowing you to skip any/all file I/O! - Saving the audio and annotations to disk is still supported, but is now optional. - While this update modifies the API of several functions, it should still be backwards compatible. diff --git a/scaper/core.py b/scaper/core.py index ed5dbfc..466f3e8 100644 --- a/scaper/core.py +++ b/scaper/core.py @@ -1897,13 +1897,15 @@ def _generate_audio(self, audio_path, ann, reverb=None, # Apply short fade in and out # (avoid unnatural sound onsets/offsets) - fade_in_samples = int(self.fade_in_len * self.sr) - fade_out_samples = int(self.fade_out_len * self.sr) - fade_in_window = np.sin(np.linspace(0, np.pi / 2, fade_in_samples))[..., None] - fade_out_window = np.sin(np.linspace(np.pi / 2, 0, fade_out_samples))[..., None] - - event_audio[:fade_in_samples] *= fade_in_window - event_audio[-fade_out_samples:] *= fade_out_window + if self.fade_in_len > 0: + fade_in_samples = int(self.fade_in_len * self.sr) + fade_in_window = np.sin(np.linspace(0, np.pi / 2, fade_in_samples))[..., None] + event_audio[:fade_in_samples] *= fade_in_window + + if self.fade_out_len > 0: + fade_out_samples = int(self.fade_out_len * self.sr) + fade_out_window = np.sin(np.linspace(np.pi / 2, 0, fade_out_samples))[..., None] + event_audio[-fade_out_samples:] *= fade_out_window # Pad with silence before/after event to match the # soundscape duration diff --git a/scaper/version.py b/scaper/version.py index e29b47e..5eca42f 100644 --- a/scaper/version.py +++ b/scaper/version.py @@ -3,4 +3,4 @@ """Version info""" short_version = '1.5' -version = '1.5.0' +version = '1.5.1' diff --git a/tests/test_core.py b/tests/test_core.py index 6111b60..6f36bb2 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1951,6 +1951,58 @@ def _generate_soundscape_with_short_background(background_file, audio_path, jams sc.generate(audio_path, jams_path) +def test_scaper_generate_with_fade(): + # Test scaper generate with different fade lengths + # Works by using a fade of 0 at first then comparing + # samples of the event using different fades. + + fade_lens = [0, 0.01, 0.05, 0.1] + outputs = {} + + for fade_in in fade_lens: + for fade_out in fade_lens: + sc = scaper.Scaper(0.2, FG_PATH, BG_PATH, random_state=0) + sc.sr = 16000 + sc.ref_db = -20 + + sc.fade_in_len = fade_in + sc.fade_out_len = fade_out + + sc.add_event( + label=('const', 'siren'), + source_file=('choose', []), + source_time=('uniform', 0, 10), + event_time=('const', 0), + event_duration=('const', 0.2), + snr=('uniform', -5, 5), + pitch_shift=('uniform', -1, 1), + time_stretch=('uniform', 0.8, 1.2)) + + _, _, _, event_audio_list = sc.generate() + + outputs[(fade_in, fade_out)] = event_audio_list[0] + + no_fade = outputs[(0, 0)] + for key, val in outputs.items(): + fade_in, fade_out = key + fade_in_samples, fade_out_samples = ( + int(fade_in * sc.sr), int(fade_out * sc.sr) + ) + # Compare first fade_in_samples with no_fade + if fade_in_samples > 0: + ratio = val[:fade_in_samples] / no_fade[:fade_in_samples] + fade_in_window = np.sin( + np.linspace(0, np.pi / 2, fade_in_samples))[..., None] + mask = np.invert(np.isnan(ratio)) + assert np.allclose(ratio[mask], fade_in_window[mask]) + + if fade_out_samples > 0: + ratio = val[-fade_out_samples:] / no_fade[-fade_out_samples:] + fade_out_window = np.sin( + np.linspace(np.pi / 2, 0, fade_out_samples))[..., None] + # Ignore points where the signal has no energy + mask = np.invert(np.isnan(ratio)) + assert np.allclose(ratio[mask], fade_out_window[mask]) def test_scaper_with_short_background(): SHORT_BG_FILE = os.path.join(