diff --git a/.flake8 b/.flake8 deleted file mode 100644 index cb2ed23..0000000 --- a/.flake8 +++ /dev/null @@ -1,10 +0,0 @@ -[flake8] -exclude = - .eggs, - build, -extend-ignore = - # math, https://github.com/PyCQA/pycodestyle/issues/513 - W503, -per-file-ignores = - # ignore unused imports - __init__.py: F401 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 2f8baef..1f5613e 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -14,10 +14,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Python 3.8 + - name: Set up Python 3.10 uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.10' - name: Install pre-commit hooks run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 92a17ee..119cb13 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,13 +9,15 @@ # # default_language_version: - python: python3.8 + python: python3.10 repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.276 + rev: v0.1.8 hooks: - id: ruff + args: [ --fix ] + - id: ruff-format - repo: https://github.com/codespell-project/codespell rev: v2.2.4 hooks: diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3bc0815..cf21b93 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -41,7 +41,7 @@ Coding Convention ----------------- We follow the PEP8_ convention for Python code -and check for correct syntax with ruff_. +and use ruff_ as a linter and code formatter. In addition, we check for common spelling errors with codespell_. Both tools and possible exceptions @@ -61,7 +61,8 @@ You can also install ruff_ and codespell_ and call it directly:: pip install ruff codespell # consider system wide installation - ruff check . + ruff check --fix . # lint all Python files, and fix any fixable errors + ruff format . # format code of all Python files codespell It can be restricted to specific folders:: diff --git a/README.rst b/README.rst index 12f7b34..29ac571 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ Code example for reading a file: import audiofile - signal, sampling_rate = audiofile.read('signal.wav') + signal, sampling_rate = audiofile.read("signal.wav") Under the hood it uses soundfile_ to read the audio files, converting non-supported formats first to WAV files. diff --git a/audiofile/__init__.py b/audiofile/__init__.py index 4db059e..3a6fd8d 100644 --- a/audiofile/__init__.py +++ b/audiofile/__init__.py @@ -16,6 +16,7 @@ # Dynamically get the version of the installed module try: import importlib.metadata + __version__ = importlib.metadata.version(__name__) except Exception: # pragma: no cover importlib = None # pragma: no cover diff --git a/audiofile/core/conftest.py b/audiofile/core/conftest.py index 07b04c9..ca1127f 100644 --- a/audiofile/core/conftest.py +++ b/audiofile/core/conftest.py @@ -11,29 +11,28 @@ np.random.seed(1) -@pytest.fixture(scope='session', autouse=True) +@pytest.fixture(scope="session", autouse=True) def audio_file(): - create_audio_files('.') + create_audio_files(".") yield # Clean up - for file in audeer.list_file_names('.', filetype='wav'): + for file in audeer.list_file_names(".", filetype="wav"): if os.path.exists(file): os.remove(file) - for file in audeer.list_file_names('.', filetype='flac'): + for file in audeer.list_file_names(".", filetype="flac"): if os.path.exists(file): os.remove(file) def create_audio_files( - basedir, - + basedir, ): sampling_rate = 8000 - mono_wav_file = audeer.path(basedir, 'mono.wav') - stereo_wav_file = audeer.path(basedir, 'stereo.wav') - stereo_flac_file = audeer.path(basedir, 'stereo.flac') + mono_wav_file = audeer.path(basedir, "mono.wav") + stereo_wav_file = audeer.path(basedir, "stereo.wav") + stereo_flac_file = audeer.path(basedir, "stereo.flac") signal = am_fm_synth(1.5, 1, sampling_rate) audiofile.write(mono_wav_file, signal, sampling_rate) signal = am_fm_synth(1.5, 2, sampling_rate) @@ -42,11 +41,11 @@ def create_audio_files( def am_fm_synth( - duration: float, - num_channels: int = 1, - sampling_rate: int = 16000, - *, - dtype: type = np.float32, + duration: float, + num_channels: int = 1, + sampling_rate: int = 16000, + *, + dtype: type = np.float32, ) -> np.ndarray: r"""Synthesise an AM/FM signal of given duration (sampled at given rate). @@ -78,7 +77,7 @@ def am_fm_synth( # No reinitialisation (to get true stereo) for t in range(n_samples): sig[ch_indx, t] = g * np.cos(ph_fm) - sig[ch_indx, t] *= ((1 - g_am) + g_am * np.square(np.cos(ph_am))) + sig[ch_indx, t] *= (1 - g_am) + g_am * np.square(np.cos(ph_am)) ph_am += omega_am / 2 ph_fm += omega0_car + omega_dev * np.cos(omega_mod * t) return sig diff --git a/audiofile/core/convert.py b/audiofile/core/convert.py index d8453f3..dde7873 100644 --- a/audiofile/core/convert.py +++ b/audiofile/core/convert.py @@ -7,10 +7,10 @@ def convert( - infile: str, - outfile: str, - offset: float = 0, - duration: float = None, + infile: str, + outfile: str, + offset: float = 0, + duration: float = None, ): """Convert any audio/video file to WAV. @@ -29,6 +29,6 @@ def convert( # Convert to WAV file with ffmpeg run_ffmpeg(infile, outfile, offset, duration) except FileNotFoundError: - raise binary_missing_error('ffmpeg') + raise binary_missing_error("ffmpeg") except subprocess.CalledProcessError: raise broken_file_error(infile) diff --git a/audiofile/core/info.py b/audiofile/core/info.py index 28ab997..4181d9b 100644 --- a/audiofile/core/info.py +++ b/audiofile/core/info.py @@ -33,35 +33,35 @@ def bit_depth(file: str) -> typing.Optional[int]: broken or format is not supported Examples: - >>> bit_depth('stereo.wav') + >>> bit_depth("stereo.wav") 16 """ file = audeer.safe_path(file) file_type = file_extension(file) - if file_type == 'wav': + if file_type == "wav": precision_mapping = { - 'PCM_16': 16, - 'PCM_24': 24, - 'PCM_32': 32, - 'PCM_U8': 8, - 'FLOAT': 32, - 'DOUBLE': 64, - 'ULAW': 8, - 'ALAW': 8, - 'IMA_ADPCM': 4, - 'MS_ADPCM': 4, - 'GSM610': 16, # not sure if this could be variable? - 'G721_32': 4, # not sure if correct + "PCM_16": 16, + "PCM_24": 24, + "PCM_32": 32, + "PCM_U8": 8, + "FLOAT": 32, + "DOUBLE": 64, + "ULAW": 8, + "ALAW": 8, + "IMA_ADPCM": 4, + "MS_ADPCM": 4, + "GSM610": 16, # not sure if this could be variable? + "G721_32": 4, # not sure if correct } - elif file_type == 'flac': + elif file_type == "flac": precision_mapping = { - 'PCM_16': 16, - 'PCM_24': 24, - 'PCM_32': 32, - 'PCM_S8': 8, + "PCM_16": 16, + "PCM_24": 24, + "PCM_32": 32, + "PCM_S8": 8, } - if file_extension(file) in ['wav', 'flac']: + if file_extension(file) in ["wav", "flac"]: depth = precision_mapping[soundfile.info(file).subtype] else: depth = None @@ -85,7 +85,7 @@ def channels(file: str) -> int: broken or format is not supported Examples: - >>> channels('stereo.wav') + >>> channels("stereo.wav") 2 """ @@ -94,20 +94,16 @@ def channels(file: str) -> int: return soundfile.info(file).channels else: try: - cmd = ['soxi', '-c', file] + cmd = ["soxi", "-c", file] return int(run(cmd)) except (FileNotFoundError, subprocess.CalledProcessError): # For MP4 stored and returned number of channels can be different - cmd1 = [ - 'mediainfo', '--Inform=Audio;%Channel(s)_Original%', file - ] - cmd2 = [ - 'mediainfo', '--Inform=Audio;%Channel(s)%', file - ] + cmd1 = ["mediainfo", "--Inform=Audio;%Channel(s)_Original%", file] + cmd2 = ["mediainfo", "--Inform=Audio;%Channel(s)%", file] try: return int(run(cmd1)) except FileNotFoundError: - raise binary_missing_error('mediainfo') + raise binary_missing_error("mediainfo") except (ValueError, subprocess.CalledProcessError): try: return int(run(cmd2)) @@ -152,7 +148,7 @@ def duration(file: str, sloppy=False) -> float: broken or format is not supported Examples: - >>> duration('stereo.wav') + >>> duration("stereo.wav") 1.5 """ @@ -162,17 +158,17 @@ def duration(file: str, sloppy=False) -> float: if sloppy: try: - cmd = ['soxi', '-D', file] + cmd = ["soxi", "-D", file] duration = float(run(cmd)) except (FileNotFoundError, subprocess.CalledProcessError): try: - cmd = ['mediainfo', '--Inform=Audio;%Duration%', file] + cmd = ["mediainfo", "--Inform=Audio;%Duration%", file] duration = run(cmd) if duration: # Convert to seconds, as mediainfo returns milliseconds duration = float(duration) / 1000 except FileNotFoundError: - raise binary_missing_error('mediainfo') + raise binary_missing_error("mediainfo") # Behavior for broken files is different on Windows # where no error is raised, # but an empty duration is returned. @@ -206,22 +202,21 @@ def samples(file: str) -> int: broken or format is not supported Examples: - >>> samples('stereo.wav') + >>> samples("stereo.wav") 12000 """ + def samples_as_int(file): - return int( - soundfile.info(file).duration * soundfile.info(file).samplerate - ) + return int(soundfile.info(file).duration * soundfile.info(file).samplerate) file = audeer.safe_path(file) if file_extension(file) in SNDFORMATS: return samples_as_int(file) else: # Always convert to WAV for non SNDFORMATS - with tempfile.TemporaryDirectory(prefix='audiofile') as tmpdir: - tmpfile = os.path.join(tmpdir, 'tmp.wav') + with tempfile.TemporaryDirectory(prefix="audiofile") as tmpdir: + tmpfile = os.path.join(tmpdir, "tmp.wav") convert_to_wav(file, tmpfile) return samples_as_int(tmpfile) @@ -242,7 +237,7 @@ def sampling_rate(file: str) -> int: broken or format is not supported Examples: - >>> sampling_rate('stereo.wav') + >>> sampling_rate("stereo.wav") 8000 """ @@ -251,11 +246,11 @@ def sampling_rate(file: str) -> int: return soundfile.info(file).samplerate else: try: - cmd = ['soxi', '-r', file] + cmd = ["soxi", "-r", file] return int(run(cmd)) except (FileNotFoundError, subprocess.CalledProcessError): try: - cmd = ['mediainfo', '--Inform=Audio;%SamplingRate%', file] + cmd = ["mediainfo", "--Inform=Audio;%SamplingRate%", file] sampling_rate = run(cmd) if sampling_rate: return int(sampling_rate) @@ -264,6 +259,6 @@ def sampling_rate(file: str) -> int: # to align coverage under Windows and Linux raise subprocess.CalledProcessError(-2, cmd) except FileNotFoundError: - raise binary_missing_error('mediainfo') + raise binary_missing_error("mediainfo") except subprocess.CalledProcessError: raise broken_file_error(file) diff --git a/audiofile/core/io.py b/audiofile/core/io.py index 36f6b60..4b2dbeb 100644 --- a/audiofile/core/io.py +++ b/audiofile/core/io.py @@ -16,14 +16,14 @@ def convert_to_wav( - infile: str, - outfile: str = None, - offset: typing.Union[float, int, str, np.timedelta64] = None, - duration: typing.Union[float, int, str, np.timedelta64] = None, - bit_depth: int = 16, - normalize: bool = False, - overwrite: bool = False, - **kwargs, + infile: str, + outfile: str = None, + offset: typing.Union[float, int, str, np.timedelta64] = None, + duration: typing.Union[float, int, str, np.timedelta64] = None, + bit_depth: int = 16, + normalize: bool = False, + overwrite: bool = False, + **kwargs, ) -> str: """Convert any audio/video file to WAV. @@ -88,20 +88,17 @@ def convert_to_wav( or the provided unit is not supported Examples: - >>> path = convert_to_wav('stereo.flac') + >>> path = convert_to_wav("stereo.flac") >>> os.path.basename(path) 'stereo.wav' """ infile = audeer.safe_path(infile) if outfile is None: - outfile = audeer.replace_file_extension(infile, 'wav') + outfile = audeer.replace_file_extension(infile, "wav") else: outfile = audeer.safe_path(outfile) - if ( - infile == outfile - and not overwrite - ): + if infile == outfile and not overwrite: raise RuntimeError( f"'{infile}' would be overwritten. " "Select 'overwrite=True', " @@ -124,12 +121,12 @@ def convert_to_wav( def read( - file: str, - duration: typing.Union[float, int, str, np.timedelta64] = None, - offset: typing.Union[float, int, str, np.timedelta64] = None, - always_2d: bool = False, - dtype: str = 'float32', - **kwargs, + file: str, + duration: typing.Union[float, int, str, np.timedelta64] = None, + offset: typing.Union[float, int, str, np.timedelta64] = None, + always_2d: bool = False, + dtype: str = "float32", + **kwargs, ) -> typing.Tuple[np.array, int]: """Read audio file. @@ -202,12 +199,12 @@ def read( .. plot:: :context: close-figs - >>> signal, sampling_rate = read('mono.wav', always_2d=True) + >>> signal, sampling_rate = read("mono.wav", always_2d=True) >>> sampling_rate 8000 >>> signal.shape (1, 12000) - >>> signal, sampling_rate = read('mono.wav') + >>> signal, sampling_rate = read("mono.wav") >>> signal.shape (12000,) >>> import audplot @@ -216,7 +213,7 @@ def read( .. plot:: :context: close-figs - >>> signal, sampling_rate = read('mono.wav', duration=0.5) + >>> signal, sampling_rate = read("mono.wav", duration=0.5) >>> # Extend signal to original length >>> signal = np.pad(signal, (0, 8000)) >>> audplot.waveform(signal) @@ -224,7 +221,7 @@ def read( .. plot:: :context: close-figs - >>> signal, sampling_rate = read('mono.wav', duration=-0.5) + >>> signal, sampling_rate = read("mono.wav", duration=-0.5) >>> # Extend signal to original length >>> signal = np.pad(signal, (8000, 0)) >>> audplot.waveform(signal) @@ -232,7 +229,7 @@ def read( .. plot:: :context: close-figs - >>> signal, sampling_rate = read('mono.wav', offset='4000', duration='4000') + >>> signal, sampling_rate = read("mono.wav", offset="4000", duration="4000") >>> # Extend signal to original length >>> signal = np.pad(signal, (4000, 4000)) >>> audplot.waveform(signal) @@ -242,7 +239,7 @@ def read( >>> # Use audresample for resampling and remixing >>> import audresample - >>> signal, sampling_rate = read('stereo.wav') + >>> signal, sampling_rate = read("stereo.wav") >>> signal.shape (2, 12000) >>> target_rate = 16000 @@ -259,13 +256,14 @@ def read( # Parse offset and duration values if ( - duration is not None - or isinstance(duration, str) - or (offset is not None and isinstance(offset, str)) - or (offset is not None and offset != 0) + duration is not None + or isinstance(duration, str) + or (offset is not None and isinstance(offset, str)) + or (offset is not None and offset != 0) ): # Import sampling_rate here to avoid circular imports from audiofile.core.info import sampling_rate as get_sampling_rate + sampling_rate = get_sampling_rate(file) if duration is not None: duration = duration_in_seconds(duration, sampling_rate) @@ -279,33 +277,22 @@ def read( # Support for negative offset/duration values # by counting them from end of signal # - if ( - offset is not None and offset < 0 - or duration is not None and duration < 0 - ): + if offset is not None and offset < 0 or duration is not None and duration < 0: # Import duration here to avoid circular imports from audiofile.core.info import duration as get_duration + signal_duration = get_duration(file) # offset | duration # None | < 0 - if ( - offset is None - and duration is not None and duration < 0 - ): + if offset is None and duration is not None and duration < 0: offset = max([0, signal_duration + duration]) duration = None # None | >= 0 - if ( - offset is None - and duration is not None and duration >= 0 - ): + if offset is None and duration is not None and duration >= 0: if np.isinf(duration): duration = None # >= 0 | < 0 - elif ( - offset is not None and offset >= 0 - and duration is not None and duration < 0 - ): + elif offset is not None and offset >= 0 and duration is not None and duration < 0: if np.isinf(offset) and np.isinf(duration): offset = 0 duration = None @@ -319,32 +306,20 @@ def read( offset = max([0, offset + duration]) duration = min([-duration, orig_offset]) # >= 0 | >= 0 - elif ( - offset is not None and offset >= 0 - and duration is not None and duration >= 0 - ): + elif offset is not None and offset >= 0 and duration is not None and duration >= 0: if np.isinf(offset): duration = 0 elif np.isinf(duration): duration = None # < 0 | None - elif ( - offset is not None and offset < 0 - and duration is None - ): + elif offset is not None and offset < 0 and duration is None: offset = max([0, signal_duration + offset]) # >= 0 | None - elif ( - offset is not None and offset >= 0 - and duration is None - ): + elif offset is not None and offset >= 0 and duration is None: if np.isinf(offset): duration = 0 # < 0 | > 0 - elif ( - offset is not None and offset < 0 - and duration is not None and duration > 0 - ): + elif offset is not None and offset < 0 and duration is not None and duration > 0: if np.isinf(offset) and np.isinf(duration): offset = 0 duration = None @@ -360,10 +335,7 @@ def read( duration = min([duration, signal_duration - offset]) offset = max([0, offset]) # < 0 | < 0 - elif ( - offset is not None and offset < 0 - and duration is not None and duration < 0 - ): + elif offset is not None and offset < 0 and duration is not None and duration < 0: if np.isinf(offset): duration = 0 elif np.isinf(duration): @@ -383,6 +355,7 @@ def read( duration = audmath.samples(duration, sampling_rate) if duration == 0: from audiofile.core.info import channels as get_channels + channels = get_channels(file) if channels > 1 or always_2d: signal = np.zeros((channels, 0)) @@ -406,8 +379,8 @@ def read( # It might be the case that MP3 files will be supported by soundfile in # the future as well. For a discussion on MP3 support in the underlying # libsndfile see https://github.com/erikd/libsndfile/issues/258. - with tempfile.TemporaryDirectory(prefix='audiofile') as tmpdir: - tmpfile = os.path.join(tmpdir, 'tmp.wav') + with tempfile.TemporaryDirectory(prefix="audiofile") as tmpdir: + tmpfile = os.path.join(tmpdir, "tmp.wav") # offset and duration have to be given in seconds if offset != 0: offset /= sampling_rate @@ -441,12 +414,12 @@ def read( def write( - file: str, - signal: np.array, - sampling_rate: int, - bit_depth: int = 16, - normalize: bool = False, - **kwargs, + file: str, + signal: np.array, + sampling_rate: int, + bit_depth: int = 16, + normalize: bool = False, + **kwargs, ): """Write (normalized) audio files. @@ -478,29 +451,29 @@ def write( Examples: >>> sampling_rate = 8000 >>> signal = np.random.uniform(-1, 1, (1, 1000)) - >>> write('mono.wav', signal, sampling_rate) + >>> write("mono.wav", signal, sampling_rate) >>> signal = np.random.uniform(-1.2, 1.2, (2, 1000)) - >>> write('stereo.flac', signal, sampling_rate, normalize=True) + >>> write("stereo.flac", signal, sampling_rate, normalize=True) """ # noqa: E501 file = audeer.safe_path(file) file_type = file_extension(file) # Check for allowed precisions - if file_type == 'wav': + if file_type == "wav": depth_mapping = { - 8: 'PCM_U8', - 16: 'PCM_16', - 24: 'PCM_24', - 32: 'PCM_32', + 8: "PCM_U8", + 16: "PCM_16", + 24: "PCM_24", + 32: "PCM_32", } - elif file_type == 'flac': + elif file_type == "flac": depth_mapping = { - 8: 'PCM_S8', - 16: 'PCM_16', - 24: 'PCM_24', + 8: "PCM_S8", + 16: "PCM_16", + 24: "PCM_24", } - if file_type in ['wav', 'flac']: + if file_type in ["wav", "flac"]: bit_depths = sorted(list(depth_mapping.keys())) if bit_depth not in bit_depths: raise RuntimeError( @@ -516,10 +489,10 @@ def write( else: channels = 1 if channels > MAX_CHANNELS[file_type]: - if file_type != 'wav': + if file_type != "wav": hint = " Consider using 'wav' instead." else: - hint = '' + hint = "" raise RuntimeError( "The maximum number of allowed channels " f"for '{file_type}' is {MAX_CHANNELS[file_type]}.{hint}" diff --git a/audiofile/core/utils.py b/audiofile/core/utils.py index eab3955..c76e87d 100644 --- a/audiofile/core/utils.py +++ b/audiofile/core/utils.py @@ -8,14 +8,14 @@ MAX_CHANNELS = { - 'wav': 65535, - 'ogg': 255, - 'mp3': 2, - 'flac': 8, + "wav": 65535, + "ogg": 255, + "mp3": 2, + "flac": 8, } r"""Maximum number of channels per format.""" -SNDFORMATS = ['wav', 'flac', 'mp3', 'ogg'] +SNDFORMATS = ["wav", "flac", "mp3", "ogg"] r"""File formats handled by soundfile""" @@ -30,10 +30,10 @@ def binary_missing_error(binary: str) -> Exception: """ return FileNotFoundError( - f'{binary} cannot be found.\n' - 'Please make sure it is installed.\n' - 'For further instructions visit: ' - 'https://audeering.github.io/audiofile/installation.html' + f"{binary} cannot be found.\n" + "Please make sure it is installed.\n" + "For further instructions visit: " + "https://audeering.github.io/audiofile/installation.html" ) @@ -52,14 +52,13 @@ def broken_file_error(file: str) -> Exception: """ return RuntimeError( - f'Error opening {file}: ' - 'File contains data in an unknown format.' + f"Error opening {file}: File contains data in an unknown format." ) def duration_in_seconds( - duration: typing.Union[float, int, str, np.timedelta64], - sampling_rate: typing.Union[float, int], + duration: typing.Union[float, int, str, np.timedelta64], + sampling_rate: typing.Union[float, int], ) -> np.floating: r"""Duration in seconds. @@ -84,51 +83,26 @@ def file_extension(path): def run(shell_command): """Return the output of a shell command provided as string.""" - out = subprocess.check_output( - shell_command, - stderr=subprocess.STDOUT - ) + out = subprocess.check_output(shell_command, stderr=subprocess.STDOUT) try: return out.split()[0] except IndexError: - return '' + return "" def run_ffmpeg(infile, outfile, offset, duration): """Convert audio file to WAV file.""" if duration: - cmd = [ - 'ffmpeg', - '-ss', str(offset), - '-i', infile, - '-t', str(duration), - outfile - ] + cmd = ["ffmpeg", "-ss", str(offset), "-i", infile, "-t", str(duration), outfile] else: - cmd = [ - 'ffmpeg', - '-ss', str(offset), - '-i', infile, - outfile, - ] + cmd = ["ffmpeg", "-ss", str(offset), "-i", infile, outfile] run(cmd) def run_sox(infile, outfile, offset, duration): """Convert audio file to WAV file.""" if duration: - cmd = [ - 'sox', - infile, - outfile, - 'trim', str(offset), - str(duration), - ] + cmd = ["sox", infile, outfile, "trim", str(offset), str(duration)] else: - cmd = [ - 'sox', - infile, - outfile, - 'trim', str(offset), - ] + cmd = ["sox", infile, outfile, "trim", str(offset)] run(cmd) diff --git a/docs/benchmark/benchmark_info.py b/docs/benchmark/benchmark_info.py index 79a6d81..fbce45a 100644 --- a/docs/benchmark/benchmark_info.py +++ b/docs/benchmark/benchmark_info.py @@ -1,9 +1,10 @@ +import argparse import os import os.path import time -import argparse -import utils + import loaders +import utils def get_files(dir, extension): @@ -23,7 +24,7 @@ def __init__( self, root, download=True, - extension='wav', + extension="wav", lib="librosa", ): self.root = os.path.expanduser(root) @@ -39,59 +40,53 @@ def __len__(self): if __name__ == "__main__": - - parser = argparse.ArgumentParser(description='Process some integers.') - parser.add_argument('--ext', type=str, default="wav") + parser = argparse.ArgumentParser(description="Process some integers.") + parser.add_argument("--ext", type=str, default="wav") args = parser.parse_args() columns = [ - 'ext', - 'lib', - 'duration', - 'time', + "ext", + "lib", + "duration", + "time", ] - store = utils.DF_writer(columns) + store = utils.DFWriter(columns) # libraries to be benchmarked libs = [ - 'ar_ffmpeg', - 'ar_mad', - 'audiofile', - 'audiofile_sloppy', - 'pedalboard', - 'soundfile', - 'sox', + "ar_ffmpeg", + "ar_mad", + "audiofile", + "audiofile_sloppy", + "pedalboard", + "soundfile", + "sox", ] - audio_walk = sorted(os.walk('AUDIO')) + audio_walk = sorted(os.walk("AUDIO")) if len(audio_walk) == 0: raise RuntimeError( - 'No audio files were found.\n' + "No audio files were found.\n" "Make sure you executed 'bash generate_audio.sh'" ) for lib in libs: - # MP4 and MP3 is not supported by all libraries - if ( - lib in ['soundfile', 'sox', 'pedalboard'] - and args.ext == 'mp4' - ): + if lib in ["soundfile", "sox", "pedalboard"] and args.ext == "mp4": continue - if lib == 'ar_ffmpeg' and args.ext == 'mp3': # too slow + if lib == "ar_ffmpeg" and args.ext == "mp3": # too slow continue - if lib == 'ar_mad' and args.ext != 'mp3': + if lib == "ar_mad" and args.ext != "mp3": continue print(f"Benchmark metadata {args.ext} with {lib}") for root, dirs, fnames in audio_walk: for audio_dir in dirs: - duration = int(audio_dir) dataset = AudioFolder( os.path.join(root, audio_dir), - lib='info_' + lib, + lib="info_" + lib, extension=args.ext, ) @@ -100,13 +95,13 @@ def __len__(self): # with contextlib.suppress(UserWarning): for fp in dataset.audio_files: info = dataset.loader_function(fp) - info['duration'] + info["duration"] end = time.time() # Store ar_ffmpeg and ar_mad as audioread - if lib in ['ar_ffmpeg', 'ar_mad']: - lib_name = 'audioread' + if lib in ["ar_ffmpeg", "ar_mad"]: + lib_name = "audioread" else: lib_name = lib @@ -117,4 +112,4 @@ def __len__(self): time=float(end - start) / len(dataset), ) - store.df.to_pickle(f'results/benchmark_info_{args.ext}.pickle') + store.df.to_pickle(f"results/benchmark_info_{args.ext}.pickle") diff --git a/docs/benchmark/benchmark_read.py b/docs/benchmark/benchmark_read.py index 4d02b4d..c1d8a55 100644 --- a/docs/benchmark/benchmark_read.py +++ b/docs/benchmark/benchmark_read.py @@ -2,14 +2,13 @@ import os import time +import loaders import matplotlib import numpy as np - import utils -import loaders -matplotlib.use('Agg') +matplotlib.use("Agg") def get_files(dir, extension): @@ -29,7 +28,7 @@ def __init__( self, root, download=True, - extension='wav', + extension="wav", lib="librosa", ): self.root = os.path.expanduser(root) @@ -45,60 +44,57 @@ def __len__(self): if __name__ == "__main__": - - parser = argparse.ArgumentParser(description='Process some integers.') - parser.add_argument('--ext', type=str, default="wav") + parser = argparse.ArgumentParser(description="Process some integers.") + parser.add_argument("--ext", type=str, default="wav") args = parser.parse_args() columns = [ - 'ext', - 'lib', - 'duration', - 'time', + "ext", + "lib", + "duration", + "time", ] - store = utils.DF_writer(columns) + store = utils.DFWriter(columns) # libraries to be benchmarked libs = [ - 'ar_ffmpeg', - 'ar_mad', - 'audiofile', - 'librosa', - 'pedalboard', - 'scipy', - 'soundfile', + "ar_ffmpeg", + "ar_mad", + "audiofile", + "librosa", + "pedalboard", + "scipy", + "soundfile", ] - audio_walk = sorted(os.walk('AUDIO')) + audio_walk = sorted(os.walk("AUDIO")) if len(audio_walk) == 0: raise RuntimeError( - 'No audio files were found.\n' + "No audio files were found.\n" "Make sure you executed 'bash generate_audio.sh'" ) for lib in libs: - # Not all libraries support all file formats - if lib == 'scipy' and args.ext != 'wav': + if lib == "scipy" and args.ext != "wav": continue - if lib == 'ar_ffmpeg' and args.ext == 'mp3': # too slow + if lib == "ar_ffmpeg" and args.ext == "mp3": # too slow continue - if lib == 'ar_mad' and args.ext != 'mp3': + if lib == "ar_mad" and args.ext != "mp3": continue - if lib == 'soundfile' and args.ext == 'mp4': + if lib == "soundfile" and args.ext == "mp4": continue - if lib == 'pedalboard' and args.ext == 'mp4': + if lib == "pedalboard" and args.ext == "mp4": continue print(f"Benchmark read {args.ext} with {lib}") for root, dirs, fnames in audio_walk: for audio_dir in dirs: - duration = int(audio_dir) dataset = AudioFolder( os.path.join(root, audio_dir), - lib='load_' + lib, + lib="load_" + lib, extension=args.ext, ) @@ -111,8 +107,8 @@ def __len__(self): end = time.time() # Store ar_ffmpeg and ar_mad as audioread - if lib in ['ar_ffmpeg', 'ar_mad']: - lib_name = 'audioread' + if lib in ["ar_ffmpeg", "ar_mad"]: + lib_name = "audioread" else: lib_name = lib @@ -123,4 +119,4 @@ def __len__(self): time=float(end - start) / len(dataset), ) - store.df.to_pickle(f'results/benchmark_read_{args.ext}.pickle') + store.df.to_pickle(f"results/benchmark_read_{args.ext}.pickle") diff --git a/docs/benchmark/loaders.py b/docs/benchmark/loaders.py index 07b759e..a70b95f 100644 --- a/docs/benchmark/loaders.py +++ b/docs/benchmark/loaders.py @@ -1,15 +1,17 @@ -import audiofile as af -import audioread.rawread +import audioread.ffdec import audioread.gstdec import audioread.maddec -import audioread.ffdec -import pedalboard -import soundfile as sf -import numpy as np +import audioread.rawread import librosa +import numpy as np +import pedalboard from scipy.io import wavfile +import soundfile as sf import sox +import audiofile as af + + """ Some of the code taken from: https://github.com/aubio/aubio/blob/master/python/demos/demo_reading_speed.py @@ -23,13 +25,13 @@ def load_soundfile(fp): def load_scipy(fp): rate, sig = wavfile.read(fp) - sig = sig.astype('float32') / 32767 + sig = sig.astype("float32") / 32767 return sig def load_scipy_mmap(fp): rate, sig = wavfile.read(fp, mmap=True) - sig = sig.astype('float32') / 32767 + sig = sig.astype("float32") / 32767 return sig @@ -64,8 +66,6 @@ def load_ar_ffmpeg(fp): def load_librosa(fp): - """Librosa audio loading is using - """ # loading with `sr=None` is disabling the internal resampling sig, rate = librosa.load(fp, sr=None) return sig @@ -85,9 +85,9 @@ def load_pedalboard(fp): def _convert_buffer_to_float(buf, n_bytes=2, dtype=np.float32): # taken from librosa.util.utils # Invert the scale of the data - scale = 1. / float(1 << ((8 * n_bytes) - 1)) + scale = 1.0 / float(1 << ((8 * n_bytes) - 1)) # Construct the format string - fmt = f'>> |\.\.\. |\$ ' +copybutton_prompt_text = r">>> |\.\.\. |\$ " copybutton_prompt_is_regexp = True # Plotting plot_include_source = True plot_html_show_source_link = False plot_html_show_formats = False -plot_pre_code = '' +plot_pre_code = "" plot_rcparams = { - 'figure.figsize': '5, 3.8', # inch + "figure.figsize": "5, 3.8", # inch } -plot_formats = ['png'] +plot_formats = ["png"] # Disable auto-generation of TOC entries in the API # https://github.com/sphinx-doc/sphinx/issues/6316 @@ -80,29 +78,28 @@ # HTML -------------------------------------------------------------------- -html_theme = 'sphinx_audeering_theme' +html_theme = "sphinx_audeering_theme" html_theme_options = { - 'display_version': True, - 'footer_links': False, - 'logo_only': False, + "display_version": True, + "footer_links": False, + "logo_only": False, } html_context = { - 'display_github': True, + "display_github": True, } html_title = title # Copy API (sub-)module RST files to docs/api/ folder --------------------- -audeer.rmdir('api') -audeer.mkdir('api') -api_src_files = audeer.list_file_names('api-src') +audeer.rmdir("api") +audeer.mkdir("api") +api_src_files = audeer.list_file_names("api-src") api_dst_files = [ - audeer.path('api', os.path.basename(src_file)) - for src_file in api_src_files + audeer.path("api", os.path.basename(src_file)) for src_file in api_src_files ] for src_file, dst_file in zip(api_src_files, api_dst_files): shutil.copyfile(src_file, dst_file) # Create audio files ------------------------------------------------------ -create_audio_files('api') +create_audio_files("api") diff --git a/docs/usage.rst b/docs/usage.rst index daba831..d869ac2 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -24,7 +24,7 @@ let's create a dummy signal containing noise: sampling_rate = 8000 # in Hz noise = np.random.normal(0, 1, sampling_rate) noise /= np.amax(np.abs(noise)) - audiofile.write('noise.flac', noise, sampling_rate) + audiofile.write("noise.flac", noise, sampling_rate) File information @@ -34,23 +34,23 @@ Now you can get metadata information on that signal: .. jupyter-execute:: - audiofile.channels('noise.flac') + audiofile.channels("noise.flac") .. jupyter-execute:: - audiofile.duration('noise.flac') + audiofile.duration("noise.flac") .. jupyter-execute:: - audiofile.samples('noise.flac') + audiofile.samples("noise.flac") .. jupyter-execute:: - audiofile.sampling_rate('noise.flac') + audiofile.sampling_rate("noise.flac") .. jupyter-execute:: - audiofile.bit_depth('noise.flac') + audiofile.bit_depth("noise.flac") Read a file @@ -60,10 +60,10 @@ You can read the signal: .. jupyter-execute:: - signal, sampling_rate = audiofile.read('noise.flac') + signal, sampling_rate = audiofile.read("noise.flac") - print(f'sampling rate: {sampling_rate}') - print(f'signal shape: {signal.shape}') + print(f"sampling rate: {sampling_rate}") + print(f"signal shape: {signal.shape}") If you prefer a workflow that returns a 2D signal with channel as the first dimension, @@ -71,19 +71,19 @@ enforce it with: .. jupyter-execute:: - signal, sampling_rate = audiofile.read('noise.flac', always_2d=True) + signal, sampling_rate = audiofile.read("noise.flac", always_2d=True) - print(f'sampling rate: {sampling_rate}') - print(f'signal shape: {signal.shape}') + print(f"sampling rate: {sampling_rate}") + print(f"signal shape: {signal.shape}") If you just want to read from 500 ms to 900 ms of the signal: .. jupyter-execute:: - signal, sampling_rate = audiofile.read('noise.flac', offset=0.5, duration=0.4) + signal, sampling_rate = audiofile.read("noise.flac", offset=0.5, duration=0.4) - print(f'sampling rate: {sampling_rate}') - print(f'signal shape: {signal.shape}') + print(f"sampling rate: {sampling_rate}") + print(f"signal shape: {signal.shape}") Convert a file @@ -95,9 +95,9 @@ You can convert any file to WAV using: import audeer - audiofile.convert_to_wav('noise.flac', 'noise.wav') + audiofile.convert_to_wav("noise.flac", "noise.wav") - audeer.list_file_names('.', filetype='wav', basenames=True) + audeer.list_file_names(".", filetype="wav", basenames=True) Resample/Remix a file @@ -114,13 +114,13 @@ But it can be easily achieved with :mod:`audresample`. import audresample target_rate = 16000 - signal, sampling_rate = audiofile.read('noise.flac', always_2d=True) + signal, sampling_rate = audiofile.read("noise.flac", always_2d=True) signal = audresample.resample(signal, sampling_rate, target_rate) signal = audresample.remix(signal, channels=[0, 0]) - audiofile.write('noise-remix.flac', signal, target_rate) + audiofile.write("noise-remix.flac", signal, target_rate) - print(f'sampling rate: {audiofile.sampling_rate("noise-remix.flac")}') - print(f'signal shape: {signal.shape}') + print(f"sampling rate: {audiofile.sampling_rate('noise-remix.flac')}") + print(f"signal shape: {signal.shape}") .. _soundfile: https://python-soundfile.readthedocs.io/ @@ -135,6 +135,6 @@ But it can be easily achieved with :mod:`audresample`. :hide-output: import os - os.remove('noise.wav') - os.remove('noise.flac') - os.remove('noise-remix.flac') + os.remove("noise.wav") + os.remove("noise.flac") + os.remove("noise-remix.flac") diff --git a/pyproject.toml b/pyproject.toml index abce202..7fff43a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,12 @@ addopts = ''' # ----- ruff -------------------------------------------------------------- # [tool.ruff] +cache-dir = '.cache/ruff' + +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint] select = [ 'D', # pydocstyle 'E', # pycodestyle errors @@ -101,21 +107,22 @@ exclude = [ 'docs/benchmark', ] -line-length = 79 - -cache-dir = '.cache/ruff' - -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] '__init__.py' = [ 'F401', # * imported but unused ] +'docs/benchmark/*' = [ + 'D101', # Missing docstring in public class + 'D102', # Missing docstring in public method + 'D105', # Missing docstring in magic method +] # ----- I: isort ----- # # Check correct order/syntax of import statements # -[tool.ruff.isort] +[tool.ruff.lint.isort] # All from imports have their own line, e.g. # @@ -152,7 +159,7 @@ section-order = [ 'first-party', 'local-folder', ] -[tool.ruff.isort.sections] +[tool.ruff.lint.isort.sections] 'audeering' = [ 'audb', 'audbackend', @@ -176,7 +183,7 @@ section-order = [ # # Check variable/class names follow PEP8 naming convention # -[tool.ruff.pep8-naming] +[tool.ruff.lint.pep8-naming] ignore-names = [ 'config', # allow lowercase class name 'test_*', # allow uppercase name when testing a class @@ -187,7 +194,7 @@ ignore-names = [ # # Check docstrings follow selected convention # -[tool.ruff.pydocstyle] +[tool.ruff.lint.pydocstyle] convention = 'google' diff --git a/tests/test_audiofile.py b/tests/test_audiofile.py index 3b9b5db..0ba23e8 100644 --- a/tests/test_audiofile.py +++ b/tests/test_audiofile.py @@ -13,10 +13,10 @@ SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) -ASSETS_DIR = os.path.join(SCRIPT_DIR, 'assets') +ASSETS_DIR = os.path.join(SCRIPT_DIR, "assets") -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def audio_file(tmpdir_factory, request): """Fixture to generate audio file. @@ -24,14 +24,14 @@ def audio_file(tmpdir_factory, request): as parameter to this fixture. """ - file = str(tmpdir_factory.mktemp('audio').join('file.wav')) + 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') +@pytest.fixture(scope="function") def empty_file(tmpdir, request): """Fixture to generate empty audio files. @@ -39,7 +39,7 @@ def empty_file(tmpdir, request): """ # Create empty audio file - empty_file = os.path.join(tmpdir, 'empty-file.wav') + empty_file = os.path.join(tmpdir, "empty-file.wav") af.write(empty_file, np.array([[]]), 16000) # Rename to match extension @@ -54,18 +54,18 @@ def empty_file(tmpdir, request): os.remove(ofpath) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def hide_system_path(): """Fixture to hide system path in test.""" - current_path = os.environ['PATH'] - os.environ['PATH'] = '' + current_path = os.environ["PATH"] + os.environ["PATH"] = "" yield - os.environ['PATH'] = current_path + os.environ["PATH"] = current_path -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def non_audio_file(tmpdir, request): """Fixture to generate broken audio files. @@ -74,8 +74,8 @@ def non_audio_file(tmpdir, request): """ # 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() + broken_file = os.path.join(tmpdir, f"broken-file.{file_ext}") + open(broken_file, "w").close() yield broken_file @@ -87,14 +87,14 @@ def tolerance(condition, sampling_rate=0): """Absolute tolerance for different condition.""" tol = 0 if condition == 8: - tol = 2 ** -7 + tol = 2**-7 elif condition == 16: - tol = 2 ** -11 # half precision + tol = 2**-11 # half precision elif condition == 24: - tol = 2 ** -17 # to be checked + tol = 2**-17 # to be checked elif condition == 32: - tol = 2 ** -24 # single precision - elif condition == 'duration': + tol = 2**-24 # single precision + elif condition == "duration": tol = 1 / sampling_rate return tol @@ -104,11 +104,7 @@ def ensure_two_dimensions(x): return np.atleast_2d(x) -def sine(duration=1, - sampling_rate=44100, - channels=1, - magnitude=1, - frequency=100): +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) @@ -119,12 +115,12 @@ def sine(duration=1, def write_and_read( - file, - signal, - sampling_rate, - bit_depth=16, - always_2d=False, - normalize=False, + 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) @@ -149,10 +145,10 @@ def _magnitude(signal): return np.max(np.abs(signal)) -@pytest.mark.parametrize('duration', ['0', 0, 0.0]) -@pytest.mark.parametrize('offset', [0, 1]) +@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')) + file = str(tmpdir.join("signal.wav")) sampling_rate = 8000 signal = sine( duration=0.1, @@ -169,17 +165,17 @@ def test_read(tmpdir, duration, offset): @pytest.mark.parametrize( - 'convert', + "convert", (True, False), ) @pytest.mark.parametrize( - 'empty_file', - ('bin', 'mp4', 'wav'), + "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')) + 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 @@ -192,46 +188,46 @@ def test_empty_file(tmpdir, convert, empty_file): 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': + 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'), + "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='ffmpeg'): + with pytest.raises(expected_error, match="ffmpeg"): signal, sampling_rate = af.read(empty_file) # Metadata - with pytest.raises(expected_error, match='mediainfo'): + with pytest.raises(expected_error, match="mediainfo"): af.channels(empty_file) - with pytest.raises(expected_error, match='ffmpeg'): + with pytest.raises(expected_error, match="ffmpeg"): af.duration(empty_file) - with pytest.raises(expected_error, match='mediainfo'): + with pytest.raises(expected_error, match="mediainfo"): af.duration(empty_file, sloppy=True) - with pytest.raises(expected_error, match='ffmpeg'): + with pytest.raises(expected_error, match="ffmpeg"): af.samples(empty_file) - with pytest.raises(expected_error, match='mediainfo'): + with pytest.raises(expected_error, match="mediainfo"): af.sampling_rate(empty_file) # Convert - with pytest.raises(expected_error, match='ffmpeg'): - converted_file = str(tmpdir.join('signal-converted.wav')) + with pytest.raises(expected_error, match="ffmpeg"): + converted_file = str(tmpdir.join("signal-converted.wav")) af.convert_to_wav(empty_file, converted_file) @pytest.mark.parametrize( - 'ext', - ('bin', 'mp4', 'wav'), + "ext", + ("bin", "mp4", "wav"), ) def test_missing_file(tmpdir, ext): - missing_file = f'missing_file.{ext}' + missing_file = f"missing_file.{ext}" expected_error = RuntimeError # Reading file with pytest.raises(expected_error): @@ -247,25 +243,25 @@ def test_missing_file(tmpdir, ext): af.duration(missing_file, sloppy=True) # Convert with pytest.raises(expected_error): - converted_file = str(tmpdir.join('signal-converted.wav')) + converted_file = str(tmpdir.join("signal-converted.wav")) af.convert_to_wav(missing_file, converted_file) @pytest.mark.parametrize( - 'non_audio_file', - ('bin', 'mp4', 'wav'), + "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' + 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': + if audeer.file_extension(non_audio_file) == "wav": with pytest.raises(expected_error, match=error_msg): af.bit_depth(non_audio_file) else: @@ -282,15 +278,15 @@ def test_broken_file(tmpdir, non_audio_file): af.sampling_rate(non_audio_file) # Convert with pytest.raises(expected_error, match=error_msg): - converted_file = str(tmpdir.join('signal-converted.wav')) + 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("normalize", [False, True]) +@pytest.mark.parametrize("bit_depth", [8, 16, 24]) @pytest.mark.parametrize( - 'file_extension', - ('wav', 'flac', 'ogg', 'mp3'), + "file_extension", + ("wav", "flac", "ogg", "mp3"), ) def test_convert_to_wav(tmpdir, normalize, bit_depth, file_extension): sampling_rate = 8000 @@ -302,9 +298,9 @@ def test_convert_to_wav(tmpdir, normalize, bit_depth, file_extension): sampling_rate=sampling_rate, channels=channels, ) - infile = str(tmpdir.join(f'signal.{file_extension}')) + infile = str(tmpdir.join(f"signal.{file_extension}")) af.write(infile, signal, sampling_rate, bit_depth=bit_depth) - if file_extension == 'wav': + if file_extension == "wav": error_msg = ( f"'{infile}' would be overwritten. " "Select 'overwrite=True', " @@ -323,7 +319,7 @@ def test_convert_to_wav(tmpdir, normalize, bit_depth, file_extension): overwrite=True, ) else: - outfile = str(tmpdir.join('signal_converted.wav')) + outfile = str(tmpdir.join("signal_converted.wav")) af.convert_to_wav( infile, outfile, @@ -339,19 +335,19 @@ def test_convert_to_wav(tmpdir, normalize, bit_depth, file_extension): assert converted_signal.max() <= 1.0 assert converted_signal.min() < -0.95 assert converted_signal.min() >= -1.0 - if file_extension == 'mp3': + 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': + 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']: + elif file_extension in ["wav", "flac"]: assert af.bit_depth(outfile) == bit_depth if normalize: assert abs_difference < tolerance(bit_depth) + magnitude_offset @@ -365,10 +361,8 @@ def test_convert_to_wav(tmpdir, normalize, bit_depth, file_extension): @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) + file = str(tmpdir.join("signal.wav")) + signal = sine(duration=duration, sampling_rate=sampling_rate, channels=channels) sig, fs = write_and_read( file, signal, @@ -384,7 +378,7 @@ def test_wav(tmpdir, bit_depth, duration, sampling_rate, channels, always_2d): info.duration, duration, rtol=0, - atol=tolerance('duration', sampling_rate), + atol=tolerance("duration", sampling_rate), ) assert info.samplerate == sampling_rate assert info.channels == channels @@ -394,23 +388,24 @@ def test_wav(tmpdir, bit_depth, duration, sampling_rate, channels, always_2d): _duration(sig, fs), duration, rtol=0, - atol=tolerance('duration', sampling_rate), + 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_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 type(af.duration(file)) is float - assert type(af.sampling_rate(file)) is int - assert type(af.channels(file)) is int - assert type(af.samples(file)) is int + 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 @@ -430,35 +425,34 @@ def test_wav(tmpdir, bit_depth, duration, sampling_rate, channels, 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]) +@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')) + 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)) + 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("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]) +@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: + if file_type == "flac" and channels > 8: return None - if file_type == 'mp3' and channels > 2: + if file_type == "mp3" and channels > 2: return None - file = str(tmpdir.join('signal.' + file_type)) + file = str(tmpdir.join("signal." + file_type)) signal = sine( magnitude=magnitude, sampling_rate=sampling_rate, @@ -469,7 +463,7 @@ def test_file_type(tmpdir, file_type, magnitude, sampling_rate, channels): # Test file type assert audeer.file_extension(file) == file_type # Test magnitude - if file_type == 'mp3': + if file_type == "mp3": atol = tolerance(8) else: atol = tolerance(16) @@ -491,17 +485,17 @@ def test_file_type(tmpdir, file_type, magnitude, sampling_rate, channels): assert sig.ndim == 2 assert _samples(sig) == _samples(signal) assert info.frames == _samples(signal) - if file_type in ['mp3', 'ogg']: + if file_type in ["mp3", "ogg"]: bit_depth = None assert af.bit_depth(file) == bit_depth def test_other_formats(): files = [ - 'gs-16b-1c-44100hz.opus', - 'gs-16b-1c-8000hz.amr', - 'gs-16b-1c-44100hz.m4a', - 'gs-16b-1c-44100hz.aac', + "gs-16b-1c-44100hz.opus", + "gs-16b-1c-8000hz.amr", + "gs-16b-1c-44100hz.m4a", + "gs-16b-1c-44100hz.aac", ] header_durations = [ # as given by mediainfo 15.839, @@ -531,10 +525,10 @@ def test_other_formats(): @pytest.mark.parametrize( - 'audio_file', + "audio_file", [ ( - np.array([[.0, .0, .1, .1, .2, .2]], dtype=np.float32), + np.array([[0.0, 0.0, 0.1, 0.1, 0.2, 0.2]], dtype=np.float32), 2, # Hz ), ], @@ -545,47 +539,47 @@ def test_other_formats(): # 1 during the second second, # 2 during the third second. @pytest.mark.parametrize( - 'offset, duration, expected', + "offset, duration, expected", [ # None | None - (None, None, [[.0, .0, .1, .1, .2, .2]]), - ('None', 'None', [[.0, .0, .1, .1, .2, .2]]), - ('NaN', 'NaN', [[.0, .0, .1, .1, .2, .2]]), - ('NaT', 'NaT', [[.0, .0, .1, .1, .2, .2]]), - (np.NaN, np.NaN, [[.0, .0, .1, .1, .2, .2]]), - (pd.NaT, pd.NaT, [[.0, .0, .1, .1, .2, .2]]), + (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, .1, .1, .2, .2]]), + (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, .1, .1, .2, .2]]), - (0.5, None, [[.0, .1, .1, .2, .2]]), - (1.0, None, [[.1, .1, .2, .2]]), - (1.5, None, [[.1, .2, .2]]), - (2.0, None, [[.2, .2]]), - (2.5, None, [[.2]]), + (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, .1, .1, .2, .2]]), - ('1', 'None', [[.0, .1, .1, .2, .2]]), - ('2', 'None', [[.1, .1, .2, .2]]), - ('3', 'None', [[.1, .2, .2]]), - ('4', 'None', [[.2, .2]]), - ('5', 'None', [[.2]]), - ('6', 'None', [[]]), - ('7', 'None', [[]]), - ('8', '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, .1, .1, .2, .2]]), - (0.5, np.Inf, [[.0, .1, .1, .2, .2]]), - (1.0, np.Inf, [[.1, .1, .2, .2]]), - (1.5, np.Inf, [[.1, .2, .2]]), - (2.0, np.Inf, [[.2, .2]]), - (2.5, np.Inf, [[.2]]), + (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, [[]]), @@ -594,164 +588,164 @@ def test_other_formats(): (1.5, 0.0, [[]]), (2.0, 0.0, [[]]), (2.5, 0.0, [[]]), - (0.0, 0.5, [[.0]]), - (0.5, 0.5, [[.0]]), - (1.0, 0.5, [[.1]]), - (1.5, 0.5, [[.1]]), - (2.0, 0.5, [[.2]]), - (2.5, 0.5, [[.2]]), + (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.5, 1.0, [[.0, .1]]), - (1.0, 1.0, [[.1, .1]]), - (1.5, 1.0, [[.1, .2]]), - (2.0, 1.0, [[.2, .2]]), - (2.5, 1.0, [[.2]]), + (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, .1]]), - (0.5, 1.5, [[.0, .1, .1]]), - (1.0, 1.5, [[.1, .1, .2]]), - (1.5, 1.5, [[.1, .2, .2]]), - (2.0, 1.5, [[.2, .2]]), - (2.5, 1.5, [[.2]]), + (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, .1, .1]]), - (0.5, 2.0, [[.0, .1, .1, .2]]), - (1.0, 2.0, [[.1, .1, .2, .2]]), - (1.5, 2.0, [[.1, .2, .2]]), - (2.0, 2.0, [[.2, .2]]), - (2.5, 2.0, [[.2]]), + (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, .1, .1, .2]]), - (0.5, 2.5, [[.0, .1, .1, .2, .2]]), - (1.0, 2.5, [[.1, .1, .2, .2]]), - (1.5, 2.5, [[.1, .2, .2]]), - (2.0, 2.5, [[.2, .2]]), - (2.5, 2.5, [[.2]]), + (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, .1, .1, .2, .2]]), - (0.5, 3.0, [[.0, .1, .1, .2, .2]]), - (1.0, 3.0, [[.1, .1, .2, .2]]), - (1.5, 3.0, [[.1, .2, .2]]), - (2.0, 3.0, [[.2, .2]]), - (2.5, 3.0, [[.2]]), + (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, .1, .1, .2, .2]]), - (0.5, 3.5, [[.0, .1, .1, .2, .2]]), - (1.0, 3.5, [[.1, .1, .2, .2]]), - (1.5, 3.5, [[.1, .2, .2]]), - (2.0, 3.5, [[.2, .2]]), - (2.5, 3.5, [[.2]]), + (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, .1, .1, .2, .2]]), - ('Inf', 'Inf', [[]]), - ('Inf', '1', [[]]), - ('0', 'Inf', [[.0, .0, .1, .1, .2, .2]]), - ('1', 'Inf', [[.0, .1, .1, .2, .2]]), - ('2', 'Inf', [[.1, .1, .2, .2]]), - ('3', 'Inf', [[.1, .2, .2]]), - ('4', 'Inf', [[.2, .2]]), - ('5', 'Inf', [[.2]]), - ('6', 'Inf', [[]]), - ('7', 'Inf', [[]]), - ('0', '0', [[]]), - ('1', '0', [[]]), - ('2', '0', [[]]), - ('3', '0', [[]]), - ('4', '0', [[]]), - ('5', '0', [[]]), - ('0', '1', [[.0]]), - ('1', '1', [[.0]]), - ('2', '1', [[.1]]), - ('3', '1', [[.1]]), - ('4', '1', [[.2]]), - ('5', '1', [[.2]]), - ('6', '1', [[]]), - ('0', '2', [[.0, .0]]), - ('1', '2', [[.0, .1]]), - ('2', '2', [[.1, .1]]), - ('3', '2', [[.1, .2]]), - ('4', '2', [[.2, .2]]), - ('5', '2', [[.2]]), - ('6', '2', [[]]), - ('0', '3', [[.0, .0, .1]]), - ('1', '3', [[.0, .1, .1]]), - ('2', '3', [[.1, .1, .2]]), - ('3', '3', [[.1, .2, .2]]), - ('4', '3', [[.2, .2]]), - ('5', '3', [[.2]]), - ('6', '3', [[]]), - ('0', '4', [[.0, .0, .1, .1]]), - ('1', '4', [[.0, .1, .1, .2]]), - ('2', '4', [[.1, .1, .2, .2]]), - ('3', '4', [[.1, .2, .2]]), - ('4', '4', [[.2, .2]]), - ('5', '4', [[.2]]), - ('6', '4', [[]]), - ('0', '5', [[.0, .0, .1, .1, .2]]), - ('1', '5', [[.0, .1, .1, .2, .2]]), - ('2', '5', [[.1, .1, .2, .2]]), - ('3', '5', [[.1, .2, .2]]), - ('4', '5', [[.2, .2]]), - ('5', '5', [[.2]]), - ('6', '5', [[]]), - ('0', '6', [[.0, .0, .1, .1, .2, .2]]), - ('1', '6', [[.0, .1, .1, .2, .2]]), - ('2', '6', [[.1, .1, .2, .2]]), - ('3', '6', [[.1, .2, .2]]), - ('4', '6', [[.2, .2]]), - ('5', '6', [[.2]]), - ('6', '6', [[]]), - ('0', '7', [[.0, .0, .1, .1, .2, .2]]), - ('1', '7', [[.0, .1, .1, .2, .2]]), - ('2', '7', [[.1, .1, .2, .2]]), - ('3', '7', [[.1, .2, .2]]), - ('4', '7', [[.2, .2]]), - ('5', '7', [[.2]]), - ('6', '7', [[]]), - ('0', '8', [[.0, .0, .1, .1, .2, .2]]), + (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, .1, .1, .2, .2]]), - (None, -0.5, [[.2]]), - (None, -1.0, [[.2, .2]]), - (None, -1.5, [[.1, .2, .2]]), - (None, -2.0, [[.1, .1, .2, .2]]), - (None, -2.5, [[.0, .1, .1, .2, .2]]), - (None, -3.0, [[.0, .0, .1, .1, .2, .2]]), - (None, -3.5, [[.0, .0, .1, .1, .2, .2]]), - (None, -4.0, [[.0, .0, .1, .1, .2, .2]]), - ('None', '-Inf', [[.0, .0, .1, .1, .2, .2]]), - ('None', '-1', [[.2]]), - ('None', '-2', [[.2, .2]]), - ('None', '-3', [[.1, .2, .2]]), - ('None', '-4', [[.1, .1, .2, .2]]), - ('None', '-5', [[.0, .1, .1, .2, .2]]), - ('None', '-6', [[.0, .0, .1, .1, .2, .2]]), - ('None', '-7', [[.0, .0, .1, .1, .2, .2]]), - ('None', '-8', [[.0, .0, .1, .1, .2, .2]]), + (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, .1, .1, .2, .2]]), - (-0.5, None, [[.2]]), - (-1.0, None, [[.2, .2]]), - (-1.5, None, [[.1, .2, .2]]), - (-2.0, None, [[.1, .1, .2, .2]]), - (-2.5, None, [[.0, .1, .1, .2, .2]]), - (-3.0, None, [[.0, .0, .1, .1, .2, .2]]), - (-3.5, None, [[.0, .0, .1, .1, .2, .2]]), - (-4.0, None, [[.0, .0, .1, .1, .2, .2]]), - ('-Inf', 'None', [[.0, .0, .1, .1, .2, .2]]), - ('-1', 'None', [[.2]]), - ('-2', 'None', [[.2, .2]]), - ('-3', 'None', [[.1, .2, .2]]), - ('-4', 'None', [[.1, .1, .2, .2]]), - ('-5', 'None', [[.0, .1, .1, .2, .2]]), - ('-6', 'None', [[.0, .0, .1, .1, .2, .2]]), - ('-7', 'None', [[.0, .0, .1, .1, .2, .2]]), - ('-7', 'None', [[.0, .0, .1, .1, .2, .2]]), + (-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, .1, .1, .2, .2]]), + (-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, .1, .1, .2, .2]]), + (-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, [[]]), @@ -760,349 +754,349 @@ def test_other_formats(): (-3.0, 0.0, [[]]), (-3.5, 0.0, [[]]), (-4.0, 0.0, [[]]), - (-0.5, 0.5, [[.2]]), - (-1.0, 0.5, [[.2]]), - (-1.5, 0.5, [[.1]]), - (-2.0, 0.5, [[.1]]), - (-2.5, 0.5, [[.0]]), - (-3.0, 0.5, [[.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, [[.2]]), - (-1.0, 1.0, [[.2, .2]]), - (-1.5, 1.0, [[.1, .2]]), - (-2.0, 1.0, [[.1, .1]]), - (-2.5, 1.0, [[.0, .1]]), - (-3.0, 1.0, [[.0, .0]]), - (-3.5, 1.0, [[.0]]), + (-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, [[.2]]), - (-1.0, 1.5, [[.2, .2]]), - (-1.5, 1.5, [[.1, .2, .2]]), - (-2.0, 1.5, [[.1, .1, .2]]), - (-2.5, 1.5, [[.0, .1, .1]]), - (-3.0, 1.5, [[.0, .0, .1]]), - (-3.5, 1.5, [[.0, .0]]), - (-4.0, 1.5, [[.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, [[.2]]), - (-1.0, 2.0, [[.2, .2]]), - (-1.5, 2.0, [[.1, .2, .2]]), - (-2.0, 2.0, [[.1, .1, .2, .2]]), - (-2.5, 2.0, [[.0, .1, .1, .2]]), - (-3.0, 2.0, [[.0, .0, .1, .1]]), - (-3.5, 2.0, [[.0, .0, .1]]), - (-4.0, 2.0, [[.0, .0]]), - (-4.5, 2.0, [[.0]]), + (-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, .1, .1, .2, .2]]), - ('-Inf', '8', [[]]), - ('-8', 'Inf', [[.0, .0, .1, .1, .2, .2]]), - ('-1', '0', [[]]), - ('-2', '0', [[]]), - ('-3', '0', [[]]), - ('-4', '0', [[]]), - ('-5', '0', [[]]), - ('-6', '0', [[]]), - ('-7', '0', [[]]), - ('-8', '0', [[]]), - ('-1', '1', [[.2]]), - ('-2', '1', [[.2]]), - ('-3', '1', [[.1]]), - ('-4', '1', [[.1]]), - ('-5', '1', [[.0]]), - ('-6', '1', [[.0]]), - ('-7', '1', [[]]), - ('-8', '1', [[]]), - ('-1', '2', [[.2]]), - ('-2', '2', [[.2, .2]]), - ('-3', '2', [[.1, .2]]), - ('-4', '2', [[.1, .1]]), - ('-5', '2', [[.0, .1]]), - ('-6', '2', [[.0, .0]]), - ('-7', '2', [[.0]]), - ('-8', '2', [[]]), - ('-1', '3', [[.2]]), - ('-2', '3', [[.2, .2]]), - ('-3', '3', [[.1, .2, .2]]), - ('-4', '3', [[.1, .1, .2]]), - ('-5', '3', [[.0, .1, .1]]), - ('-6', '3', [[.0, .0, .1]]), - ('-7', '3', [[.0, .0]]), - ('-8', '3', [[.0]]), - ('-9', '3', [[]]), - ('-1', '4', [[.2]]), - ('-2', '4', [[.2, .2]]), - ('-3', '4', [[.1, .2, .2]]), - ('-4', '4', [[.1, .1, .2, .2]]), - ('-5', '4', [[.0, .1, .1, .2]]), - ('-6', '4', [[.0, .0, .1, .1]]), - ('-7', '4', [[.0, .0, .1]]), - ('-8', '4', [[.0, .0]]), - ('-9', '4', [[.0]]), - ('-10', '4', [[]]), + ("-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, .1, .1, .2, .2]]), + (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, .1, .1, .2, .2]]), - (2.0, -np.Inf, [[.0, .0, .1, .1]]), + (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]]), - (1.0, -0.5, [[.0]]), - (1.5, -0.5, [[.1]]), - (2.0, -0.5, [[.1]]), - (2.5, -0.5, [[.2]]), - (3.0, -0.5, [[.2]]), + (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]]), - (1.0, -1.0, [[.0, .0]]), - (1.5, -1.0, [[.0, .1]]), - (2.0, -1.0, [[.1, .1]]), - (2.5, -1.0, [[.1, .2]]), - (3.0, -1.0, [[.2, .2]]), - (3.5, -1.0, [[.2]]), + (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]]), - (1.0, -1.5, [[.0, .0]]), - (1.5, -1.5, [[.0, .0, .1]]), - (2.0, -1.5, [[.0, .1, .1]]), - (2.5, -1.5, [[.1, .1, .2]]), - (3.0, -1.5, [[.1, .2, .2]]), - (3.5, -1.5, [[.2, .2]]), - (4.0, -1.5, [[.2]]), + (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]]), - (1.0, -2.0, [[.0, .0]]), - (1.5, -2.0, [[.0, .0, .1]]), - (2.0, -2.0, [[.0, .0, .1, .1]]), - (2.5, -2.0, [[.0, .1, .1, .2]]), - (3.0, -2.0, [[.1, .1, .2, .2]]), - (3.5, -2.0, [[.1, .2, .2]]), - (4.0, -2.0, [[.2, .2]]), - (4.5, -2.0, [[.2]]), + (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, .1, .1]]), - (2.5, -2.5, [[.0, .0, .1, .1, .2]]), - (3.0, -2.5, [[.0, .1, .1, .2, .2]]), - (3.5, -2.5, [[.1, .1, .2, .2]]), - (4.0, -2.5, [[.1, .2, .2]]), - (4.5, -2.5, [[.2, .2]]), - (5.0, -2.5, [[.2]]), + (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, .1, .1]]), - (2.5, -3.0, [[.0, .0, .1, .1, .2]]), - (3.0, -3.0, [[.0, .0, .1, .1, .2, .2]]), - (3.5, -3.0, [[.0, .1, .1, .2, .2]]), - (4.0, -3.0, [[.1, .1, .2, .2]]), - (4.5, -3.0, [[.1, .2, .2]]), - (5.0, -3.0, [[.2, .2]]), - (5.5, -3.0, [[.2]]), + (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, .1, .1, .2, .2]]), - (3.0, -4.0, [[.0, .0, .1, .1, .2, .2]]), - ('Inf', '-Inf', [[.0, .0, .1, .1, .2, .2]]), - ('Inf', '-8', [[]]), - ('8', '-Inf', [[.0, .0, .1, .1, .2, .2]]), - ('4', '-Inf', [[.0, .0, .1, .1]]), - ('0', '-1', [[]]), - ('1', '-1', [[.0]]), - ('2', '-1', [[.0]]), - ('3', '-1', [[.1]]), - ('4', '-1', [[.1]]), - ('5', '-1', [[.2]]), - ('6', '-1', [[.2]]), - ('7', '-1', [[]]), - ('8', '-1', [[]]), - ('0', '-2', [[]]), - ('1', '-2', [[.0]]), - ('2', '-2', [[.0, .0]]), - ('3', '-2', [[.0, .1]]), - ('4', '-2', [[.1, .1]]), - ('5', '-2', [[.1, .2]]), - ('6', '-2', [[.2, .2]]), - ('7', '-2', [[.2]]), - ('8', '-2', [[]]), - ('0', '-3', [[]]), - ('1', '-3', [[.0]]), - ('2', '-3', [[.0, .0]]), - ('3', '-3', [[.0, .0, .1]]), - ('4', '-3', [[.0, .1, .1]]), - ('5', '-3', [[.1, .1, .2]]), - ('6', '-3', [[.1, .2, .2]]), - ('7', '-3', [[.2, .2]]), - ('8', '-3', [[.2]]), - ('9', '-3', [[]]), - ('0', '-4', [[]]), - ('1', '-4', [[.0]]), - ('2', '-4', [[.0, .0]]), - ('3', '-4', [[.0, .0, .1]]), - ('4', '-4', [[.0, .0, .1, .1]]), - ('5', '-4', [[.0, .1, .1, .2]]), - ('6', '-4', [[.1, .1, .2, .2]]), - ('7', '-4', [[.1, .2, .2]]), - ('8', '-4', [[.2, .2]]), - ('9', '-4', [[.2]]), - ('10', '-4', [[]]), - ('4', '-5', [[.0, .0, .1, .1]]), - ('5', '-5', [[.0, .0, .1, .1, .2]]), - ('6', '-5', [[.0, .1, .1, .2, .2]]), - ('7', '-5', [[.1, .1, .2, .2]]), - ('8', '-5', [[.1, .2, .2]]), - ('9', '-5', [[.2, .2]]), - ('10', '-5', [[.2]]), - ('11', '-5', [[]]), - ('4', '-6', [[.0, .0, .1, .1]]), - ('5', '-6', [[.0, .0, .1, .1, .2]]), - ('6', '-6', [[.0, .0, .1, .1, .2, .2]]), - ('7', '-6', [[.0, .1, .1, .2, .2]]), - ('8', '-6', [[.1, .1, .2, .2]]), - ('9', '-6', [[.1, .2, .2]]), - ('10', '-6', [[.2, .2]]), - ('11', '-6', [[.2]]), - ('12', '-6', [[]]), - ('6', '-6', [[.0, .0, .1, .1, .2, .2]]), - ('6', '-7', [[.0, .0, .1, .1, .2, .2]]), - ('6', '-8', [[.0, .0, .1, .1, .2, .2]]), + (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, [[.2]]), - (-1.0, -0.5, [[.1]]), - (-1.5, -0.5, [[.1]]), - (-2.0, -0.5, [[.0]]), - (-2.5, -0.5, [[.0]]), + (-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, [[.1, .2]]), - (-1.0, -1.0, [[.1, .1]]), - (-1.5, -1.0, [[.0, .1]]), - (-2.0, -1.0, [[.0, .0]]), - (-2.5, -1.0, [[.0]]), + (-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, [[.1, .1, .2]]), - (-1.0, -1.5, [[.0, .1, .1]]), - (-1.5, -1.5, [[.0, .0, .1]]), - (-2.0, -1.5, [[.0, .0]]), - (-2.5, -1.5, [[.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, .1, .1, .2]]), - (-1.0, -2.0, [[.0, .0, .1, .1]]), - (-1.5, -2.0, [[.0, .0, .1]]), - (-2.0, -2.0, [[.0, .0]]), - (-2.5, -2.0, [[.0]]), + (-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, .1, .1, .2]]), - (-1.0, -2.5, [[.0, .0, .1, .1]]), - (-1.5, -2.5, [[.0, .0, .1]]), - (-2.0, -2.5, [[.0, .0]]), - (-2.5, -2.5, [[.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, .1, .1, .2]]), - (-1.0, -3.0, [[.0, .0, .1, .1]]), - (-1.5, -3.0, [[.0, .0, .1]]), - (-2.0, -3.0, [[.0, .0]]), - (-2.5, -3.0, [[.0]]), + (-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, .1, .1, .2]]), - (-1.0, -3.5, [[.0, .0, .1, .1]]), - (-1.5, -3.5, [[.0, .0, .1]]), - (-2.0, -3.5, [[.0, .0]]), - (-2.5, -3.5, [[.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, .1, .1, .2]]), - (-1.0, -4.0, [[.0, .0, .1, .1]]), - (-1.5, -4.0, [[.0, .0, .1]]), - (-2.0, -4.0, [[.0, .0]]), - (-2.5, -4.0, [[.0]]), + (-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', [[.2]]), - ('-2', '-1', [[.1]]), - ('-3', '-1', [[.1]]), - ('-4', '-1', [[.0]]), - ('-5', '-1', [[.0]]), - ('-6', '-1', [[]]), - ('-7', '-1', [[]]), - ('-8', '-1', [[]]), - ('-1', '-2', [[.1, .2]]), - ('-2', '-2', [[.1, .1]]), - ('-3', '-2', [[.0, .1]]), - ('-4', '-2', [[.0, .0]]), - ('-5', '-2', [[.0]]), - ('-6', '-2', [[]]), - ('-7', '-2', [[]]), - ('-8', '-2', [[]]), - ('-1', '-3', [[.1, .1, .2]]), - ('-2', '-3', [[.0, .1, .1]]), - ('-3', '-3', [[.0, .0, .1]]), - ('-4', '-3', [[.0, .0]]), - ('-5', '-3', [[.0]]), - ('-6', '-3', [[]]), - ('-7', '-3', [[]]), - ('-8', '-3', [[]]), - ('-1', '-4', [[.0, .1, .1, .2]]), - ('-2', '-4', [[.0, .0, .1, .1]]), - ('-3', '-4', [[.0, .0, .1]]), - ('-4', '-4', [[.0, .0]]), - ('-5', '-4', [[.0]]), - ('-6', '-4', [[]]), - ('-7', '-4', [[]]), - ('-8', '-4', [[]]), - ('-1', '-5', [[.0, .0, .1, .1, .2]]), - ('-2', '-5', [[.0, .0, .1, .1]]), - ('-3', '-5', [[.0, .0, .1]]), - ('-4', '-5', [[.0, .0]]), - ('-5', '-5', [[.0]]), - ('-6', '-5', [[]]), - ('-7', '-5', [[]]), - ('-8', '-5', [[]]), - ('-1', '-6', [[.0, .0, .1, .1, .2]]), - ('-2', '-6', [[.0, .0, .1, .1]]), - ('-3', '-6', [[.0, .0, .1]]), - ('-4', '-6', [[.0, .0]]), - ('-5', '-6', [[.0]]), - ('-6', '-6', [[]]), - ('-7', '-6', [[]]), - ('-8', '-6', [[]]), - ('-1', '-7', [[.0, .0, .1, .1, .2]]), - ('-2', '-7', [[.0, .0, .1, .1]]), - ('-3', '-7', [[.0, .0, .1]]), - ('-4', '-7', [[.0, .0]]), - ('-5', '-7', [[.0]]), - ('-6', '-7', [[]]), - ('-7', '-7', [[]]), - ('-8', '-7', [[]]), - ('-1', '-8', [[.0, .0, .1, .1, .2]]), - ('-2', '-8', [[.0, .0, .1, .1]]), - ('-3', '-8', [[.0, .0, .1]]), - ('-4', '-8', [[.0, .0]]), - ('-5', '-8', [[.0]]), - ('-6', '-8', [[]]), - ('-7', '-8', [[]]), - ('-8', '-8', [[]]), - ] + ("-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 @@ -1120,7 +1114,6 @@ def test_read_duration_and_offset(audio_file, offset, duration, expected): def test_read_duration_and_offset_file_formats(tmpdir): - # Prepare signals sampling_rate = 44100 channels = 1 @@ -1129,9 +1122,9 @@ def test_read_duration_and_offset_file_formats(tmpdir): 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') + 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) @@ -1146,7 +1139,7 @@ def test_read_duration_and_offset_file_formats(tmpdir): _duration(sig, sampling_rate), af.duration(file) - offset, rtol=0, - atol=tolerance('duration', sampling_rate), + atol=tolerance("duration", sampling_rate), ) # Duration and offset in negative seconds @@ -1159,12 +1152,12 @@ def test_read_duration_and_offset_file_formats(tmpdir): _duration(sig, sampling_rate), -offset, rtol=0, - atol=tolerance('duration', sampling_rate), + atol=tolerance("duration", sampling_rate), ) # Duration and offset in samples - offset = '100' - duration = '200' + offset = "100" + duration = "200" duration_s = float(duration) / sampling_rate offset_s = float(offset) / sampling_rate sig, fs = af.read( @@ -1180,7 +1173,7 @@ def test_read_duration_and_offset_file_formats(tmpdir): _duration(sig, sampling_rate), af.duration(file) - offset_s, rtol=0, - atol=tolerance('duration', sampling_rate), + atol=tolerance("duration", sampling_rate), ) # Duration that results in empty signal @@ -1193,11 +1186,11 @@ def test_read_duration_and_offset_file_formats(tmpdir): @pytest.mark.parametrize( - 'audio_file', + "audio_file", [ ( np.array( - [[.0, .1, .2, .3, .4, .5, .6, .7, .8, .9]], + [[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 @@ -1207,24 +1200,23 @@ def test_read_duration_and_offset_file_formats(tmpdir): ) @pytest.mark.parametrize( # offset and duration need to be given in seconds - 'offset, duration, expected', + "offset, duration, expected", [ - (0.1, 0.1, [.1]), + (0.1, 0.1, [0.1]), (0.1, 0.03, []), - (0.1, 0.15, [.1, .2]), - (0.1, 0.19, [.1, .2]), - (0.049, 0.19, [.0, .1]), - (0.15, 0.15, [.2, .3]), - ] + (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, + tmpdir, + audio_file, + offset, + duration, + expected, ): - # Prepare signals # Ensure that the rounding from duration to samples @@ -1247,7 +1239,7 @@ def test_read_duration_and_offset_rounding( return None # sox - convert_file = str(tmpdir.join('signal-sox.wav')) + convert_file = str(tmpdir.join("signal-sox.wav")) try: af.core.utils.run_sox(audio_file, convert_file, offset, duration) signal, _ = af.read(convert_file) @@ -1261,7 +1253,7 @@ def test_read_duration_and_offset_rounding( pass # ffmpeg - convert_file = str(tmpdir.join('signal-ffmpeg.wav')) + convert_file = str(tmpdir.join("signal-ffmpeg.wav")) af.core.utils.run_ffmpeg(audio_file, convert_file, offset, duration) signal, _ = af.read(convert_file) np.testing.assert_allclose( @@ -1272,13 +1264,12 @@ def test_read_duration_and_offset_rounding( 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) + 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 = ( @@ -1286,22 +1277,19 @@ def test_write_errors(): "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) + 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) + 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." - ) + 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) + write_and_read("test.wav", np.zeros((65536, 100)), sampling_rate)