import os import re import numpy as np from numpy.testing import assert_allclose import pandas as pd import pytest import soundfile import audeer import audiofile as af SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) ASSETS_DIR = os.path.join(SCRIPT_DIR, "assets") @pytest.fixture(scope="module") def audio_file(tmpdir_factory, request): """Fixture to generate audio file. Provide ``(signal, sampling_rate)`` as parameter to this fixture. """ file = str(tmpdir_factory.mktemp("audio").join("file.wav")) signal, sampling_rate = request.param af.write(file, signal, sampling_rate) yield file @pytest.fixture(scope="function") def empty_file(tmpdir, request): """Fixture to generate empty audio files. The request parameter allows to select the file extension. """ # Create empty audio file empty_file = os.path.join(tmpdir, "empty-file.wav") af.write(empty_file, np.array([[]]), 16000) # Rename to match extension file_ext = request.param ofpath = audeer.replace_file_extension(empty_file, file_ext) if os.path.exists(empty_file): os.rename(empty_file, ofpath) yield ofpath if os.path.exists(ofpath): os.remove(ofpath) @pytest.fixture(scope="function") def hide_system_path(): """Fixture to hide system path in test.""" current_path = os.environ["PATH"] os.environ["PATH"] = "" yield os.environ["PATH"] = current_path @pytest.fixture(scope="function") def non_audio_file(tmpdir, request): """Fixture to generate broken audio files. The request parameter allows to select the file extension. """ # Create empty file to simulate broken/non-audio file file_ext = request.param broken_file = os.path.join(tmpdir, f"broken-file.{file_ext}") open(broken_file, "w").close() yield broken_file if os.path.exists(broken_file): os.remove(broken_file) def tolerance(condition, sampling_rate=0): """Absolute tolerance for different condition.""" tol = 0 if condition == 8: tol = 2**-7 elif condition == 16: tol = 2**-11 # half precision elif condition == 24: tol = 2**-17 # to be checked elif condition == 32: tol = 2**-24 # single precision elif condition == "duration": tol = 1 / sampling_rate return tol def ensure_two_dimensions(x): """Converts (n,) to (1, n).""" return np.atleast_2d(x) def sine(duration=1, sampling_rate=44100, channels=1, magnitude=1, frequency=100): """Generate test tone.""" t = np.linspace(0, duration, int(np.ceil(duration * sampling_rate))) signal = magnitude * np.sin(2 * np.pi * frequency * t) if channels > 1: signal = ensure_two_dimensions(signal) signal = np.repeat(signal, channels, axis=0) return signal def write_and_read( file, signal, sampling_rate, bit_depth=16, always_2d=False, normalize=False, ): """Write and read audio files.""" af.write(file, signal, sampling_rate, bit_depth, normalize) return af.read(file, always_2d=always_2d) def _channels(signal): signal = ensure_two_dimensions(signal) return np.shape(signal)[0] def _samples(signal): signal = ensure_two_dimensions(signal) return np.shape(signal)[1] def _duration(signal, sampling_rate): return _samples(signal) / sampling_rate def _magnitude(signal): return np.max(np.abs(signal)) @pytest.mark.parametrize("duration", ["0", 0, 0.0]) @pytest.mark.parametrize("offset", [0, 1]) def test_read(tmpdir, duration, offset): file = str(tmpdir.join("signal.wav")) sampling_rate = 8000 signal = sine( duration=0.1, sampling_rate=sampling_rate, channels=1, ) af.write(file, signal, sampling_rate) sig, fs = af.read(file, duration=duration, offset=offset) assert sig.shape == (0,) assert fs == sampling_rate sig, fs = af.read(file, always_2d=True, duration=duration, offset=offset) assert sig.shape == (1, 0) assert fs == sampling_rate @pytest.mark.parametrize( "convert", (True, False), ) @pytest.mark.parametrize( "empty_file", ("bin", "mp4", "wav"), indirect=True, ) def test_empty_file(tmpdir, convert, empty_file): if convert: converted_file = str(tmpdir.join("signal-converted.wav")) path = af.convert_to_wav(empty_file, converted_file) assert path == audeer.path(converted_file) empty_file = converted_file # Reading file signal, sampling_rate = af.read(empty_file) assert len(signal) == 0 # Metadata for sloppy in [True, False]: assert af.duration(empty_file, sloppy=sloppy) == 0.0 assert af.channels(empty_file) == 1 assert af.sampling_rate(empty_file) == sampling_rate assert af.samples(empty_file) == 0 if audeer.file_extension(empty_file) == "wav": assert af.bit_depth(empty_file) == 16 else: assert af.bit_depth(empty_file) is None @pytest.mark.parametrize( "empty_file", ("bin", "mp4"), indirect=True, ) def test_missing_binaries(tmpdir, hide_system_path, empty_file): expected_error = FileNotFoundError # Reading file with pytest.raises(expected_error, match="mediainfo"): signal, sampling_rate = af.read(empty_file) # Metadata with pytest.raises(expected_error, match="mediainfo"): af.channels(empty_file) with pytest.raises(expected_error, match="mediainfo"): af.duration(empty_file) with pytest.raises(expected_error, match="mediainfo"): af.duration(empty_file, sloppy=True) with pytest.raises(expected_error, match="mediainfo"): af.has_video(empty_file) with pytest.raises(expected_error, match="mediainfo"): af.samples(empty_file) with pytest.raises(expected_error, match="mediainfo"): af.sampling_rate(empty_file) # Convert with pytest.raises(expected_error, match="mediainfo"): converted_file = str(tmpdir.join("signal-converted.wav")) af.convert_to_wav(empty_file, converted_file) @pytest.mark.parametrize( "ext", ("bin", "mp4", "wav"), ) def test_missing_file(tmpdir, ext): missing_file = f"missing_file.{ext}" expected_error = RuntimeError # Reading file with pytest.raises(expected_error): signal, sampling_rate = af.read(missing_file) # Metadata with pytest.raises(expected_error): af.sampling_rate(missing_file) with pytest.raises(expected_error): af.channels(missing_file) with pytest.raises(expected_error): af.duration(missing_file) with pytest.raises(expected_error): af.duration(missing_file, sloppy=True) # Convert with pytest.raises(expected_error): converted_file = str(tmpdir.join("signal-converted.wav")) af.convert_to_wav(missing_file, converted_file) @pytest.mark.parametrize( "file, expected_error, expected_error_message", [ ("missing_file.bin", RuntimeError, "'missing_file.bin' does not exist"), ("missing_file.mp4", RuntimeError, "'missing_file.mp4' does not exist"), ("missing_file.wav", None, None), ], ) def test_missing_file_has_video(file, expected_error, expected_error_message): if expected_error is not None: with pytest.raises(expected_error, match=expected_error_message): af.has_video(file) else: assert af.has_video(file) is False @pytest.mark.parametrize( "non_audio_file", ("bin", "mp4", "wav"), indirect=True, ) def test_broken_file(tmpdir, non_audio_file): # Only match the beginning of error message # as the default soundfile message differs at the end on macOS error_msg = "Error opening" expected_error = RuntimeError # Reading file with pytest.raises(expected_error, match=error_msg): af.read(non_audio_file) # Metadata if audeer.file_extension(non_audio_file) == "wav": with pytest.raises(expected_error, match=error_msg): af.bit_depth(non_audio_file) else: assert af.bit_depth(non_audio_file) is None with pytest.raises(expected_error, match=error_msg): af.channels(non_audio_file) with pytest.raises(expected_error, match=error_msg): af.duration(non_audio_file) with pytest.raises(expected_error, match=error_msg): af.duration(non_audio_file, sloppy=True) with pytest.raises(expected_error, match=error_msg): af.samples(non_audio_file) with pytest.raises(expected_error, match=error_msg): af.sampling_rate(non_audio_file) # Convert with pytest.raises(expected_error, match=error_msg): converted_file = str(tmpdir.join("signal-converted.wav")) af.convert_to_wav(non_audio_file, converted_file) @pytest.mark.parametrize("normalize", [False, True]) @pytest.mark.parametrize("bit_depth", [8, 16, 24]) @pytest.mark.parametrize( "file_extension", ("wav", "flac", "ogg", "mp3"), ) def test_convert_to_wav(tmpdir, normalize, bit_depth, file_extension): sampling_rate = 8000 channels = 1 magnitude_offset = 0.5 signal = sine( duration=0.1, magnitude=1 - magnitude_offset, sampling_rate=sampling_rate, channels=channels, ) infile = str(tmpdir.join(f"signal.{file_extension}")) af.write(infile, signal, sampling_rate, bit_depth=bit_depth) if file_extension == "wav": error_msg = ( f"'{infile}' would be overwritten. " "Select 'overwrite=True', " "or provide an 'outfile' argument." ) with pytest.raises(RuntimeError, match=re.escape(error_msg)): outfile = af.convert_to_wav( infile, bit_depth=bit_depth, normalize=normalize, ) outfile = af.convert_to_wav( infile, bit_depth=bit_depth, normalize=normalize, overwrite=True, ) else: outfile = str(tmpdir.join("signal_converted.wav")) af.convert_to_wav( infile, outfile, bit_depth=bit_depth, normalize=normalize, ) converted_signal, converted_sampling_rate = af.read(outfile) assert converted_sampling_rate == sampling_rate if normalize: # The actual maximum/minimum value can vary # based on the used codec/format assert converted_signal.max() > 0.95 assert converted_signal.max() <= 1.0 assert converted_signal.min() < -0.95 assert converted_signal.min() >= -1.0 if file_extension == "mp3": assert af.bit_depth(outfile) == bit_depth # Don't compare signals for MP3 # as duration differs as well else: abs_difference = np.abs(converted_signal - signal).max() if file_extension == "ogg": assert af.bit_depth(outfile) == bit_depth if normalize: assert abs_difference < 0.06 + magnitude_offset else: assert abs_difference < 0.06 elif file_extension in ["wav", "flac"]: assert af.bit_depth(outfile) == bit_depth if normalize: assert abs_difference < tolerance(bit_depth) + magnitude_offset else: assert abs_difference < tolerance(bit_depth) @pytest.mark.parametrize("bit_depth", [8, 16, 24, 32]) @pytest.mark.parametrize("duration", [0.01, 0.9999, 2]) @pytest.mark.parametrize("sampling_rate", [100, 8000, 44100]) @pytest.mark.parametrize("channels", [1, 2, 3, 10]) @pytest.mark.parametrize("always_2d", [False, True]) def test_wav(tmpdir, bit_depth, duration, sampling_rate, channels, always_2d): file = str(tmpdir.join("signal.wav")) signal = sine(duration=duration, sampling_rate=sampling_rate, channels=channels) sig, fs = write_and_read( file, signal, sampling_rate, bit_depth=bit_depth, always_2d=always_2d, ) # Expected number of samples samples = int(np.ceil(duration * sampling_rate)) # Compare with soundfile implementation to check write() info = soundfile.info(file) assert_allclose( info.duration, duration, rtol=0, atol=tolerance("duration", sampling_rate), ) assert info.samplerate == sampling_rate assert info.channels == channels assert info.frames == samples # Compare with signal values to check read() assert_allclose( _duration(sig, fs), duration, rtol=0, atol=tolerance("duration", sampling_rate), ) assert fs == sampling_rate assert _channels(sig) == channels assert _samples(sig) == samples # Test audiofile metadata methods assert_allclose( af.duration(file), duration, rtol=0, atol=tolerance("duration", sampling_rate) ) assert af.sampling_rate(file) == sampling_rate assert af.channels(file) == channels assert af.samples(file) == samples assert af.bit_depth(file) == bit_depth # Test types of audiofile metadata methods assert isinstance(af.duration(file), float) assert isinstance(af.sampling_rate(file), int) assert isinstance(af.channels(file), int) assert isinstance(af.samples(file), int) # Test dimensions of array if channels == 1 and not always_2d: assert sig.ndim == 1 else: assert sig.ndim == 2 # Test additional arguments to read if sampling_rate > 100: offset = 0.001 duration = duration - 2 * offset sig, fs = af.read( file, offset=offset, duration=duration, always_2d=always_2d, ) assert _samples(sig) == round(duration * sampling_rate) @pytest.mark.parametrize("magnitude", [0.01, 0.1, 1]) @pytest.mark.parametrize("normalize", [False, True]) @pytest.mark.parametrize("bit_depth", [16, 24, 32]) @pytest.mark.parametrize("sampling_rate", [44100]) def test_magnitude(tmpdir, magnitude, normalize, bit_depth, sampling_rate): file = str(tmpdir.join("signal.wav")) signal = sine(magnitude=magnitude, sampling_rate=sampling_rate) if normalize: magnitude = 1.0 sig, _ = write_and_read( file, signal, sampling_rate, bit_depth=bit_depth, normalize=normalize ) assert_allclose(_magnitude(sig), magnitude, rtol=0, atol=tolerance(bit_depth)) assert type(_magnitude(sig)) is np.float32 @pytest.mark.parametrize("file_type", ["wav", "flac", "mp3", "ogg"]) @pytest.mark.parametrize("sampling_rate", [8000, 48000]) @pytest.mark.parametrize("channels", [1, 2, 8, 255]) @pytest.mark.parametrize("magnitude", [0.01]) def test_file_type(tmpdir, file_type, magnitude, sampling_rate, channels): # Skip unallowed combinations if file_type == "flac" and channels > 8: return None if file_type == "mp3" and channels > 2: return None file = str(tmpdir.join("signal." + file_type)) signal = sine( magnitude=magnitude, sampling_rate=sampling_rate, channels=channels, ) bit_depth = 16 sig, fs = write_and_read(file, signal, sampling_rate, bit_depth=bit_depth) # Test file type assert audeer.file_extension(file) == file_type # Test magnitude if file_type == "mp3": atol = tolerance(8) else: atol = tolerance(16) assert_allclose( _magnitude(sig), magnitude, rtol=0, atol=atol, ) # Test metadata info = soundfile.info(file) assert fs == sampling_rate assert info.samplerate == sampling_rate assert _channels(sig) == channels assert info.channels == channels if channels == 1: assert sig.ndim == 1 else: assert sig.ndim == 2 assert _samples(sig) == _samples(signal) assert info.frames == _samples(signal) if file_type in ["mp3", "ogg"]: bit_depth = None assert af.bit_depth(file) == bit_depth assert af.has_video(file) is False @pytest.mark.parametrize( "file, header_duration, audio, video", # header duration as given by mediainfo [ ("gs-16b-1c-16000hz.opus", 15.839, True, False), ("gs-16b-1c-8000hz.amr", 15.840000, True, False), ("gs-16b-1c-44100hz.m4a", 15.833, True, False), ("gs-16b-1c-44100hz.aac", None, True, False), ("video.mp4", None, False, True), ], ) def test_other_formats(file, header_duration, audio, video): path = os.path.join(ASSETS_DIR, file) if audio: signal, sampling_rate = af.read(path) assert af.channels(path) == _channels(signal) assert af.sampling_rate(path) == sampling_rate assert af.samples(path) == _samples(signal) duration = _duration(signal, sampling_rate) assert af.duration(path) == duration if header_duration is None: # Here we expect samplewise precision assert af.duration(path, sloppy=True) == duration else: # Here we expect limited precision # as the results differ between soxi and mediainfo precision = 1 sloppy_duration = round(af.duration(path, sloppy=True), precision) header_duration = round(header_duration, precision) assert sloppy_duration == header_duration assert af.bit_depth(path) is None assert af.has_video(path) is video @pytest.mark.parametrize( "audio_file", [ ( np.array([[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]], dtype=np.float32), 2, # Hz ), ], indirect=True, ) # The following test assumes a signal of 3s length, # containing 0 during the first second, # 1 during the second second, # 2 during the third second. @pytest.mark.parametrize( "offset, duration, expected", [ # None | None (None, None, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("None", "None", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("NaN", "NaN", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("NaT", "NaT", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (np.nan, np.nan, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (pd.NaT, pd.NaT, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), # None | positive (None, np.inf, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), # positive | None (np.inf, None, [[]]), (0.0, None, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (0.5, None, [[0.0, 0.1, 0.1, 0.2, 0.2]]), (1.0, None, [[0.1, 0.1, 0.2, 0.2]]), (1.5, None, [[0.1, 0.2, 0.2]]), (2.0, None, [[0.2, 0.2]]), (2.5, None, [[0.2]]), (3.0, None, [[]]), (3.5, None, [[]]), (4.0, None, [[]]), ("Inf", "None", [[]]), ("0", "None", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("1", "None", [[0.0, 0.1, 0.1, 0.2, 0.2]]), ("2", "None", [[0.1, 0.1, 0.2, 0.2]]), ("3", "None", [[0.1, 0.2, 0.2]]), ("4", "None", [[0.2, 0.2]]), ("5", "None", [[0.2]]), ("6", "None", [[]]), ("7", "None", [[]]), ("8", "None", [[]]), # positive | positive (np.inf, np.inf, [[]]), (np.inf, 1.0, [[]]), (0.0, np.inf, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (0.5, np.inf, [[0.0, 0.1, 0.1, 0.2, 0.2]]), (1.0, np.inf, [[0.1, 0.1, 0.2, 0.2]]), (1.5, np.inf, [[0.1, 0.2, 0.2]]), (2.0, np.inf, [[0.2, 0.2]]), (2.5, np.inf, [[0.2]]), (3.0, np.inf, [[]]), (3.5, np.inf, [[]]), (0.0, 0.0, [[]]), (0.5, 0.0, [[]]), (1.0, 0.0, [[]]), (1.5, 0.0, [[]]), (2.0, 0.0, [[]]), (2.5, 0.0, [[]]), (0.0, 0.5, [[0.0]]), (0.5, 0.5, [[0.0]]), (1.0, 0.5, [[0.1]]), (1.5, 0.5, [[0.1]]), (2.0, 0.5, [[0.2]]), (2.5, 0.5, [[0.2]]), (3.0, 0.5, [[]]), (0.0, 1.0, [[0.0, 0.0]]), (0.5, 1.0, [[0.0, 0.1]]), (1.0, 1.0, [[0.1, 0.1]]), (1.5, 1.0, [[0.1, 0.2]]), (2.0, 1.0, [[0.2, 0.2]]), (2.5, 1.0, [[0.2]]), (3.0, 1.0, [[]]), (0.0, 1.5, [[0.0, 0.0, 0.1]]), (0.5, 1.5, [[0.0, 0.1, 0.1]]), (1.0, 1.5, [[0.1, 0.1, 0.2]]), (1.5, 1.5, [[0.1, 0.2, 0.2]]), (2.0, 1.5, [[0.2, 0.2]]), (2.5, 1.5, [[0.2]]), (3.0, 1.5, [[]]), (0.0, 2.0, [[0.0, 0.0, 0.1, 0.1]]), (0.5, 2.0, [[0.0, 0.1, 0.1, 0.2]]), (1.0, 2.0, [[0.1, 0.1, 0.2, 0.2]]), (1.5, 2.0, [[0.1, 0.2, 0.2]]), (2.0, 2.0, [[0.2, 0.2]]), (2.5, 2.0, [[0.2]]), (3.0, 2.0, [[]]), (0.0, 2.5, [[0.0, 0.0, 0.1, 0.1, 0.2]]), (0.5, 2.5, [[0.0, 0.1, 0.1, 0.2, 0.2]]), (1.0, 2.5, [[0.1, 0.1, 0.2, 0.2]]), (1.5, 2.5, [[0.1, 0.2, 0.2]]), (2.0, 2.5, [[0.2, 0.2]]), (2.5, 2.5, [[0.2]]), (3.0, 2.5, [[]]), (0.0, 3.0, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (0.5, 3.0, [[0.0, 0.1, 0.1, 0.2, 0.2]]), (1.0, 3.0, [[0.1, 0.1, 0.2, 0.2]]), (1.5, 3.0, [[0.1, 0.2, 0.2]]), (2.0, 3.0, [[0.2, 0.2]]), (2.5, 3.0, [[0.2]]), (3.0, 3.0, [[]]), (0.0, 3.5, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (0.5, 3.5, [[0.0, 0.1, 0.1, 0.2, 0.2]]), (1.0, 3.5, [[0.1, 0.1, 0.2, 0.2]]), (1.5, 3.5, [[0.1, 0.2, 0.2]]), (2.0, 3.5, [[0.2, 0.2]]), (2.5, 3.5, [[0.2]]), (3.0, 3.5, [[]]), (0.0, 4.0, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("Inf", "Inf", [[]]), ("Inf", "1", [[]]), ("0", "Inf", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("1", "Inf", [[0.0, 0.1, 0.1, 0.2, 0.2]]), ("2", "Inf", [[0.1, 0.1, 0.2, 0.2]]), ("3", "Inf", [[0.1, 0.2, 0.2]]), ("4", "Inf", [[0.2, 0.2]]), ("5", "Inf", [[0.2]]), ("6", "Inf", [[]]), ("7", "Inf", [[]]), ("0", "0", [[]]), ("1", "0", [[]]), ("2", "0", [[]]), ("3", "0", [[]]), ("4", "0", [[]]), ("5", "0", [[]]), ("0", "1", [[0.0]]), ("1", "1", [[0.0]]), ("2", "1", [[0.1]]), ("3", "1", [[0.1]]), ("4", "1", [[0.2]]), ("5", "1", [[0.2]]), ("6", "1", [[]]), ("0", "2", [[0.0, 0.0]]), ("1", "2", [[0.0, 0.1]]), ("2", "2", [[0.1, 0.1]]), ("3", "2", [[0.1, 0.2]]), ("4", "2", [[0.2, 0.2]]), ("5", "2", [[0.2]]), ("6", "2", [[]]), ("0", "3", [[0.0, 0.0, 0.1]]), ("1", "3", [[0.0, 0.1, 0.1]]), ("2", "3", [[0.1, 0.1, 0.2]]), ("3", "3", [[0.1, 0.2, 0.2]]), ("4", "3", [[0.2, 0.2]]), ("5", "3", [[0.2]]), ("6", "3", [[]]), ("0", "4", [[0.0, 0.0, 0.1, 0.1]]), ("1", "4", [[0.0, 0.1, 0.1, 0.2]]), ("2", "4", [[0.1, 0.1, 0.2, 0.2]]), ("3", "4", [[0.1, 0.2, 0.2]]), ("4", "4", [[0.2, 0.2]]), ("5", "4", [[0.2]]), ("6", "4", [[]]), ("0", "5", [[0.0, 0.0, 0.1, 0.1, 0.2]]), ("1", "5", [[0.0, 0.1, 0.1, 0.2, 0.2]]), ("2", "5", [[0.1, 0.1, 0.2, 0.2]]), ("3", "5", [[0.1, 0.2, 0.2]]), ("4", "5", [[0.2, 0.2]]), ("5", "5", [[0.2]]), ("6", "5", [[]]), ("0", "6", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("1", "6", [[0.0, 0.1, 0.1, 0.2, 0.2]]), ("2", "6", [[0.1, 0.1, 0.2, 0.2]]), ("3", "6", [[0.1, 0.2, 0.2]]), ("4", "6", [[0.2, 0.2]]), ("5", "6", [[0.2]]), ("6", "6", [[]]), ("0", "7", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("1", "7", [[0.0, 0.1, 0.1, 0.2, 0.2]]), ("2", "7", [[0.1, 0.1, 0.2, 0.2]]), ("3", "7", [[0.1, 0.2, 0.2]]), ("4", "7", [[0.2, 0.2]]), ("5", "7", [[0.2]]), ("6", "7", [[]]), ("0", "8", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), # None | negative (None, -np.inf, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (None, -0.5, [[0.2]]), (None, -1.0, [[0.2, 0.2]]), (None, -1.5, [[0.1, 0.2, 0.2]]), (None, -2.0, [[0.1, 0.1, 0.2, 0.2]]), (None, -2.5, [[0.0, 0.1, 0.1, 0.2, 0.2]]), (None, -3.0, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (None, -3.5, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (None, -4.0, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("None", "-Inf", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("None", "-1", [[0.2]]), ("None", "-2", [[0.2, 0.2]]), ("None", "-3", [[0.1, 0.2, 0.2]]), ("None", "-4", [[0.1, 0.1, 0.2, 0.2]]), ("None", "-5", [[0.0, 0.1, 0.1, 0.2, 0.2]]), ("None", "-6", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("None", "-7", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("None", "-8", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), # negative | None (-np.inf, None, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (-0.5, None, [[0.2]]), (-1.0, None, [[0.2, 0.2]]), (-1.5, None, [[0.1, 0.2, 0.2]]), (-2.0, None, [[0.1, 0.1, 0.2, 0.2]]), (-2.5, None, [[0.0, 0.1, 0.1, 0.2, 0.2]]), (-3.0, None, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (-3.5, None, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (-4.0, None, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("-Inf", "None", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("-1", "None", [[0.2]]), ("-2", "None", [[0.2, 0.2]]), ("-3", "None", [[0.1, 0.2, 0.2]]), ("-4", "None", [[0.1, 0.1, 0.2, 0.2]]), ("-5", "None", [[0.0, 0.1, 0.1, 0.2, 0.2]]), ("-6", "None", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("-7", "None", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("-7", "None", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), # negative | positive (-np.inf, np.inf, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (-np.inf, 4.0, [[]]), (-4.0, np.inf, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (-0.5, 0.0, [[]]), (-1.0, 0.0, [[]]), (-1.5, 0.0, [[]]), (-2.0, 0.0, [[]]), (-2.5, 0.0, [[]]), (-3.0, 0.0, [[]]), (-3.5, 0.0, [[]]), (-4.0, 0.0, [[]]), (-0.5, 0.5, [[0.2]]), (-1.0, 0.5, [[0.2]]), (-1.5, 0.5, [[0.1]]), (-2.0, 0.5, [[0.1]]), (-2.5, 0.5, [[0.0]]), (-3.0, 0.5, [[0.0]]), (-3.5, 0.5, [[]]), (-4.0, 0.5, [[]]), (-0.5, 1.0, [[0.2]]), (-1.0, 1.0, [[0.2, 0.2]]), (-1.5, 1.0, [[0.1, 0.2]]), (-2.0, 1.0, [[0.1, 0.1]]), (-2.5, 1.0, [[0.0, 0.1]]), (-3.0, 1.0, [[0.0, 0.0]]), (-3.5, 1.0, [[0.0]]), (-4.0, 1.0, [[]]), (-0.5, 1.5, [[0.2]]), (-1.0, 1.5, [[0.2, 0.2]]), (-1.5, 1.5, [[0.1, 0.2, 0.2]]), (-2.0, 1.5, [[0.1, 0.1, 0.2]]), (-2.5, 1.5, [[0.0, 0.1, 0.1]]), (-3.0, 1.5, [[0.0, 0.0, 0.1]]), (-3.5, 1.5, [[0.0, 0.0]]), (-4.0, 1.5, [[0.0]]), (-4.5, 1.5, [[]]), (-0.5, 2.0, [[0.2]]), (-1.0, 2.0, [[0.2, 0.2]]), (-1.5, 2.0, [[0.1, 0.2, 0.2]]), (-2.0, 2.0, [[0.1, 0.1, 0.2, 0.2]]), (-2.5, 2.0, [[0.0, 0.1, 0.1, 0.2]]), (-3.0, 2.0, [[0.0, 0.0, 0.1, 0.1]]), (-3.5, 2.0, [[0.0, 0.0, 0.1]]), (-4.0, 2.0, [[0.0, 0.0]]), (-4.5, 2.0, [[0.0]]), (-5.0, 2.0, [[]]), ("-Inf", "Inf", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("-Inf", "8", [[]]), ("-8", "Inf", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("-1", "0", [[]]), ("-2", "0", [[]]), ("-3", "0", [[]]), ("-4", "0", [[]]), ("-5", "0", [[]]), ("-6", "0", [[]]), ("-7", "0", [[]]), ("-8", "0", [[]]), ("-1", "1", [[0.2]]), ("-2", "1", [[0.2]]), ("-3", "1", [[0.1]]), ("-4", "1", [[0.1]]), ("-5", "1", [[0.0]]), ("-6", "1", [[0.0]]), ("-7", "1", [[]]), ("-8", "1", [[]]), ("-1", "2", [[0.2]]), ("-2", "2", [[0.2, 0.2]]), ("-3", "2", [[0.1, 0.2]]), ("-4", "2", [[0.1, 0.1]]), ("-5", "2", [[0.0, 0.1]]), ("-6", "2", [[0.0, 0.0]]), ("-7", "2", [[0.0]]), ("-8", "2", [[]]), ("-1", "3", [[0.2]]), ("-2", "3", [[0.2, 0.2]]), ("-3", "3", [[0.1, 0.2, 0.2]]), ("-4", "3", [[0.1, 0.1, 0.2]]), ("-5", "3", [[0.0, 0.1, 0.1]]), ("-6", "3", [[0.0, 0.0, 0.1]]), ("-7", "3", [[0.0, 0.0]]), ("-8", "3", [[0.0]]), ("-9", "3", [[]]), ("-1", "4", [[0.2]]), ("-2", "4", [[0.2, 0.2]]), ("-3", "4", [[0.1, 0.2, 0.2]]), ("-4", "4", [[0.1, 0.1, 0.2, 0.2]]), ("-5", "4", [[0.0, 0.1, 0.1, 0.2]]), ("-6", "4", [[0.0, 0.0, 0.1, 0.1]]), ("-7", "4", [[0.0, 0.0, 0.1]]), ("-8", "4", [[0.0, 0.0]]), ("-9", "4", [[0.0]]), ("-10", "4", [[]]), # positive | negative (np.inf, -np.inf, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (np.inf, -4.0, [[]]), (4.0, -np.inf, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (2.0, -np.inf, [[0.0, 0.0, 0.1, 0.1]]), (0.0, -0.5, [[]]), (0.5, -0.5, [[0.0]]), (1.0, -0.5, [[0.0]]), (1.5, -0.5, [[0.1]]), (2.0, -0.5, [[0.1]]), (2.5, -0.5, [[0.2]]), (3.0, -0.5, [[0.2]]), (3.5, -0.5, [[]]), (4.0, -0.5, [[]]), (0.0, -1.0, [[]]), (0.5, -1.0, [[0.0]]), (1.0, -1.0, [[0.0, 0.0]]), (1.5, -1.0, [[0.0, 0.1]]), (2.0, -1.0, [[0.1, 0.1]]), (2.5, -1.0, [[0.1, 0.2]]), (3.0, -1.0, [[0.2, 0.2]]), (3.5, -1.0, [[0.2]]), (4.0, -1.0, [[]]), (0.0, -1.5, [[]]), (0.5, -1.5, [[0.0]]), (1.0, -1.5, [[0.0, 0.0]]), (1.5, -1.5, [[0.0, 0.0, 0.1]]), (2.0, -1.5, [[0.0, 0.1, 0.1]]), (2.5, -1.5, [[0.1, 0.1, 0.2]]), (3.0, -1.5, [[0.1, 0.2, 0.2]]), (3.5, -1.5, [[0.2, 0.2]]), (4.0, -1.5, [[0.2]]), (4.5, -1.5, [[]]), (0.0, -2.0, [[]]), (0.5, -2.0, [[0.0]]), (1.0, -2.0, [[0.0, 0.0]]), (1.5, -2.0, [[0.0, 0.0, 0.1]]), (2.0, -2.0, [[0.0, 0.0, 0.1, 0.1]]), (2.5, -2.0, [[0.0, 0.1, 0.1, 0.2]]), (3.0, -2.0, [[0.1, 0.1, 0.2, 0.2]]), (3.5, -2.0, [[0.1, 0.2, 0.2]]), (4.0, -2.0, [[0.2, 0.2]]), (4.5, -2.0, [[0.2]]), (5.0, -2.0, [[]]), (2.0, -2.5, [[0.0, 0.0, 0.1, 0.1]]), (2.5, -2.5, [[0.0, 0.0, 0.1, 0.1, 0.2]]), (3.0, -2.5, [[0.0, 0.1, 0.1, 0.2, 0.2]]), (3.5, -2.5, [[0.1, 0.1, 0.2, 0.2]]), (4.0, -2.5, [[0.1, 0.2, 0.2]]), (4.5, -2.5, [[0.2, 0.2]]), (5.0, -2.5, [[0.2]]), (5.5, -2.5, [[]]), (2.0, -3.0, [[0.0, 0.0, 0.1, 0.1]]), (2.5, -3.0, [[0.0, 0.0, 0.1, 0.1, 0.2]]), (3.0, -3.0, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (3.5, -3.0, [[0.0, 0.1, 0.1, 0.2, 0.2]]), (4.0, -3.0, [[0.1, 0.1, 0.2, 0.2]]), (4.5, -3.0, [[0.1, 0.2, 0.2]]), (5.0, -3.0, [[0.2, 0.2]]), (5.5, -3.0, [[0.2]]), (6.0, -3.0, [[]]), (3.0, -3.5, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), (3.0, -4.0, [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("Inf", "-Inf", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("Inf", "-8", [[]]), ("8", "-Inf", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("4", "-Inf", [[0.0, 0.0, 0.1, 0.1]]), ("0", "-1", [[]]), ("1", "-1", [[0.0]]), ("2", "-1", [[0.0]]), ("3", "-1", [[0.1]]), ("4", "-1", [[0.1]]), ("5", "-1", [[0.2]]), ("6", "-1", [[0.2]]), ("7", "-1", [[]]), ("8", "-1", [[]]), ("0", "-2", [[]]), ("1", "-2", [[0.0]]), ("2", "-2", [[0.0, 0.0]]), ("3", "-2", [[0.0, 0.1]]), ("4", "-2", [[0.1, 0.1]]), ("5", "-2", [[0.1, 0.2]]), ("6", "-2", [[0.2, 0.2]]), ("7", "-2", [[0.2]]), ("8", "-2", [[]]), ("0", "-3", [[]]), ("1", "-3", [[0.0]]), ("2", "-3", [[0.0, 0.0]]), ("3", "-3", [[0.0, 0.0, 0.1]]), ("4", "-3", [[0.0, 0.1, 0.1]]), ("5", "-3", [[0.1, 0.1, 0.2]]), ("6", "-3", [[0.1, 0.2, 0.2]]), ("7", "-3", [[0.2, 0.2]]), ("8", "-3", [[0.2]]), ("9", "-3", [[]]), ("0", "-4", [[]]), ("1", "-4", [[0.0]]), ("2", "-4", [[0.0, 0.0]]), ("3", "-4", [[0.0, 0.0, 0.1]]), ("4", "-4", [[0.0, 0.0, 0.1, 0.1]]), ("5", "-4", [[0.0, 0.1, 0.1, 0.2]]), ("6", "-4", [[0.1, 0.1, 0.2, 0.2]]), ("7", "-4", [[0.1, 0.2, 0.2]]), ("8", "-4", [[0.2, 0.2]]), ("9", "-4", [[0.2]]), ("10", "-4", [[]]), ("4", "-5", [[0.0, 0.0, 0.1, 0.1]]), ("5", "-5", [[0.0, 0.0, 0.1, 0.1, 0.2]]), ("6", "-5", [[0.0, 0.1, 0.1, 0.2, 0.2]]), ("7", "-5", [[0.1, 0.1, 0.2, 0.2]]), ("8", "-5", [[0.1, 0.2, 0.2]]), ("9", "-5", [[0.2, 0.2]]), ("10", "-5", [[0.2]]), ("11", "-5", [[]]), ("4", "-6", [[0.0, 0.0, 0.1, 0.1]]), ("5", "-6", [[0.0, 0.0, 0.1, 0.1, 0.2]]), ("6", "-6", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("7", "-6", [[0.0, 0.1, 0.1, 0.2, 0.2]]), ("8", "-6", [[0.1, 0.1, 0.2, 0.2]]), ("9", "-6", [[0.1, 0.2, 0.2]]), ("10", "-6", [[0.2, 0.2]]), ("11", "-6", [[0.2]]), ("12", "-6", [[]]), ("6", "-6", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("6", "-7", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), ("6", "-8", [[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]]), # negative | negative (-np.inf, -np.inf, [[]]), (-np.inf, -4.0, [[]]), (-4.0, -np.inf, [[]]), (-0.5, -0.5, [[0.2]]), (-1.0, -0.5, [[0.1]]), (-1.5, -0.5, [[0.1]]), (-2.0, -0.5, [[0.0]]), (-2.5, -0.5, [[0.0]]), (-3.0, -0.5, [[]]), (-3.5, -0.5, [[]]), (-4.0, -0.5, [[]]), (-0.5, -1.0, [[0.1, 0.2]]), (-1.0, -1.0, [[0.1, 0.1]]), (-1.5, -1.0, [[0.0, 0.1]]), (-2.0, -1.0, [[0.0, 0.0]]), (-2.5, -1.0, [[0.0]]), (-3.0, -1.0, [[]]), (-3.5, -1.0, [[]]), (-4.0, -1.0, [[]]), (-0.5, -1.5, [[0.1, 0.1, 0.2]]), (-1.0, -1.5, [[0.0, 0.1, 0.1]]), (-1.5, -1.5, [[0.0, 0.0, 0.1]]), (-2.0, -1.5, [[0.0, 0.0]]), (-2.5, -1.5, [[0.0]]), (-3.0, -1.5, [[]]), (-3.5, -1.5, [[]]), (-4.0, -1.5, [[]]), (-0.5, -2.0, [[0.0, 0.1, 0.1, 0.2]]), (-1.0, -2.0, [[0.0, 0.0, 0.1, 0.1]]), (-1.5, -2.0, [[0.0, 0.0, 0.1]]), (-2.0, -2.0, [[0.0, 0.0]]), (-2.5, -2.0, [[0.0]]), (-3.0, -2.0, [[]]), (-3.5, -2.0, [[]]), (-4.0, -2.0, [[]]), (-0.5, -2.5, [[0.0, 0.0, 0.1, 0.1, 0.2]]), (-1.0, -2.5, [[0.0, 0.0, 0.1, 0.1]]), (-1.5, -2.5, [[0.0, 0.0, 0.1]]), (-2.0, -2.5, [[0.0, 0.0]]), (-2.5, -2.5, [[0.0]]), (-3.0, -2.5, [[]]), (-3.5, -2.5, [[]]), (-4.0, -2.5, [[]]), (-0.5, -3.0, [[0.0, 0.0, 0.1, 0.1, 0.2]]), (-1.0, -3.0, [[0.0, 0.0, 0.1, 0.1]]), (-1.5, -3.0, [[0.0, 0.0, 0.1]]), (-2.0, -3.0, [[0.0, 0.0]]), (-2.5, -3.0, [[0.0]]), (-3.0, -3.0, [[]]), (-3.5, -3.0, [[]]), (-4.0, -3.0, [[]]), (-0.5, -3.5, [[0.0, 0.0, 0.1, 0.1, 0.2]]), (-1.0, -3.5, [[0.0, 0.0, 0.1, 0.1]]), (-1.5, -3.5, [[0.0, 0.0, 0.1]]), (-2.0, -3.5, [[0.0, 0.0]]), (-2.5, -3.5, [[0.0]]), (-3.0, -3.5, [[]]), (-3.5, -3.5, [[]]), (-4.0, -3.5, [[]]), (-0.5, -4.0, [[0.0, 0.0, 0.1, 0.1, 0.2]]), (-1.0, -4.0, [[0.0, 0.0, 0.1, 0.1]]), (-1.5, -4.0, [[0.0, 0.0, 0.1]]), (-2.0, -4.0, [[0.0, 0.0]]), (-2.5, -4.0, [[0.0]]), (-3.0, -4.0, [[]]), (-3.5, -4.0, [[]]), (-4.0, -4.0, [[]]), ("-Inf", "-Inf", [[]]), ("-Inf", "-8", [[]]), ("-8", "-Inf", [[]]), ("-1", "-1", [[0.2]]), ("-2", "-1", [[0.1]]), ("-3", "-1", [[0.1]]), ("-4", "-1", [[0.0]]), ("-5", "-1", [[0.0]]), ("-6", "-1", [[]]), ("-7", "-1", [[]]), ("-8", "-1", [[]]), ("-1", "-2", [[0.1, 0.2]]), ("-2", "-2", [[0.1, 0.1]]), ("-3", "-2", [[0.0, 0.1]]), ("-4", "-2", [[0.0, 0.0]]), ("-5", "-2", [[0.0]]), ("-6", "-2", [[]]), ("-7", "-2", [[]]), ("-8", "-2", [[]]), ("-1", "-3", [[0.1, 0.1, 0.2]]), ("-2", "-3", [[0.0, 0.1, 0.1]]), ("-3", "-3", [[0.0, 0.0, 0.1]]), ("-4", "-3", [[0.0, 0.0]]), ("-5", "-3", [[0.0]]), ("-6", "-3", [[]]), ("-7", "-3", [[]]), ("-8", "-3", [[]]), ("-1", "-4", [[0.0, 0.1, 0.1, 0.2]]), ("-2", "-4", [[0.0, 0.0, 0.1, 0.1]]), ("-3", "-4", [[0.0, 0.0, 0.1]]), ("-4", "-4", [[0.0, 0.0]]), ("-5", "-4", [[0.0]]), ("-6", "-4", [[]]), ("-7", "-4", [[]]), ("-8", "-4", [[]]), ("-1", "-5", [[0.0, 0.0, 0.1, 0.1, 0.2]]), ("-2", "-5", [[0.0, 0.0, 0.1, 0.1]]), ("-3", "-5", [[0.0, 0.0, 0.1]]), ("-4", "-5", [[0.0, 0.0]]), ("-5", "-5", [[0.0]]), ("-6", "-5", [[]]), ("-7", "-5", [[]]), ("-8", "-5", [[]]), ("-1", "-6", [[0.0, 0.0, 0.1, 0.1, 0.2]]), ("-2", "-6", [[0.0, 0.0, 0.1, 0.1]]), ("-3", "-6", [[0.0, 0.0, 0.1]]), ("-4", "-6", [[0.0, 0.0]]), ("-5", "-6", [[0.0]]), ("-6", "-6", [[]]), ("-7", "-6", [[]]), ("-8", "-6", [[]]), ("-1", "-7", [[0.0, 0.0, 0.1, 0.1, 0.2]]), ("-2", "-7", [[0.0, 0.0, 0.1, 0.1]]), ("-3", "-7", [[0.0, 0.0, 0.1]]), ("-4", "-7", [[0.0, 0.0]]), ("-5", "-7", [[0.0]]), ("-6", "-7", [[]]), ("-7", "-7", [[]]), ("-8", "-7", [[]]), ("-1", "-8", [[0.0, 0.0, 0.1, 0.1, 0.2]]), ("-2", "-8", [[0.0, 0.0, 0.1, 0.1]]), ("-3", "-8", [[0.0, 0.0, 0.1]]), ("-4", "-8", [[0.0, 0.0]]), ("-5", "-8", [[0.0]]), ("-6", "-8", [[]]), ("-7", "-8", [[]]), ("-8", "-8", [[]]), ], ) def test_read_duration_and_offset(audio_file, offset, duration, expected): # Read with provided duration/offset signal, _ = af.read( audio_file, offset=offset, duration=duration, always_2d=True, ) np.testing.assert_allclose( signal, np.array(expected, dtype=np.float32), rtol=1e-03, ) def test_read_duration_and_offset_file_formats(tmpdir): # Prepare signals sampling_rate = 44100 channels = 1 signal = sine( magnitude=1, sampling_rate=sampling_rate, channels=channels, ) wav_file = str(tmpdir.join("signal.wav")) mp3_file = str(tmpdir.join("signal.mp3")) m4a_file = audeer.path(ASSETS_DIR, "gs-16b-1c-44100hz.m4a") af.write(wav_file, signal, sampling_rate) af.write(mp3_file, signal, sampling_rate) for file in [wav_file, mp3_file, m4a_file]: # Duration and offset in seconds offset = 0.1 duration = 0.5 sig, fs = af.read(file, offset=offset, duration=duration) assert _duration(sig, sampling_rate) == duration sig, fs = af.read(file, offset=offset) assert_allclose( _duration(sig, sampling_rate), af.duration(file) - offset, rtol=0, atol=tolerance("duration", sampling_rate), ) # Duration and offset in negative seconds offset = -0.1 duration = -0.5 sig, fs = af.read(file, offset=offset, duration=duration) assert _duration(sig, sampling_rate) == -duration sig, fs = af.read(file, offset=af.duration(file) + offset) assert_allclose( _duration(sig, sampling_rate), -offset, rtol=0, atol=tolerance("duration", sampling_rate), ) # Duration and offset in samples offset = "100" duration = "200" duration_s = float(duration) / sampling_rate offset_s = float(offset) / sampling_rate sig, fs = af.read( file, offset=offset, duration=duration, always_2d=True, ) assert _duration(sig, sampling_rate) == duration_s assert sig.shape[1] == float(duration) sig, fs = af.read(file, offset=offset) assert_allclose( _duration(sig, sampling_rate), af.duration(file) - offset_s, rtol=0, atol=tolerance("duration", sampling_rate), ) # Duration that results in empty signal duration = 0.000001 sig, fs = af.read(file, duration=duration) np.testing.assert_array_equal( sig, np.array([], np.float32), ) @pytest.mark.parametrize( "audio_file", [ ( np.array( [[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]], dtype=np.float32, ), 10, # Hz ), ], indirect=True, ) @pytest.mark.parametrize( # offset and duration need to be given in seconds "offset, duration, expected", [ (0.1, 0.1, [0.1]), (0.1, 0.03, []), (0.1, 0.15, [0.1, 0.2]), (0.1, 0.19, [0.1, 0.2]), (0.049, 0.19, [0.0, 0.1]), (0.15, 0.15, [0.2, 0.3]), ], ) def test_read_duration_and_offset_rounding( tmpdir, audio_file, offset, duration, expected, ): # Prepare signals # Ensure that the rounding from duration to samples # is done as expected # and in the same way # when reading with sox or ffmpeg # soundfile signal, sampling_rate = af.read(audio_file, offset=offset, duration=duration) np.testing.assert_allclose( signal, np.array(expected, dtype=np.float32), rtol=1e-03, ) if len(expected) == 0: # duration of 0 is handled inside af.read() # even when duration is only 0 after rounding # as ffmpeg cannot handle those cases return None # sox convert_file = str(tmpdir.join("signal-sox.wav")) try: af.core.utils.run_sox(audio_file, convert_file, offset, duration, sampling_rate) signal, _ = af.read(convert_file) np.testing.assert_allclose( signal, np.array(expected, dtype=np.float32), rtol=1e-03, ) except FileNotFoundError: # When testing without an installation of sox pass # ffmpeg convert_file = str(tmpdir.join("signal-ffmpeg.wav")) af.core.utils.run_ffmpeg(audio_file, convert_file, offset, duration, sampling_rate) signal, _ = af.read(convert_file) np.testing.assert_allclose( signal, np.array(expected, dtype=np.float32), rtol=1e-03, ) def test_write_errors(): sampling_rate = 44100 # Call with unallowed bit depths expected_error = '"bit_depth" has to be one of' with pytest.raises(RuntimeError, match=expected_error): af.write("test.wav", np.zeros((1, 100)), sampling_rate, bit_depth=1) # Checking for not allowed combinations of channel and file type expected_error = ( "The maximum number of allowed channels " "for 'flac' is 8. Consider using 'wav' instead." ) with pytest.raises(RuntimeError, match=expected_error): write_and_read("test.flac", np.zeros((9, 100)), sampling_rate) expected_error = ( "The maximum number of allowed channels " "for 'mp3' is 2. Consider using 'wav' instead." ) with pytest.raises(RuntimeError, match=expected_error): write_and_read("test.mp3", np.zeros((3, 100)), sampling_rate) expected_error = ( "The maximum number of allowed channels " "for 'ogg' is 255. Consider using 'wav' instead." ) with pytest.raises(RuntimeError, match=expected_error): write_and_read("test.ogg", np.zeros((256, 100)), sampling_rate) expected_error = "The maximum number of allowed channels " "for 'wav' is 65535." with pytest.raises(RuntimeError, match=expected_error): write_and_read("test.wav", np.zeros((65536, 100)), sampling_rate)
Memory