Repository: claritychallenge/clarity (language: python)

Issue:
85-unit-tests-for-evaluator-module

Adding the pyupgrade precommit hook
We can very cheaply improve the code base by using [pyupgrade](https://github.com/asottile/pyupgrade). This will automatically check and modernise Python code while staying compatible with some given minimum version.   eg. to make code as modern as possible while still supporting Python 3.7 we just need to run.

```
pyupgrade --py37-plus
```

It can be run by pre-commit and is very fast. It makes fixes automatically so there is no real downside.

A quick test is showing that most changes will be removing "r" when opening files in read mode - which is now a default and considered bad style to include. But also we can modernise a lot of type hints. pyupgrade will do this for us if just add 

```
from __future__ import annotations
```



Discussion / hints:

Fix patch:
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index af760092a..20db4902b 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -1,4 +1,28 @@
-# pylint compliance (https://github.com/claritychallenge/clarity/pull/224)
+# pathlib consistency (<https://github.com/claritychallenge/clarity/pull/236>)
+
+c2a4a8b9853a4f459b369b1bfcb5071f59a88cdb # Ignoring pathlib consistency commits from blame history
+3386ea686528fc04ef75b8451371ac1b2aa571a8 # Merge pull request #242 from claritychallenge/main
+90eb7f02d153e8c34db94591d8f2fdfaac60cc02 # Fixes to recipes/e029+e032_sheffield/prepare_data
+5200a5a361f47aaf66bff4378efa5b810b4532db # Fixes for enhancer/gha/gha_interface
+953f8e44a10b04410dd0443c7f3e9a523bf562f1 # Made final fixes for clarity/data/cec1_dataset
+38664e1c50d97bce58242245596bea37a8ccc7db # Made fixes for clarity/data/cec1_dataset
+b815b23158891e7e81193b19883d3b3a8edbeebe # Made fixes for clarity/predictor/torch_msbg
+f1f6075d260850ad2044ca06c7a1692a91a9686f # Made fixes for recipes/cec1/e009_sheffield
+14daec63384e5c0fb9de599256ed9e86c2da81d7 # Made fixes for recipes/cec1/baseline
+72cb8dbfee2bd1656ae71d11e3f228e54cc643d7 # Made fixes for cpc1/e029 and cpc/e032 train
+a68d98c15006e29614f138241245483398a1310e # Made fixes for cpc1/e029 and cpc/e032 infer
+4852633d4af38a3a3c827042968872cae8e251ee # Fixed files in tests/
+f1d7850df68d82cbbc4e3eb53c0c2074eac6084c # Fixed a bug introduced using Path(__file__)
+a5a247c675c948a8769568032ec9af7c274927e8 # Fixed files in recipes/cpc1/baseline
+6e5b53655441bf59c103d88d04d39427addac648 # Mixed files in clarity/data
+c6963d7041a372f193540217eb74890dbfc0d739 # pathlib for clarity/evaluator/msbg/msbg_utils.py
+6b83a96cd4ab5f4122b708a1f254a8792b222bb5 # Converted recipes/cpc1/e029_sheffield/prepare_data.py
+0b942c784d35791d43e1e2d61b984c935754e4a8 # Made fixes for cpc1/e029 and cpc/e032 evaluate
+6055023abadfb5e806b43d92de3ff3de62a50fad # Made fixes for recipes/cec2
+049588082b5566d816388f35a23eb00635c1e858 # Merge pull request #235 from claritychallenge/222-fix-issues-raised-by-pylint
+
+# pylint compliance (<https://github.com/claritychallenge/clarity/pull/224>)
+
 1c4f7def50b63e449c1292ebcb1072a333752dc2 # Ignoring commit that fix pylint errors
 bd5f3068d3d149a2c169bcf2fe1ba42e00f04c22 # Dealt with F6401 false positives, and final, final missed issues.
 1e4d8d6b634cb08d2c75d8b360bef1a87a03f83c # Mopped up a few missed issues
@@ -28,26 +52,36 @@ abd4992d14ed357bde180132f80c3772ba65fe3a # Fixed all C0200 use enumerate issues
 65f263d073f1e6c6d03cafd6e2f2232ecf4cd36b # Fixed all lines over the 88 character limit used in Black
 3ddfdab42496db024610d265120639ac6731c8c6 # Merge pull request #223 from claritychallenge/jpb/220-enable-some-pylint-error-checking-in-run_tests-github-workflow
 
-
 # PEP8 compliance - clarity/evaluators/hasqi
+
 14e21eff76a1350cabaa19ad26f2556892d51761
 961846ca661b5b97f0d1a75585452aed6f15c5f7
 
 # PEP8 compliance - clarity/evaluators/haaqi
+
 55e7f5c81b0e5a2bf27bd21a0a1319dd16ddbd3c
 14f6ae14279b6abccd3da6876c712a1c3f66a0b9
 
 # PEP8 compliance - clarity/evaluators/haspi
+
 41db4281fa6b3ea6758cadbd49c8403caeea3529
 8b1198d7e5c7b1436b15d40e95f5581939030164
 86092355a8777cc191491739e7a28ae7a7a6421a
 3e49c8496f7c44e631ff379961cde509e0868d5d
 
 # PEP8 compliance - clarity/evaluators/msbg
+
 3f7505d79cc6e1fe4cb6d4dc8b783ba845d3eadf
 fa7810ca3dd73a705128bc2e94dbbaaa3dfa2162
 396807bf710ee017d600cc9f6f30557f3337d803
 
 # PEP8 compliance - clarity/evaluators/mbstoi
+
 d0ebc22b2da8482dcfa75c7206fe4f089ad6ddd8
 8f5426e3e854fe818fcc288a4b3cf9bc696914c0
+
+# pyupgrade compliance (<https://github.com/claritychallenge/clarity/issues/244>)
+
+8cd7155c4a08df5aaa2d2baa313be2475004ce79 # Committing fixes made by pyupgrade on complete codebase
+73838d09ca94dd8c13c49ff4f5a60b92544cb480 # Upgrading all files with Tuple annotations
+32a0122f3bcf14dce7400edf761db32cde0727a5 # Upgrading all files that imported typing
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 06e1fcf59..60cd65df4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.4.0  # Use the ref you want to point at
+    rev: v4.4.0 # Use the ref you want to point at
     hooks:
       - id: trailing-whitespace
         types: [file, text]
@@ -18,17 +18,23 @@ repos:
       - id: check-byte-order-marker
       # - id: check-yaml
 
+  - repo: https://github.com/asottile/pyupgrade
+    rev: v3.3.1
+    hooks:
+      - id: pyupgrade
+        args: [--py37-plus]
+
   - repo: https://github.com/psf/black
     rev: 23.1.0
     hooks:
       - id: black
         types: [python]
-        additional_dependencies: ['click==8.0.4']
+        additional_dependencies: ["click==8.0.4"]
 
   - repo: https://github.com/DavidAnson/markdownlint-cli2
     rev: v0.6.0
     hooks:
-    - id: markdownlint-cli2
+      - id: markdownlint-cli2
 
   - repo: https://github.com/pycqa/flake8.git
     rev: 6.0.0
@@ -45,7 +51,7 @@ repos:
         args: ["--profile", "black"]
 
   - repo: https://github.com/nbQA-dev/nbQA
-    rev: 1.6.3
+    rev: 1.6.4
     hooks:
       - id: nbqa-black
       - id: nbqa-flake8
@@ -57,17 +63,16 @@ repos:
     hooks:
       - id: nbstripout
 
-
   - repo: https://github.com/pre-commit/mirrors-mypy
     rev: v1.1.1
     hooks:
       - id: mypy
 
   - repo: https://github.com/charliermarsh/ruff-pre-commit
-  # Ruff version.
-    rev: 'v0.0.245'
+    # Ruff version.
+    rev: "v0.0.257"
     hooks:
-    - id: ruff
+      - id: ruff
 
   # Serious pylint errors that will be enforced by CI
   - repo: https://github.com/pycqa/pylint
@@ -75,8 +80,8 @@ repos:
     hooks:
       - id: pylint
         args:
-        - --errors-only
-        - --disable=E0401
+          - --errors-only
+          - --disable=E0401
 
   # Pylint warnings that are bing skipped by CI (see skip: [pylint_warnings] below)
   - repo: local
@@ -90,7 +95,7 @@ repos:
         exclude: clarity/_version.py # 3rd party code that doesn't conform
 ci:
   autofix_prs: true
-  autofix_commit_msg: '[pre-commit.ci] Fixing issues with pre-commit'
+  autofix_commit_msg: "[pre-commit.ci] Fixing issues with pre-commit"
   autoupdate_schedule: weekly
-  autoupdate_commit_msg: '[pre-commit.ci] pre-commit-autoupdate'
+  autoupdate_commit_msg: "[pre-commit.ci] pre-commit-autoupdate"
   skip: [pylint_warnings] # Optionally list ids of hooks to skip on CI
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4abfdf516..dcfc2048b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,5 +3,10 @@
     "cSpell.words": [
         "haspi",
         "hasqi"
-    ]
+    ],
+    "python.testing.pytestArgs": [
+        "tests"
+    ],
+    "python.testing.unittestEnabled": false,
+    "python.testing.pytestEnabled": true
 }
\ No newline at end of file
diff --git a/clarity/_version.py b/clarity/_version.py
index f59d79b08..0f8ad9db4 100644
--- a/clarity/_version.py
+++ b/clarity/_version.py
@@ -109,7 +109,7 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=
             return None, None
     else:
         if verbose:
-            print("unable to find command, tried %s" % (commands,))
+            print(f"unable to find command, tried {commands}")
         return None, None
     stdout = process.communicate()[0].strip().decode()
     if process.returncode != 0:
@@ -159,7 +159,7 @@ def git_get_keywords(versionfile_abs):
     # _version.py.
     keywords = {}
     try:
-        with open(versionfile_abs, "r", encoding="utf-8") as fobj:
+        with open(versionfile_abs, encoding="utf-8") as fobj:
             for line in fobj:
                 if line.strip().startswith("git_refnames ="):
                     mo = re.search(r'=\s*"(.*)"', line)
@@ -361,7 +361,7 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
             if verbose:
                 fmt = "tag '%s' doesn't start with prefix '%s'"
                 print(fmt % (full_tag, tag_prefix))
-            pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
+            pieces["error"] = "tag '{}' doesn't start with prefix '{}'".format(
                 full_tag,
                 tag_prefix,
             )
diff --git a/clarity/data/HOA_tools_cec2.py b/clarity/data/HOA_tools_cec2.py
index ae565b4c0..9ff0add33 100644
--- a/clarity/data/HOA_tools_cec2.py
+++ b/clarity/data/HOA_tools_cec2.py
@@ -1,6 +1,7 @@
 """Tools to support higher order ambisonic processing."""
+from __future__ import annotations
+
 import logging
-from typing import List
 
 import numpy as np
 from numba import njit
@@ -457,7 +458,7 @@ def compute_rms(input_signal: np.ndarray, axis: int = 0) -> float:
     return np.sqrt(np.mean(input_signal**2, axis=axis))
 
 
-def equalise_rms_levels(inputs: List[np.ndarray]) -> List[np.ndarray]:
+def equalise_rms_levels(inputs: list[np.ndarray]) -> list[np.ndarray]:
     """Equalise RMS levels.
 
     Args:
diff --git a/clarity/data/scene_builder_cec2.py b/clarity/data/scene_builder_cec2.py
index 050bd9d36..ce14e4b4b 100644
--- a/clarity/data/scene_builder_cec2.py
+++ b/clarity/data/scene_builder_cec2.py
@@ -1,4 +1,6 @@
 """Code for building the scenes.json files."""
+from __future__ import annotations
+
 import itertools
 import json
 import logging
@@ -6,7 +8,6 @@
 import random
 import re
 from enum import Enum
-from typing import Dict, List
 
 import numpy as np
 from tqdm import tqdm
@@ -39,7 +40,7 @@ def set_random_seed(random_seed):
         np.random.seed(random_seed)
 
 
-def get_vector(text: str, vector_name: str) -> List[float]:
+def get_vector(text: str, vector_name: str) -> list[float]:
     """Get a vector quantity from the rpf file.
     Will read rpf vector quantities, eg.
     "sourceViewVectors = -0.095,-0.995, 0.000"
@@ -58,7 +59,7 @@ def get_vector(text: str, vector_name: str) -> List[float]:
     return x
 
 
-def get_room_dims(text: str) -> List:
+def get_room_dims(text: str) -> list:
     """Find the room dimensions in the rpf file.
 
     Args:
@@ -87,7 +88,7 @@ def get_room_name(text: str) -> str:
     return re.findall(r"R\d\d\d\d\d", text)[0]
 
 
-def read_rpf_file(rpf_filename: str) -> Dict:
+def read_rpf_file(rpf_filename: str) -> dict:
     """Process an rpf file and return key contents as a dictionary.
 
     Args:
@@ -97,7 +98,7 @@ def read_rpf_file(rpf_filename: str) -> Dict:
         dict: dictionary of rpf file contents
             {"position": sourcePositions, "view_vector": sourceViewVectors}
     """
-    with open(rpf_filename, "r", encoding="utf-8") as f:
+    with open(rpf_filename, encoding="utf-8") as f:
         text = f.read()
 
     rpf_dict = {}
@@ -124,7 +125,7 @@ def read_rpf_file(rpf_filename: str) -> Dict:
     return rpf_dict
 
 
-def build_room(target_file: str, interferer_files: List[str]) -> Dict:
+def build_room(target_file: str, interferer_files: list[str]) -> dict:
     """Build room json file from contents of related rpf files.
     Note, there is an rpf file for each source in the scene. All of these
     files are read and a single scene json file is constructed.
@@ -159,7 +160,7 @@ def build_room(target_file: str, interferer_files: List[str]) -> Dict:
 
 def make_rpf_filename_dict(
     rpf_location: str, scene_str: str, n_interferers: int
-) -> Dict:
+) -> dict:
     """Construct dictionary storing all rpf files that will be processed.
 
     Args:
@@ -236,7 +237,7 @@ def add_this_target_to_scene(
 
 
 # SNR handling
-def generate_snr(snr_range: List[int]) -> float:
+def generate_snr(snr_range: list[int]) -> float:
     """Generate a random Signal Noise Ratio (SNR).
 
     Args:
@@ -257,7 +258,7 @@ class InterfererType(Enum):
     MUSIC = "music"
 
 
-def select_interferer_types(allowed_n_interferers: list) -> List[InterfererType]:
+def select_interferer_types(allowed_n_interferers: list) -> list[InterfererType]:
     """Select the interferer types to use.
 
     The number of interferer is drawn randomly from list of allowed valued.
@@ -283,8 +284,8 @@ def valid(selection):
 
 
 def select_random_interferer(
-    interferers: List[list], dataset: str, required_samples: int
-) -> Dict:
+    interferers: list[list], dataset: str, required_samples: int
+) -> dict:
     """Randomly select an interferer.
     Interferers stored as list of list. First randomly select a sublist
     then randomly select an item from sublist matching constraints.
@@ -345,8 +346,8 @@ def add_interferer_to_scene_inner(
     scene: dict,
     interferers: dict,
     number: list,
-    start_time_range: List[int],
-    end_early_time_range: List[int],
+    start_time_range: list[int],
+    end_early_time_range: list[int],
 ):
     """Randomly select interferers and add them to the given scene.
     A random number of interferers is chosen, then each is given a random type
@@ -425,7 +426,7 @@ def generate_rotation(
     angle_initial_mean: float,
     angle_initial_sd: float,
     angle_final_range: tuple,
-) -> List[dict]:
+) -> list[dict]:
     """Generate a suitable head rotation for the given scene.
     Based on behavioural studies by Hadley et al. TODO: find ref
 
@@ -541,7 +542,7 @@ def load(self, filename) -> None:
         Args:
             filename (str): filename to JSON file from which room data is loaded.
         """
-        with open(filename, "r", encoding="utf-8") as f:
+        with open(filename, encoding="utf-8") as f:
             self.rooms = json.load(f)
         self.rebuild_dict()
 
@@ -683,7 +684,7 @@ def add_target_to_scene(
 
         Raises: TypeError if room_selection is not SEQUENTIAL or RANDOM
         """
-        with open(target_speakers, "r", encoding="utf-8") as f:
+        with open(target_speakers, encoding="utf-8") as f:
             targets = json.load(f)
 
         targets_dataset = [t for t in targets if t["dataset"] == dataset]
@@ -743,7 +744,7 @@ def add_interferer_to_scene(
             None
         """
         # Load and prepare speech interferer metadata
-        with open(speech_interferers, "r", encoding="utf-8") as f:
+        with open(speech_interferers, encoding="utf-8") as f:
             interferers_speech = json.load(f)
         for interferer in interferers_speech:
             interferer["ID"] = (
@@ -753,7 +754,7 @@ def add_interferer_to_scene(
         interferers_speech = [interferers_speech]
 
         # Load and prepare noise (i.e. noise) interferer metadata
-        with open(noise_interferers, "r", encoding="utf-8") as f:
+        with open(noise_interferers, encoding="utf-8") as f:
             interferers_noise = json.load(f)
         for interferer in interferers_noise:
             interferer["ID"] += ".wav"
@@ -763,7 +764,7 @@ def add_interferer_to_scene(
         interferers_noise = list(interferer_by_type.values())
 
         # Load and prepare music interferer metadata
-        with open(music_interferers, "r", encoding="utf-8") as f:
+        with open(music_interferers, encoding="utf-8") as f:
             interferers_music = json.load(f)
         for interferer in interferers_music:
             interferer["ID"] = interferer[
diff --git a/clarity/data/scene_renderer_cec1.py b/clarity/data/scene_renderer_cec1.py
index c3ae323cf..c5ac66a23 100644
--- a/clarity/data/scene_renderer_cec1.py
+++ b/clarity/data/scene_renderer_cec1.py
@@ -1,7 +1,7 @@
 """Scene rendering for CEC1 challenge."""
 import logging
 import math
-import os
+from pathlib import Path
 
 import numpy as np
 import soundfile
@@ -337,7 +337,5 @@ def check_scene_exists(scene: dict, output_path: str, num_channels: int) -> bool
             ]
         )
 
-    scene_exists = True
-    for filename in files_to_check:
-        scene_exists = scene_exists and os.path.exists(filename)
-    return scene_exists
+    # Return True if all files exist, False otherwise
+    return all(Path(filename).exists() for filename in files_to_check)
diff --git a/clarity/data/scene_renderer_cec2.py b/clarity/data/scene_renderer_cec2.py
index 584cbfe16..b97fdc422 100644
--- a/clarity/data/scene_renderer_cec2.py
+++ b/clarity/data/scene_renderer_cec2.py
@@ -103,7 +103,7 @@ def __init__(
         self.hoa_rotator = HOARotator(self.ambisonic_order, resolution=0.1)
 
         # Build dictionary for looking up room data
-        with open(f"{self.metadata.room_definitions}", "r", encoding="utf-8") as f:
+        with open(f"{self.metadata.room_definitions}", encoding="utf-8") as f:
             rooms = json.load(f)
         self.room_dict = {room["name"]: room for room in rooms}
 
@@ -431,7 +431,7 @@ def render_scenes(self, scenes: dict):
         """
         # Make all necessary output directories
         output_path = self.paths.scenes
-        for dataset in set(s["dataset"] for s in scenes):
+        for dataset in {s["dataset"] for s in scenes}:
             full_output_path = Path(output_path.format(dataset=dataset))
             if not full_output_path.exists():
                 full_output_path.mkdir(parents=True, exist_ok=True)
diff --git a/clarity/data/utils.py b/clarity/data/utils.py
index 1c93e88ef..9f851ebec 100644
--- a/clarity/data/utils.py
+++ b/clarity/data/utils.py
@@ -1,23 +1,21 @@
 """Utilities for data generation."""
+from __future__ import annotations
+
 # pylint: disable=wrong-import-position
-import os
 import sys
+from pathlib import Path
 
 if sys.version_info >= (3, 8):
     from typing import Literal
 else:
     from typing_extensions import Literal
 
-from typing import Union
-
 import numpy as np
 import scipy
 import scipy.io
 
 SPEECH_FILTER = scipy.io.loadmat(
-    os.path.join(
-        os.path.dirname(os.path.abspath(__file__)), "params", "speech_weight.mat"
-    ),
+    Path(__file__).parent / "params/speech_weight.mat",
     squeeze_me=True,
 )
 SPEECH_FILTER = np.array(SPEECH_FILTER["filt"])
@@ -73,7 +71,7 @@ def speechweighted_snr(target: np.ndarray, noise: np.ndarray) -> float:
     return sw_snr
 
 
-def sum_signals(signals: list) -> Union[np.ndarray, Literal[0]]:
+def sum_signals(signals: list) -> np.ndarray | Literal[0]:
     """Return sum of a list of signals.
 
     Signals are stored as a list of ndarrays whose size can vary in the first
diff --git a/clarity/dataset/cec1_dataset.py b/clarity/dataset/cec1_dataset.py
index b4a8e190c..d51652d44 100644
--- a/clarity/dataset/cec1_dataset.py
+++ b/clarity/dataset/cec1_dataset.py
@@ -1,5 +1,5 @@
 import json
-import os
+from pathlib import Path
 
 import librosa
 import numpy as np
@@ -37,7 +37,7 @@ def __init__(
         self.testing = testing
 
         self.scene_list = []
-        with open(scenes_file, "r", encoding="utf-8") as fp:
+        with open(scenes_file, encoding="utf-8") as fp:
             scene_json = json.load(fp)
             if not testing:
                 for scene in scene_json:
@@ -93,28 +93,24 @@ def lowpass_filtering(self, x):
         return lfilter(self.lowpass_filter, 1, x)
 
     def __getitem__(self, item):
+        scenes_folder = Path(self.scenes_folder)
         if self.num_channels == 2:
             mixed = read_wavfile(
-                os.path.join(
-                    self.scenes_folder, self.scene_list[item] + self.mixed_suffix
-                )
+                scenes_folder / (self.scene_list[item] + self.mixed_suffix)
             )
         elif self.num_channels == 6:
             mixed = []
             for suffix in self.mixed_suffix:
                 mixed.append(
-                    read_wavfile(
-                        os.path.join(self.scenes_folder, self.scene_list[item] + suffix)
-                    )
+                    read_wavfile(scenes_folder / (self.scene_list[item] + suffix))
                 )
             mixed = np.concatenate(mixed, axis=0)
         else:
             raise NotImplementedError
+        target = None
         if not self.testing:
             target = read_wavfile(
-                os.path.join(
-                    self.scenes_folder, self.scene_list[item] + self.target_suffix
-                )
+                scenes_folder / (self.scene_list[item] + self.target_suffix)
             )
 
         if self.sr != 44100:
@@ -124,7 +120,7 @@ def __getitem__(self, item):
                     librosa.resample(mixed[i], target_sr=44100, orig_sr=self.sr)
                 )
             mixed = np.array(mixed_resampled)
-            if not self.testing:
+            if target is not None:
                 for i in range(target.shape[0]):
                     target_resampled.append(
                         librosa.resample(target[i], target_sr=44100, orig_sr=self.sr)
@@ -137,7 +133,8 @@ def __getitem__(self, item):
         if self.norm:
             mixed_max = np.max(np.abs(mixed))
             mixed = mixed / mixed_max
-            target = target / mixed_max
+            if target is not None:
+                target = target / mixed_max
 
         if not self.testing:
             return_data = (
diff --git a/clarity/enhancer/compressor.py b/clarity/enhancer/compressor.py
index e9371da4e..5ec3f159b 100644
--- a/clarity/enhancer/compressor.py
+++ b/clarity/enhancer/compressor.py
@@ -1,7 +1,7 @@
 """Compressor Class"""
 from __future__ import annotations
 
-from typing import Any, List, Tuple
+from typing import Any
 
 import numpy as np
 
@@ -68,7 +68,7 @@ def set_release(self, t_msec: float) -> None:
         reciprocal_time = 1 / t_sec
         self.release = reciprocal_time / self.fs
 
-    def process(self, signal: np.ndarray) -> Tuple[Any, np.ndarray, List[Any]]:
+    def process(self, signal: np.ndarray) -> tuple[Any, np.ndarray, list[Any]]:
         """DESCRIPTION
 
         Args:
diff --git a/clarity/enhancer/dnn/mc_conv_tasnet.py b/clarity/enhancer/dnn/mc_conv_tasnet.py
index 2bcf71e1a..8a6f40e01 100644
--- a/clarity/enhancer/dnn/mc_conv_tasnet.py
+++ b/clarity/enhancer/dnn/mc_conv_tasnet.py
@@ -1,8 +1,7 @@
-"""
-Adopted from https://github.com/kaituoxu/Conv-TasNet
-"""
+"""Adopted from https://github.com/kaituoxu/Conv-TasNet"""
+from __future__ import annotations
+
 import math
-from typing import Optional
 
 import torch
 import torch.nn.functional as F
@@ -197,7 +196,7 @@ def forward(self, mixture):
 
 
 class Decoder(nn.Module):
-    def __init__(self, N, L, device: Optional[str] = None):
+    def __init__(self, N, L, device: str | None = None):
         super().__init__()
         # device for overlap_and add
         if device is None:
diff --git a/clarity/enhancer/gha/gainrule_camfit.py b/clarity/enhancer/gha/gainrule_camfit.py
index bc2e821c7..b0fd474b5 100644
--- a/clarity/enhancer/gha/gainrule_camfit.py
+++ b/clarity/enhancer/gha/gainrule_camfit.py
@@ -378,7 +378,7 @@ def gainrule_camfit_compr(
             16000,
         ]
     )
-    LTASS_edge_freq = np.zeros((26))
+    LTASS_edge_freq = np.zeros(26)
     LTASS_edge_freq[1:-1] = np.sqrt(LTASS_freq[0:-1] * LTASS_freq[1:])
     LTASS_edge_freq[-1] = 16000 * np.power(2, (1 / 6))
     LTASS_lev = np.array(
diff --git a/clarity/enhancer/gha/gha_interface.py b/clarity/enhancer/gha/gha_interface.py
index ebf534be8..46b4b69c0 100644
--- a/clarity/enhancer/gha/gha_interface.py
+++ b/clarity/enhancer/gha/gha_interface.py
@@ -1,8 +1,11 @@
+from __future__ import annotations
+
 import logging
 import os
 import pathlib
 import subprocess
 import tempfile
+from pathlib import Path
 
 import numpy as np
 import soundfile
@@ -114,10 +117,8 @@ def process_files(self, infile_names, outfile_name, audiogram, listener=None):
         )
         formatted_sGt = format_gaintable(gaintable, noisegate_corr=True)
 
-        cfg_template = os.path.join(
-            os.path.dirname(os.path.abspath(__file__)),
-            "cfg_files",
-            f"{self.cfg_file}_template.cfg",
+        cfg_template = (
+            Path(__file__).parent / f"cfg_files/{self.cfg_file}_template.cfg",
         )
 
         # Merge CH1 and CH3 files. This is the baseline configuration.
@@ -178,7 +179,12 @@ def process_files(self, infile_names, outfile_name, audiogram, listener=None):
         logging.info("OpenMHA processing complete")
 
     def read_signal(
-        self, filename, offset=0, nsamples=-1, nchannels=0, offset_is_samples=False
+        self,
+        filename: str | Path,
+        offset: int = 0,
+        nsamples: int = -1,
+        nchannels: int = 0,
+        offset_is_samples: bool = False,
     ):
         """Read a wavefile and return as numpy array of floats.
 
@@ -212,7 +218,9 @@ def read_signal(
 
         return x
 
-    def write_signal(self, filename, x, floating_point=True):
+    def write_signal(
+        self, filename: str | Path, x, floating_point: bool = True
+    ) -> None:
         """Write a signal as fixed or floating point wav file."""
 
         if floating_point is False:
diff --git a/clarity/enhancer/nalr.py b/clarity/enhancer/nalr.py
index c285c8ebb..bac9c49aa 100644
--- a/clarity/enhancer/nalr.py
+++ b/clarity/enhancer/nalr.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
 import math
-from typing import Optional, Tuple, Union
 
 import numpy as np
 import scipy
@@ -10,8 +11,8 @@ def firwin2(
     n: int,
     f: np.ndarray,
     a: np.ndarray,
-    window: Optional[Union[str, float]] = None,
-    antisymmetric: Optional[bool] = None,  # pylint: disable=unused-argument
+    window: str | float | None = None,
+    antisymmetric: bool | None = None,  # pylint: disable=unused-argument
 ) -> np.ndarray:
     """FIR filter design using the window method.
     Partial implementation of scipy firwin2 but using our own MATLAB-derived fir2.
@@ -27,8 +28,8 @@ def firwin2(
         np.ndarray:  The FIR filter coefficients as a 1-D array of length n.
     """
     window_shape = None
-    window_type: Union[str, float, None]
-    window_param: Union[str, float]
+    window_type: str | float | None
+    window_param: str | float
     if isinstance(window, tuple):
         window_type, window_param = window if window is not None else (None, 0)
     else:
@@ -48,8 +49,8 @@ def firwin2(
 
 
 def fir2(
-    nn: int, ff: np.ndarray, aa: np.ndarray, npt: Optional[int] = None
-) -> Tuple[np.ndarray, int]:
+    nn: int, ff: np.ndarray, aa: np.ndarray, npt: int | None = None
+) -> tuple[np.ndarray, int]:
     """FIR arbitrary shape filter design using the frequency sampling method.
     Translation of MATLAB fir2.
 
@@ -96,7 +97,7 @@ def fir2(
             ne = int(np.fix(ff[i + 1] * npt)) - 1
 
         j = np.arange(nb, ne + 1)
-        inc: Union[float, np.ndarray] = 0.0 if nb == ne else (j - nb) / (ne - nb)
+        inc: float | np.ndarray = 0.0 if nb == ne else (j - nb) / (ne - nb)
         H[nb : (ne + 1)] = (inc * aa[i + 1]) + ((1 - inc) * aa[i])
         nb = ne + 1
 
diff --git a/clarity/evaluator/haspi/eb.py b/clarity/evaluator/haspi/eb.py
index d69a0d32c..3e7571d92 100644
--- a/clarity/evaluator/haspi/eb.py
+++ b/clarity/evaluator/haspi/eb.py
@@ -1347,7 +1347,7 @@ def mel_cepstrum_correlation(reference, distorted, threshold, addnoise):
     k = np.arange(nbands)
     mel_cepstral = np.zeros((nbands, nbasis))
     for n in range(nbasis):
-        basis = np.cos(k * float(freq[n]) * np.pi / float((nbands - 1)))
+        basis = np.cos(k * float(freq[n]) * np.pi / float(nbands - 1))
         mel_cepstral[:, n] = basis / np.linalg.norm(basis)
 
     # Find the segments that lie sufficiently above the quiescent rate
@@ -1419,7 +1419,7 @@ def melcor9(
     threshold,
     add_noise,
     segment_size,
-    n_cepstral_coef=6.0,
+    n_cepstral_coef=6,
 ):
     """
     Compute the cross-correlations between the input signal
@@ -1461,27 +1461,26 @@ def melcor9(
     nbands = reference.shape[0]
 
     # Mel cepstrum basis functions (mel cepstrum because of auditory bands)
-    n_cepstral_coef = 6  # Number of cepstral coefficients to be used
     freq = np.arange(n_cepstral_coef)
     k = np.arange(nbands)
     cepm = np.zeros((nbands, n_cepstral_coef))
     for n in range(n_cepstral_coef):
-        basis = np.cos(k * float(freq[n]) * np.pi / float((nbands - 1)))
+        basis = np.cos(k * float(freq[n]) * np.pi / float(nbands - 1))
         cepm[:, n] = basis / np.linalg.norm(basis)
 
     # Find the segments that lie sufficiently above the quiescent rate
-    reference_linear = 10 ** (
-        reference / 20
-    )  # Convert envelope dB to linear (specific loudness)
-    reference_sum = (
-        np.sum(reference_linear, 0) / nbands
-    )  # Proportional to loudness in sones
-    reference_sum = 20 * np.log10(
-        reference_sum
-    )  # Convert back to dB (loudness in phons)
-    index = np.where(reference_sum > threshold)[
-        0
-    ]  # Identify those segments above threshold
+    # Convert envelope dB to linear (specific loudness)
+    reference_linear = 10 ** (reference / 20)
+
+    # Proportional to loudness in sones
+    reference_sum = np.sum(reference_linear, 0) / nbands
+
+    # Convert back to dB (loudness in phons)
+    reference_sum = 20 * np.log10(reference_sum)
+
+    # Identify those segments above threshold
+    index = np.where(reference_sum > threshold)[0]
+
     segments_above_threshold = index.shape[0]  # Number of segments above threshold
 
     # Modulation filter bands, segment size is 8 msec
@@ -1628,14 +1627,11 @@ def melcor9_crosscovmatrix(b, nmod, nbasis, nsamp, nfir, reference_cep, processe
     processed = np.zeros((nmod, nbasis, nsamp))
     for m in range(nmod):
         for j in range(nbasis):
+            # colve and remove transients
             c = convolve(b[m], reference_cep[j, :], mode="full")
-            reference[m, j, :] = c[
-                int(nfir2) : int(nfir2 + nsamp)
-            ]  # Remove the transients
+            reference[m, j, :] = c[int(nfir2) : int(nfir2 + nsamp)]
             c = convolve(b[m], processed_cep[j, :], mode="full")
-            processed[m, j, :] = c[
-                int(nfir2) : int(nfir2 + nsamp)
-            ]  # Remove the transients
+            processed[m, j, :] = c[int(nfir2) : int(nfir2 + nsamp)]
 
     # Compute the cross-covariance matrix
     cross_covariance_matrix = np.zeros((nmod, nbasis))
@@ -1786,34 +1782,37 @@ def bm_covary(
     lagsize = 1.0  # Lag (+/-) in msec
     maxlag = np.around(lagsize * (0.001 * freq_sample))  # Lag in samples
 
-    # Compute the segment window
-    nwin = int(
-        np.around(segment_size * (0.001 * freq_sample))
-    )  # Segment size in samples
-    test = nwin - 2 * np.floor(nwin / 2)  # 0=even, 1=odd
-    if test > 0:
-        nwin = nwin + 1  # Force window length to be even
+    # Compute the segment size in samples
+    nwin = int(np.around(segment_size * (0.001 * freq_sample)))
 
+    nwin += nwin % 2 == 1  # Force window length to be even
     window = np.hanning(nwin).conj().transpose()  # Raised cosine von Hann window
-    wincorr = correlate(window, window, "full")  # Window autocorrelation, inverted
-    wincorr = wincorr[int(len(window) - 1 - maxlag) : int(maxlag + len(window))]
-    wincorr = 1 / wincorr
-    winsum2 = 1.0 / np.sum(window**2)  # Window power, inverted
+
+    # compute inverted Window autocorrelation
+    win_corr = correlate(window, window, "full")
+    start_sample = int(len(window) - 1 - maxlag)
+    end_sample = int(maxlag + len(window))
+    if start_sample < 0:
+        raise ValueError("segment size too small")
+    win_corr = 1 / win_corr[start_sample:end_sample]
+    win_sum2 = 1.0 / np.sum(window**2)  # Window power, inverted
 
     # The first segment has a half window
     nhalf = int(nwin / 2)
-    halfwindow = window[nhalf:nwin]
-    halfcorr = correlate(halfwindow, halfwindow, "full")
-    halfcorr = halfcorr[
-        int(len(halfwindow) - 1 - maxlag) : int(maxlag + len(halfwindow))
-    ]
-    halfcorr = 1 / halfcorr
-    halfsum2 = 1.0 / np.sum(halfwindow**2)  # MS sum normalization, first segment
+    half_window = window[nhalf:nwin]
+    half_corr = correlate(half_window, half_window, "full")
+    start_sample = int(len(half_window) - 1 - maxlag)
+    end_sample = int(maxlag + len(half_window))
+    if start_sample < 0:
+        raise ValueError("segment size too small")
+    half_corr = 1 / half_corr[start_sample:end_sample]
+    halfsum2 = 1.0 / np.sum(half_window**2)  # MS sum normalization, first segment
 
     # Number of segments
     nchan = reference_basilar_membrane.shape[0]
     npts = reference_basilar_membrane.shape[1]
     nseg = int(1 + np.floor(npts / nwin) + np.floor((npts - nwin / 2) / nwin))
+
     reference_mean_square = np.zeros((nchan, nseg))
     processed_mean_square = np.zeros((nchan, nseg))
     signal_cross_covariance = np.zeros((nchan, nseg))
@@ -1827,25 +1826,25 @@ def bm_covary(
 
         # The first (half) windowed segment
         nstart = 0
-        reference_seg = x[nstart:nhalf] * halfwindow  # Window the reference
-        processed_seg = y[nstart:nhalf] * halfwindow  # Window the processed signal
+        reference_seg = x[nstart:nhalf] * half_window  # Window the reference
+        processed_seg = y[nstart:nhalf] * half_window  # Window the processed signal
         reference_seg = reference_seg - np.mean(reference_seg)  # Make 0-mean
         processed_seg = processed_seg - np.mean(processed_seg)
-        ref_mean_square = (
-            np.sum(reference_seg**2) * halfsum2
-        )  # Normalize signal MS value by the window
+
+        # Normalize signal MS value by the window
+        ref_mean_square = np.sum(reference_seg**2) * halfsum2
+
         proc_mean_squared = np.sum(processed_seg**2) * halfsum2
         correlation = correlate(reference_seg, processed_seg, "full")
         correlation = correlation[
             int(len(reference_seg) - 1 - maxlag) : int(maxlag + len(reference_seg))
         ]
-        unbiased_cross_correlation = np.max(
-            np.abs(correlation * halfcorr)
-        )  # Unbiased cross-correlation
+        unbiased_cross_correlation = np.max(np.abs(correlation * half_corr))
         if (ref_mean_square > small) and (proc_mean_squared > small):
+            # Normalize cross-covariance
             signal_cross_covariance[k, 0] = unbiased_cross_correlation / np.sqrt(
                 ref_mean_square * proc_mean_squared
-            )  # Normalized cross-covariance
+            )
         else:
             signal_cross_covariance[k, 0] = 0.0
 
@@ -1861,28 +1860,25 @@ def bm_covary(
             processed_seg = y[nstart:nstop] * window  # Window the processed signal
             reference_seg = reference_seg - np.mean(reference_seg)  # Make 0-mean
             processed_seg = processed_seg - np.mean(processed_seg)
-            ref_mean_square = (
-                np.sum(reference_seg**2) * winsum2
-            )  # Normalize signal MS value by the window
-            proc_mean_squared = np.sum(processed_seg**2) * winsum2
+
+            # Normalize signal MS value by the window
+            ref_mean_square = np.sum(reference_seg**2) * win_sum2
+            proc_mean_squared = np.sum(processed_seg**2) * win_sum2
             correlation = correlate(reference_seg, processed_seg, "full")
             correlation = correlation[
                 int(len(reference_seg) - 1 - maxlag) : int(maxlag + len(reference_seg))
             ]
-            unbiased_cross_correlation = np.max(
-                np.abs(correlation * wincorr)
-            )  # Unbiased cross-corr
+            unbiased_cross_correlation = np.max(np.abs(correlation * win_corr))
             if (ref_mean_square > small) and (proc_mean_squared > small):
+                # Normalize cross-covariance
                 signal_cross_covariance[k, n] = unbiased_cross_correlation / np.sqrt(
                     ref_mean_square * proc_mean_squared
-                )  # Normalized cross-covariance
+                )
             else:
                 signal_cross_covariance[k, n] = 0.0
 
-            reference_mean_square[k, n] = ref_mean_square  # Save the reference MS level
-            processed_mean_square[
-                k, n
-            ] = proc_mean_squared  # Save the reference MS level
+            reference_mean_square[k, n] = ref_mean_square
+            processed_mean_square[k, n] = proc_mean_squared
 
         # The last (half) windowed segment
         nstart = nstart + nhalf
@@ -1891,9 +1887,8 @@ def bm_covary(
         processed_seg = y[nstart:nstop] * window[0:nhalf]  # Window the processed signal
         reference_seg = reference_seg - np.mean(reference_seg)  # Make 0-mean
         processed_seg = processed_seg - np.mean(processed_seg)
-        ref_mean_square = (
-            np.sum(reference_seg**2) * halfsum2
-        )  # Normalize signal MS value by the window
+        # Normalize signal MS value by the window
+        ref_mean_square = np.sum(reference_seg**2) * halfsum2
         proc_mean_squared = np.sum(processed_seg**2) * halfsum2
 
         correlation = np.correlate(reference_seg, processed_seg, "full")
@@ -1901,13 +1896,12 @@ def bm_covary(
             int(len(reference_seg) - 1 - maxlag) : int(maxlag + len(reference_seg))
         ]
 
-        unbiased_cross_correlation = np.max(
-            np.abs(correlation * halfcorr)
-        )  # Unbiased cross-correlation
+        unbiased_cross_correlation = np.max(np.abs(correlation * half_corr))
         if (ref_mean_square > small) and (proc_mean_squared > small):
+            # Normalized cross-covariance
             signal_cross_covariance[k, nseg - 1] = unbiased_cross_correlation / np.sqrt(
                 ref_mean_square * proc_mean_squared
-            )  # Normalized cross-covariance
+            )
         else:
             signal_cross_covariance[k, nseg - 1] = 0.0
 
@@ -2034,48 +2028,35 @@ def ave_covary2(
     # Compute the time-frequency weights. The weight=1 if a segment in a
     # frequency band is above threshold, and weight=0 if below threshold.
     weight = np.zeros((n_channels, nseg))  # No IHC synchronization roll-off
-    wsync1 = np.zeros(
-        (n_channels, nseg)
-    )  # Loss of IHC synchronization at high frequencies
-    wsync2 = np.zeros((n_channels, nseg))
-    wsync3 = np.zeros((n_channels, nseg))
-    wsync4 = np.zeros((n_channels, nseg))
-    wsync5 = np.zeros((n_channels, nseg))
-    wsync6 = np.zeros((n_channels, nseg))
-    for k in range(n_channels):
-        for n in range(nseg):
-            if (
-                signal_rms[k, n] > threshold_db
-            ):  # Thresh in dB SL for including time-freq tile
-                weight[k, n] = 1
-                wsync1[k, n] = fsync[0, k]
-                wsync2[k, n] = fsync[1, k]
-                wsync3[k, n] = fsync[2, k]
-                wsync4[k, n] = fsync[3, k]
-                wsync5[k, n] = fsync[4, k]
-                wsync6[k, n] = fsync[5, k]
+    weight[signal_rms > threshold_db] = 1
+
+    # The wsync tensor should be constructed as follows:
+    #
+    # wsync = np.zeros((6, n_channels, nseg))
+    # for k in range(n_channels):
+    #    for n in range(nseg):
+    #        # Thresh in dB SL for including time-freq tile
+    #        if signal_rms[k, n] > threshold_db:
+    #            wsync[:, k, n] = fsync[:, k]
+    #
+    # This can be written is an efficient vectorsized form as follows:
+    wsync = np.zeros((6, n_channels, nseg))
+    mask = signal_rms > threshold_db
+    fsync3d = np.repeat(fsync[..., None], nseg, axis=2)
+    wsync[:, mask] = fsync3d[:, mask]
 
     # Sum the weighted covariance values
-    csum = np.sum(
-        np.sum(weight * signal_cross_covariance)
-    )  # Sum of weighted time-freq tiles
+    # Sum of weighted time-freq tiles
+    csum = np.sum(np.sum(weight * signal_cross_covariance))
+
     wsum = np.sum(np.sum(weight))  # Total number of tiles above thresold
-    sum_weighted_time_freq = np.zeros(6)
+
     tiles_above_threshold = np.zeros(6)
+
     # Sum of weighted time-freq tiles
-    sum_weighted_time_freq[0] = np.sum(np.sum(wsync1 * signal_cross_covariance))
-    sum_weighted_time_freq[1] = np.sum(np.sum(wsync2 * signal_cross_covariance))
-    sum_weighted_time_freq[2] = np.sum(np.sum(wsync3 * signal_cross_covariance))
-    sum_weighted_time_freq[3] = np.sum(np.sum(wsync4 * signal_cross_covariance))
-    sum_weighted_time_freq[4] = np.sum(np.sum(wsync5 * signal_cross_covariance))
-    sum_weighted_time_freq[5] = np.sum(np.sum(wsync6 * signal_cross_covariance))
-    # Total number of tiles above thresold
-    tiles_above_threshold[2] = np.sum(np.sum(wsync3))
-    tiles_above_threshold[0] = np.sum(np.sum(wsync1))
-    tiles_above_threshold[1] = np.sum(np.sum(wsync2))
-    tiles_above_threshold[3] = np.sum(np.sum(wsync4))
-    tiles_above_threshold[4] = np.sum(np.sum(wsync5))
-    tiles_above_threshold[5] = np.sum(np.sum(wsync6))
+    sum_weighted_time_freq = np.sum(wsync * signal_cross_covariance, axis=(1, 2))
+
+    tiles_above_threshold = np.sum(wsync, axis=(1, 2))
 
     # Exit if not enough segments above zero
     if wsum < 1:
diff --git a/clarity/evaluator/haspi/ebm.py b/clarity/evaluator/haspi/ebm.py
index d5c19cc96..0556207d2 100644
--- a/clarity/evaluator/haspi/ebm.py
+++ b/clarity/evaluator/haspi/ebm.py
@@ -31,12 +31,11 @@ def env_filter(reference_db, processed_db, filter_cutoff, freq_sub_sample, freq_
         Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Check the filter design parameters
-    assert (
-        freq_sub_sample <= freq_samp
-    ), "Error in ebm.EnvFilt: Subsampling rate too high."
-    assert (
-        filter_cutoff <= 0.5 * freq_sub_sample
-    ), "Error in ebm.EnvFilt: LP cutoff frequency too high."
+    if freq_sub_sample > freq_samp:
+        raise ValueError("ubsampling rate too high.")
+
+    if filter_cutoff > 0.5 * freq_sub_sample:
+        raise ValueError("LP cutoff frequency too high.")
 
     # Check the data matrix orientation
     # Require each frequency band to be a separate column
@@ -96,9 +95,10 @@ def cepstral_correlation_coef(
         nbasis: number of cepstral basis functions to use
 
     Returns:
-        tuple: reference_cep cepstral coefficient matrix for the ref signal
-        (nsamp,nbasis) processed_cep cepstral coefficient matrix for the output signal
-        (nsamp,nbasis) each column is a separate basis function, from low to high
+        tuple: refernce_cep cepstral coefficient matrix for the ref signal
+            (nsamp,nbasis) processed_cep cepstral coefficient matrix for the output
+            signal (nsamp,nbasis) each column is a separate basis function, from low to
+            high
 
     Updates:
         James M. Kates, 23 April 2015.
@@ -128,7 +128,8 @@ def cepstral_correlation_coef(
     nsamp = len(index)  # Number of segments above threshold
 
     # Exit if not enough segments above zero
-    assert nsamp > 1, "Function ebm_CepCoef: Signal below threshold"
+    if nsamp <= 1:
+        raise ValueError("Signal below threshold")
 
     # Remove the silent samples
     reference_db = reference_db[index, :]
diff --git a/clarity/evaluator/haspi/ip.py b/clarity/evaluator/haspi/ip.py
index 1184e9d68..0fc251677 100644
--- a/clarity/evaluator/haspi/ip.py
+++ b/clarity/evaluator/haspi/ip.py
@@ -1,10 +1,10 @@
-"""Functions for ???"""
-from typing import Dict
+"""Functions for HASPI neural network stage."""
+from __future__ import annotations
 
 import numpy as np
 
 
-def get_neural_net():
+def get_neural_net() -> tuple[dict, list[np.ndarray], list[np.ndarray], float]:
     """
     Provide the weights derived for the ensemble of ten neural
     networks used for the HASPI_v2 intelligibility model. The neural networks
@@ -210,7 +210,10 @@ def get_neural_net():
 
 
 def nn_feed_forward_ensemble(
-    data: np.ndarray, neural_net_params: Dict, weights_hidden, weights_out
+    data: np.ndarray,
+    neural_net_params: dict,
+    weights_hidden: list[np.ndarray],
+    weights_out: list[np.ndarray],
 ) -> np.ndarray:
     """
     Function to compute the neural network ensemble response to a set of
@@ -218,7 +221,7 @@ def nn_feed_forward_ensemble(
 
     Args:
     data (np.ndarray): array of features input to the neural network
-    neural_net_params (dict): vector of neural network paramters
+    neural_net_params (dict): vector of neural network parameters
     weights_hidden (list): cell array of hidden layer weights for each network
     weights_out (list): cell array of output layer weights for each network
 
@@ -251,11 +254,14 @@ def nn_feed_forward_ensemble(
 
 
 def nn_feed_forward(
-    data: np.ndarray, neural_net_params: Dict, weights_hidden, weights_out
-):
+    data: np.ndarray,
+    neural_net_params: dict,
+    weights_hidden: np.ndarray,
+    weights_out: np.ndarray,
+) -> tuple[np.ndarray, np.ndarray]:
     """
     Compute the outputs at each layer of a neural network given
-    the input to the network and the weights. The activiation function is an
+    the input to the network and the weights. The activation function is an
     offset logistic function that gives either a logsig or hyperbolic
     tangent; the outputs from each layer have been reduced by the offset. The
     structure of the network is an input layer, one hidden layer, and an
diff --git a/clarity/evaluator/mbstoi/mbstoi.py b/clarity/evaluator/mbstoi/mbstoi.py
index 6b47ce663..b3fc67d3d 100644
--- a/clarity/evaluator/mbstoi/mbstoi.py
+++ b/clarity/evaluator/mbstoi/mbstoi.py
@@ -27,7 +27,7 @@ def mbstoi(
     right_ear_clean: np.ndarray,
     left_ear_noisy: np.ndarray,
     right_ear_noisy: np.ndarray,
-    fs_signal,
+    fs_signal: float,
     gridcoarseness: int = 1,
     sample_rate: int = 10000,
     n_frame: int = 256,
@@ -157,10 +157,10 @@ def mbstoi(
         fft_size_in_samples,
         n_third_octave_bands,
         centre_freq_first_third_octave_hz,
-    )  # (fs, nfft, num_bands, min_freq)
-    centre_frequencies = (
-        2 * math.pi * centre_frequencies
-    )  # This is now the angular frequency in radians per sec
+    )
+
+    # This is now the angular frequency in radians per sec
+    centre_frequencies = 2 * math.pi * centre_frequencies
 
     # Apply short time DFT to signals and transpose
     left_ear_clean_hat = stft(left_ear_clean, n_frame, fft_size_in_samples).transpose()
@@ -292,6 +292,7 @@ def mbstoi(
             right_ear_noisy_n = (
                 right_ear_noisy_seg[n, :] - np.sum(right_ear_noisy_seg[n, :]) / n_frames
             )
+            np.sum(left_ear_clean_n * left_ear_clean_n)
             left_improved[n, m - n_frames] = np.sum(
                 left_ear_clean_n * left_ear_clean_n
             ) / np.sum(left_ear_noisy_n * left_ear_noisy_n)
@@ -311,7 +312,7 @@ def mbstoi(
     idx = np.isfinite(dr_interm)
     dr_interm[~idx] = 0
     p_be_max = np.maximum(left_improved, right_improved)
-    dbe_interm = np.zeros((np.shape(dl_interm)))
+    dbe_interm = np.zeros(np.shape(dl_interm))
 
     idx = left_improved > right_improved
     dbe_interm[idx] = dl_interm[idx]
diff --git a/clarity/evaluator/mbstoi/mbstoi_utils.py b/clarity/evaluator/mbstoi/mbstoi_utils.py
index 53a1fdba1..caeb5866b 100644
--- a/clarity/evaluator/mbstoi/mbstoi_utils.py
+++ b/clarity/evaluator/mbstoi/mbstoi_utils.py
@@ -1,4 +1,6 @@
 """Utilities for MBSTOI processing."""
+from __future__ import annotations
+
 import logging
 
 import numpy as np
@@ -24,7 +26,7 @@ def equalisation_cancellation(
     p_ec_max: np.ndarray,
     sigma_epsilon: np.ndarray,
     sigma_delta: np.ndarray,
-):
+) -> tuple[np.ndarray, np.ndarray]:
     """Run the equalisation-cancellation (EC) stage of the MBSTOI metric.
 
     The EC loop evaluates one huge equation in every iteration (see referenced notes
@@ -63,6 +65,7 @@ def equalisation_cancellation(
             intermediate intelligibility measure
         p_ec_max (np.ndarray) : grid containing maximum values.
     """
+
     taus = np.expand_dims(taus, axis=0)
     sigma_delta = np.expand_dims(sigma_delta, axis=0)
     sigma_epsilon = np.expand_dims(sigma_epsilon, axis=0)
@@ -139,7 +142,7 @@ def equalisation_cancellation(
             # Andersen et al. 2018
 
             # Calculate Exy
-            firstpart = firstpartfunc(
+            firstpart = _firstpartfunc(
                 left_ear_clean,
                 left_ear_noisy,
                 right_ear_clean,
@@ -148,17 +151,17 @@ def equalisation_cancellation(
                 gammas,
                 epsexp,
             )
-            secondpart = secondpartfunc(
+            secondpart = _secondpartfunc(
                 left_ear_clean, left_ear_noisy, rhoy, rhox, tauexp, epsdelexp, gammas
             )
-            thirdpart = thirdpartfunc(
+            thirdpart = _thirdpartfunc(
                 right_ear_clean, right_ear_noisy, rhoy, rhox, tauexp, epsdelexp, gammas
             )
-            fourthpart = fourthpartfunc(rhox, rhoy, tauexp2, n_gammas, deltexp)
+            fourthpart = _fourthpartfunc(rhox, rhoy, tauexp2, n_gammas, deltexp)
             exy = np.real(firstpart - secondpart - thirdpart + fourthpart)
 
             # Calculate Exx
-            firstpart = firstpartfunc(
+            firstpart = _firstpartfunc(
                 left_ear_clean,
                 left_ear_clean,
                 right_ear_clean,
@@ -167,17 +170,17 @@ def equalisation_cancellation(
                 gammas,
                 epsexp,
             )
-            secondpart = secondpartfunc(
+            secondpart = _secondpartfunc(
                 left_ear_clean, left_ear_clean, rhox, rhox, tauexp, epsdelexp, gammas
             )
-            thirdpart = thirdpartfunc(
+            thirdpart = _thirdpartfunc(
                 right_ear_clean, right_ear_clean, rhox, rhox, tauexp, epsdelexp, gammas
             )
-            fourthpart = fourthpartfunc(rhox, rhox, tauexp2, n_gammas, deltexp)
+            fourthpart = _fourthpartfunc(rhox, rhox, tauexp2, n_gammas, deltexp)
             exx = np.real(firstpart - secondpart - thirdpart + fourthpart)
 
             # Calculate Eyy
-            firstpart = firstpartfunc(
+            firstpart = _firstpartfunc(
                 left_ear_noisy,
                 left_ear_noisy,
                 right_ear_noisy,
@@ -186,13 +189,13 @@ def equalisation_cancellation(
                 gammas,
                 epsexp,
             )
-            secondpart = secondpartfunc(
+            secondpart = _secondpartfunc(
                 left_ear_noisy, left_ear_noisy, rhoy, rhoy, tauexp, epsdelexp, gammas
             )
-            thirdpart = thirdpartfunc(
+            thirdpart = _thirdpartfunc(
                 right_ear_noisy, right_ear_noisy, rhoy, rhoy, tauexp, epsdelexp, gammas
             )
-            fourthpart = fourthpartfunc(rhoy, rhoy, tauexp2, n_gammas, deltexp)
+            fourthpart = _fourthpartfunc(rhoy, rhoy, tauexp2, n_gammas, deltexp)
             eyy = np.real(firstpart - secondpart - thirdpart + fourthpart)
 
             # Ensure that intermediate correlation will be sensible and compute it
@@ -217,16 +220,15 @@ def equalisation_cancellation(
 
 
 # pylint: disable=invalid-name,fixme
-def firstpartfunc(
+def _firstpartfunc(
     L1: np.ndarray,
     L2: np.ndarray,
     R1: np.ndarray,
     R2: np.ndarray,
     n_taus: int,
     gammas: np.ndarray,
-    epsexp,
-):
-    # FixMe : Complete Docstring
+    epsexp: np.ndarray | float,
+) -> np.ndarray:
     """Need a description
 
     Args:
@@ -239,25 +241,28 @@ def firstpartfunc(
 
     Returns:
     """
-    result = (
+
+    return (
         np.ones((n_taus, 1))
         * (
-            (
-                10 ** (2 * gammas) * np.sum(L1 * L2)
-                + 10 ** (-2 * gammas) * np.sum(R1 * R2)
-            )
-            * epsexp
+            10.0 ** (2 * gammas) * np.sum(L1 * L2)
+            + 10.0 ** (-2 * gammas) * np.sum(R1 * R2)
         )
+        * epsexp
         + np.sum(L1 * R2)
         + np.sum(R1 * L2)
     )
-    return result
 
 
-def secondpartfunc(
-    L1: np.ndarray, L2: np.ndarray, rho1, rho2, tauexp, epsdelexp, gammas: np.ndarray
-):
-    # FixMe : Complete Docstring
+def _secondpartfunc(
+    L1: np.ndarray,
+    L2: np.ndarray,
+    rho1: np.ndarray,
+    rho2: np.ndarray,
+    tauexp: np.ndarray,
+    epsdelexp: np.ndarray,
+    gammas: np.ndarray,
+) -> np.ndarray:
     """Need a description
 
     Args:
@@ -271,21 +276,26 @@ def secondpartfunc(
 
     Returns:
     """
-    result = (
-        2
-        * (
-            np.transpose(
-                np.dot(L1, np.real(np.transpose(rho1) * tauexp))
-                + np.dot(L2, np.real(np.transpose(rho2) * tauexp))
-            )
-            * 10**gammas
+    return (
+        2.0
+        * np.transpose(
+            np.dot(L1, np.real(np.transpose(rho1) * tauexp))
+            + np.dot(L2, np.real(np.transpose(rho2) * tauexp))
         )
+        * 10.0**gammas
         * epsdelexp
     )
-    return result
 
 
-def thirdpartfunc(R1, R2, rho1, rho2, tauexp, epsdelexp, gammas: np.ndarray):
+def _thirdpartfunc(
+    R1: np.ndarray,
+    R2: np.ndarray,
+    rho1: np.ndarray,
+    rho2: np.ndarray,
+    tauexp: np.ndarray,
+    epsdelexp: np.ndarray,
+    gammas: np.ndarray,
+) -> np.ndarray:
     # FixMe : Complete docstring
     """Need a description
 
@@ -300,19 +310,24 @@ def thirdpartfunc(R1, R2, rho1, rho2, tauexp, epsdelexp, gammas: np.ndarray):
 
     Returns:
     """
-    result = (
-        2
+    return (
+        2.0
         * np.transpose(
             np.dot(R1, np.real(np.dot(np.transpose(rho1), tauexp)))
             + np.dot(R2, np.real(np.transpose(rho2) * tauexp))
         )
-        * 10**-gammas
+        * 10.0**-gammas
         * epsdelexp
     )
-    return result
 
 
-def fourthpartfunc(rho1, rho2, tauexp2, n_gammas, deltexp):
+def _fourthpartfunc(
+    rho1: np.ndarray,
+    rho2: np.ndarray,
+    tauexp2: np.ndarray,
+    n_gammas: int,
+    deltexp: np.ndarray,
+) -> np.ndarray:
     # FixMe : Complete docstring
     """Need a description
 
@@ -325,8 +340,8 @@ def fourthpartfunc(rho1, rho2, tauexp2, n_gammas, deltexp):
 
     Returns
     """
-    result = (
-        2
+    return (
+        2.0
         * np.transpose(
             np.real(np.dot(rho1, np.conj(np.transpose(rho2))))
             + deltexp * np.real(np.dot(rho1, np.transpose(rho2) * tauexp2))
@@ -334,13 +349,15 @@ def fourthpartfunc(rho1, rho2, tauexp2, n_gammas, deltexp):
         * np.ones((1, n_gammas))
     )
 
-    return result
-
 
 # pylint: enable=invalid-name,fixme
 
 
-def stft(signal: np.ndarray, win_size: int, fft_size: int) -> np.ndarray:
+def stft(
+    signal: np.ndarray,
+    win_size: int,
+    fft_size: int,
+) -> np.ndarray:
     """Short-time Fourier transform based on MBSTOI Matlab code.
 
     Args:
@@ -372,10 +389,10 @@ def remove_silent_frames(
     right_ear_clean: np.ndarray,
     left_ear_noisy: np.ndarray,
     right_ear_noisy: np.ndarray,
-    dynamic_range: np.ndarray = 40,
+    dynamic_range: int = 40,
     frame_length: int = 256,
-    hop: float = 128,
-):
+    hop: int | float = 128,
+) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
     """Remove silent frames of x and y based on x
 
     A frame is excluded if its energy is lower than max(energy) - dyn_range
@@ -398,8 +415,8 @@ def remove_silent_frames(
         yl_sil (np.ndarray): left_ear_noisy without the silent frames in xl_sil.
         yr_sil (np.ndarray): right_ear_noisy without the silent frames in rl_sil.
     """
-    dyn_range = int(dynamic_range)
-    hop = int(hop)
+    dyn_range = int(dynamic_range)  # TODO: remove this useless line
+    hop = int(hop)  # TODO: enforce hop to be int as parameter, callers have to cast
 
     # Compute Mask
     hanning_window = np.hanning(frame_length + 2)[1:-1]
@@ -462,7 +479,12 @@ def remove_silent_frames(
     return xl_sil, xr_sil, yl_sil, yr_sil
 
 
-def thirdoct(frequency_sampling: int, nfft: int, num_bands: int, min_freq: int):
+def thirdoct(
+    frequency_sampling: int,
+    nfft: int,
+    num_bands: int,
+    min_freq: int,
+) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
     """Returns the 1/3 octave band matrix and its center frequencies
     based on mpariente/pystoi.
 
@@ -513,7 +535,10 @@ def thirdoct(frequency_sampling: int, nfft: int, num_bands: int, min_freq: int):
     )
 
 
-def find_delay_impulse(ddf: np.ndarray, initial_value: int = 22050):
+def find_delay_impulse(
+    ddf: np.ndarray,
+    initial_value: int = 22050,
+) -> np.ndarray:
     """Find binaural delay in signal ddf.
 
     Finds delay given initial location of unit impulse, initial_value.
diff --git a/clarity/evaluator/msbg/cochlea.py b/clarity/evaluator/msbg/cochlea.py
index b69f85b64..850b3b18f 100644
--- a/clarity/evaluator/msbg/cochlea.py
+++ b/clarity/evaluator/msbg/cochlea.py
@@ -1,8 +1,9 @@
 """Gammatone filterbank simulation of the Cochlea."""
+from __future__ import annotations
 
 import logging
 from dataclasses import dataclass
-from typing import Dict, Tuple, Union
+from typing import Any
 
 import numpy as np
 import scipy
@@ -24,10 +25,10 @@ class FilterBank:
 
 
 # Parameters for smearing and gammatone filtering according to degree of loss
-HL_PARAMS: Dict[str, Dict[str, Union[str, tuple]]] = {
+HL_PARAMS: dict[str, dict[str, Any]] = {
     "SEVERE": {
         "gtfbank_file": "GT4FBank_Brd3.0E_Spaced2.3E_44100Fs",
-        "smear_params": (4, 2),  # asymmetric severe smearing
+        "smear_params": (4.0, 2.0),  # asymmetric severe smearing
     },
     "MODERATE": {
         "gtfbank_file": "GT4FBank_Brd2.0E_Spaced1.5E_44100Fs",
@@ -46,7 +47,7 @@ class FilterBank:
 
 def compute_recruitment_parameters(
     gtn_cf: np.ndarray, audiogram: Audiogram, catch_up: float
-) -> Tuple[np.ndarray, np.ndarray]:
+) -> tuple[np.ndarray, np.ndarray]:
     """Compute parameters to be used in recruitment model.
 
     Computes expansion ratios for each gammatone filterbank channel and
@@ -251,8 +252,8 @@ def __init__(
         # Set-up the smearer
         self.smearer = None
         if severity_level != "NOTHING":
-            smear_params = HL_PARAMS[severity_level]["smear_params"]
-            self.smearer = Smearer(smear_params[0], smear_params[1], fs)
+            r_lower, r_upper = HL_PARAMS[severity_level]["smear_params"]
+            self.smearer = Smearer(r_lower, r_upper, fs)
 
         logging.info("Severity level - %s", severity_level)
 
diff --git a/clarity/evaluator/msbg/msbg_utils.py b/clarity/evaluator/msbg/msbg_utils.py
index bede043ec..0bb4fb100 100644
--- a/clarity/evaluator/msbg/msbg_utils.py
+++ b/clarity/evaluator/msbg/msbg_utils.py
@@ -1,9 +1,10 @@
 """Support for the MSBG hearing loss model."""
+from __future__ import annotations
+
 import json
 import logging
 import math
-import os
-from typing import Dict, Optional, Tuple, Union
+from pathlib import Path
 
 import numpy as np
 import scipy
@@ -95,7 +96,7 @@
 ) * (7.5 / 9)
 
 
-def read_gtf_file(gtf_file: str) -> Dict:
+def read_gtf_file(gtf_file: str) -> dict:
     """Read a gammatone filterbank file.
 
     List data is converted into numpy arrays.
@@ -103,9 +104,8 @@ def read_gtf_file(gtf_file: str) -> Dict:
     """
 
     # Fix filename if necessary
-    dirname = os.path.dirname(os.path.abspath(__file__))
-    gtf_file = os.path.join(dirname, gtf_file)
-    with open(gtf_file, "r", encoding="utf-8") as fp:
+    gtf_file_path = Path(__file__).parent / gtf_file
+    with gtf_file_path.open("r", encoding="utf-8") as fp:
         data = json.load(fp)
     for key in data:
         if isinstance(data[key], list):
@@ -117,8 +117,8 @@ def firwin2(
     n_taps: int,
     frequencies: np.ndarray,
     filter_gains: np.ndarray,
-    window: Union[None, str, tuple] = None,
-    antisymmetric: Optional[bool] = None,
+    window: None | str | tuple = None,
+    antisymmetric: bool | None = None,
 ):  # pylint: disable=W0613
     """FIR filter design using the window method.
 
@@ -161,8 +161,8 @@ def fir2(
     filter_length: int,
     frequencies: np.ndarray,
     filter_gains: np.ndarray,
-    n_interpolate: Optional[int] = None,
-) -> Tuple[np.ndarray, int]:
+    n_interpolate: int | None = None,
+) -> tuple[np.ndarray, int]:
     """FIR arbitrary shape filter design using the frequency sampling method.
 
     Translation of MATLAB fir2.
@@ -215,7 +215,7 @@ def fir2(
             ne = int(np.fix(frequencies[i + 1] * n_interpolate)) - 1
 
         j = np.arange(nb, ne + 1)
-        inc: Union[float, np.ndarray] = 0.0 if nb == ne else (j - nb) / (ne - nb)
+        inc: float | np.ndarray = 0.0 if nb == ne else (j - nb) / (ne - nb)
         H[nb : (ne + 1)] = inc * filter_gains[i + 1] + (1 - inc) * filter_gains[i]
         nb = ne + 1
 
@@ -262,8 +262,8 @@ def gen_tone(
 def gen_eh2008_speech_noise(
     duration: float,
     sample_frequency: float = 44100.0,
-    level: Optional[float] = None,
-    supplied_b: Optional[np.ndarray] = None,
+    level: float | None = None,
+    supplied_b: np.ndarray | None = None,
 ) -> np.ndarray:
     """Generate speech shaped noise.
 
@@ -341,8 +341,8 @@ def generate_key_percent(
     signal: np.ndarray,
     threshold_db: float,
     window_length: int,
-    percent_to_track: Optional[float] = None,
-) -> Tuple[np.ndarray, float]:
+    percent_to_track: float | None = None,
+) -> tuple[np.ndarray, float]:
     """Generate key percent.
     Locates frames above some energy threshold or tracks a certain percentage
     of frames. To track a certain percentage of frames in order to get measure
@@ -449,7 +449,7 @@ def measure_rms(
     signal: np.ndarray,
     sample_frequency: float,
     db_rel_rms: float,
-    percent_to_track: Optional[float] = None,
+    percent_to_track: float | None = None,
 ) -> tuple:
     """Measure Root Mean Square.
 
@@ -508,7 +508,7 @@ def pad(signal, length):
 
 
 def read_signal(
-    filename: str,
+    filename: str | Path,
     offset: int = 0,
     nsamples: int = -1,
     nchannels: int = 0,
@@ -517,7 +517,7 @@ def read_signal(
     """Read a wavefile and return as numpy array of floats.
 
     Args:
-        filename (string): Name of file to read
+        filename (str|Path): Name of file to read
         offset (int, optional): Offset in samples or seconds (from start). Default is 0.
         nsamples (int): Number of samples.
         nchannels (int): expected number of channel (default: 0 = any number OK)
@@ -551,7 +551,7 @@ def read_signal(
 
 
 def write_signal(
-    filename: str,
+    filename: str | Path,
     signal: np.ndarray,
     sample_frequency: float,
     floating_point: bool = True,
@@ -559,7 +559,7 @@ def write_signal(
     """Write a signal as fixed or floating point wav file.
 
     Args:
-        filename (str): name of file in to write to.
+        filename (str|Path): name of file in to write to.
         signal (ndarray): signal to write.
         sample_frequency (float): sampling frequency.
         floating_point (bool): write as floating point else an ints (default: True).
diff --git a/clarity/evaluator/msbg/smearing.py b/clarity/evaluator/msbg/smearing.py
index a55cedb59..6faa54d1b 100644
--- a/clarity/evaluator/msbg/smearing.py
+++ b/clarity/evaluator/msbg/smearing.py
@@ -1,4 +1,6 @@
 """Temporal smearing component of MSBG model."""
+from __future__ import annotations
+
 import math
 
 import numpy as np
@@ -8,7 +10,12 @@
 SHIFT = 64
 
 
-def audfilt(rl, ru, sampfreq, asize=256):
+def audfilt(
+    rl: int | float,
+    ru: int | float,
+    sampfreq: int | float,
+    asize: int = 256,
+) -> np.ndarray:
     """Calculate an auditory filter array.
 
     Args:
@@ -45,7 +52,7 @@ def audfilt(rl, ru, sampfreq, asize=256):
     return aud_filter
 
 
-def make_smear_mat3(rl, ru, fs):
+def make_smear_mat3(rl: float, ru: float, fs: int | float) -> np.darray:
     """Make the smearing filter matrix.
 
     Args:
@@ -83,7 +90,7 @@ def make_smear_mat3(rl, ru, fs):
     return f_smear
 
 
-def smear3(f_smear, inbuffer):
+def smear3(f_smear: np.ndarray, inbuffer: np.ndarray) -> np.ndarray:
     """Direct translation of smear3.m from MSBG hearing loss model.
 
     Args:
@@ -155,12 +162,7 @@ def smear3(f_smear, inbuffer):
 class Smearer:
     """Class to hold the re-usable smearing filter."""
 
-    rl: np.ndarray
-    ru: np.ndarray
-    fs: np.ndarray
-    f_smear: np.ndarray
-
-    def __init__(self, rl, ru, fs):
+    def __init__(self, rl: float, ru: float, fs: int | float) -> None:
         self.rl = rl
         self.ru = ru
         self.fs = fs
diff --git a/clarity/predictor/torch_msbg.py b/clarity/predictor/torch_msbg.py
index 578e02d82..aaa136c44 100644
--- a/clarity/predictor/torch_msbg.py
+++ b/clarity/predictor/torch_msbg.py
@@ -2,7 +2,7 @@
 An FIR-based torch implementation of approximated MSBG hearing loss model
 """
 import json
-import os
+from pathlib import Path
 
 import numpy as np
 import torch
@@ -26,57 +26,57 @@
 # old msbg matlab
 # set RMS so that peak of output file so that no clipping occurs, set so that
 # equiv0dBfileSPL > 100dB for LOUD input files
-ref_RMSdB = -31.2
+REF_RMS_DB = -31.2
 
 # what RMS of INPUT speech file translates to in real world (unweighted)
-calib_dBSPL = 65
+CALIB_DB_SPL = 65
 
 # what 0dB file signal would translate to in dB SPL:
 # constant for cochlear_simulate function
-equiv0dBfileSPL = calib_dBSPL - ref_RMSdB
+EQUIV_0_DB_FILE_SPL = CALIB_DB_SPL - REF_RMS_DB
 
 # clarity msbg
-equiv0dBSPL = 100
-ahr = 20
-equiv_0dB_SPL = equiv0dBSPL + ahr
+EQUIV_0_DB_SPL = 100
+AHR = 20
+EQUIV_0_DB_SPL = EQUIV_0_DB_SPL + AHR
 
 
-def generate_key_percent(signal, thr_dB, winlen):
-    if winlen != np.floor(winlen):
-        winlen = np.int(np.floor(winlen))
+def generate_key_percent(signal, thr_db, win_len):
+    if win_len != np.floor(win_len):
+        win_len = np.int(np.floor(win_len))
         print("\nGenerate_key_percent: \t Window length must be integer")
 
-    siglen = len(signal)
-    expected = thr_dB.copy()  # expected threshold
+    signal_len = len(signal)
+    expected = thr_db.copy()  # expected threshold
     non_zero = 10.0 ** ((expected - 30) / 10)  # put floor into histogram distribution
 
-    nframes = 0
-    totframes = np.int(np.floor(siglen / winlen))
-    every_dB = np.zeros(totframes)
+    n_frames = 0
+    total_frames = np.int(np.floor(signal_len / win_len))
+    every_db = np.zeros(total_frames)
 
-    for ix in range(totframes):
-        start = ix * winlen
-        this_sum = np.sum(signal[start : start + winlen] ** 2)  # sum of squares
-        every_dB[nframes] = 10 * np.log10(non_zero + this_sum / winlen)
-        nframes += 1
+    for ix in range(total_frames):
+        start = ix * win_len
+        this_sum = np.sum(signal[start : start + win_len] ** 2)  # sum of squares
+        every_db[n_frames] = 10 * np.log10(non_zero + this_sum / win_len)
+        n_frames += 1
 
-    used_thr_dB = expected.copy()
+    used_thr_db = expected.copy()
 
     # histogram should produce a two-peaked curve: thresh should be set in valley
     # between the two peaks, and set a bit above that, as it heads for main peak
-    frame_idx = np.where(every_dB >= expected)[0]
+    frame_idx = np.where(every_db >= expected)[0]
     valid_frames = len(frame_idx)
-    key = np.zeros(valid_frames * winlen, dtype=np.int)
+    key = np.zeros(valid_frames * win_len, dtype=np.int)
 
     # convert frame numbers into indices for signal
     for ix in range(valid_frames):
-        key[ix * winlen : ix * winlen + winlen] = np.arange(
-            frame_idx[ix] * winlen, frame_idx[ix] * winlen + winlen, dtype=np.int
+        key[ix * win_len : ix * win_len + win_len] = np.arange(
+            frame_idx[ix] * win_len, frame_idx[ix] * win_len + win_len, dtype=np.int
         )
-    return key, used_thr_dB
+    return key, used_thr_db
 
 
-def measure_rms(signal, sr, dB_rel_rms):
+def measure_rms(signal, sr, db_rel_rms):
     """Compute RMS level of a signal.
 
     Measures total power of all 10 msec frames that are above a user-specified threshold
@@ -84,7 +84,7 @@ def measure_rms(signal, sr, dB_rel_rms):
     Args:
         signal: input signal
         sr: sampling rate
-        dB_rel_rms: threshold relative to first-stage rms (if it is made of a 2*1 array,
+        db_rel_rms: threshold relative to first-stage rms (if it is made of a 2*1 array,
             second value over rules. only single value supported currently)
 
     Returns:
@@ -95,35 +95,35 @@ def measure_rms(signal, sr, dB_rel_rms):
     # first RMS is of all signal
     first_stage_rms = np.sqrt(np.mean(signal**2))
     # use this RMS to generate key threshold to more accurate RMS
-    key_thr_dB = np.max([20 * np.log10(first_stage_rms) + dB_rel_rms, -80])
-    key, used_thr_dB = generate_key_percent(
-        signal, key_thr_dB, np.int(np.round(win_secs * sr))
+    key_thr_db = np.max([20 * np.log10(first_stage_rms) + db_rel_rms, -80])
+    key, used_thr_db = generate_key_percent(
+        signal, key_thr_db, np.int(np.round(win_secs * sr))
     )
     # active = 100.0 * len(key) / len(signal)
     rms = np.sqrt(np.mean(signal[key] ** 2))
-    rel_dB_thresh = used_thr_dB - 20 * np.log10(rms)
-    return rms, key, rel_dB_thresh
+    rel_db_thresh = used_thr_db - 20 * np.log10(rms)
+    return rms, key, rel_db_thresh
 
 
 def makesmearmat3(rl, ru, sr):
-    fftsize = 512
-    nyquist = np.int(fftsize // 2)
-    fnor = audfilt(1, 1, nyquist, sr)
-    fwid = audfilt(rl, ru, nyquist, sr)
-    fnext = np.hstack([fnor, np.zeros([nyquist, nyquist // 2])])
+    fft_size = 512
+    nyquist = np.int(fft_size // 2)
+    f_nor = audfilt(1, 1, nyquist, sr)
+    f_wid = audfilt(rl, ru, nyquist, sr)
+    f_next = np.hstack([f_nor, np.zeros([nyquist, nyquist // 2])])
 
     for i in np.arange(nyquist // 2 + 1, nyquist + 1, dtype=np.int):
-        fnext[i - 1, nyquist : np.min([2 * i - 1, 3 * nyquist // 2])] = np.flip(
-            fnor[
+        f_next[i - 1, nyquist : np.min([2 * i - 1, 3 * nyquist // 2])] = np.flip(
+            f_nor[
                 i - 1, np.max([1, 2 * i - 3 * nyquist // 2]) - 1 : (2 * i - nyquist - 1)
             ]
         )
-    fsmear = np.linalg.lstsq(fnext, fwid)[
+    f_smear = np.linalg.lstsq(f_next, f_wid)[
         0
     ]  # https://stackoverflow.com/questions/33559946/numpy-vs-mldivide-matlab-operator
-    fsmear = fsmear[:nyquist, :]
+    f_smear = f_smear[:nyquist, :]
 
-    return fsmear
+    return f_smear
 
 
 def audfilt(rl, ru, size, sr):
@@ -144,17 +144,17 @@ def audfilt(rl, ru, size, sr):
 
     g = np.zeros(size)
     for i in np.arange(2, size + 1, 1, dtype=np.int):
-        fhz = (i - 1) * sr / (2 * size)
-        erbhz = 24.7 * ((fhz * 0.00437) + 1)
-        pl = 4 * fhz / (erbhz * rl)
-        pu = 4 * fhz / (erbhz * ru)
+        f_hz = (i - 1) * sr / (2 * size)
+        erb_hz = 24.7 * ((f_hz * 0.00437) + 1)
+        pl = 4 * f_hz / (erb_hz * rl)
+        pu = 4 * f_hz / (erb_hz * ru)
         j = np.arange(1, i, dtype=np.int)
         g[j - 1] = np.abs((i - j) / (i - 1))
         aud_filter[i - 1, j - 1] = (1 + (pl * g[j - 1])) * np.exp(-pl * g[j - 1])
         j = np.arange(i, size + 1, dtype=np.int)
         g[j - 1] = np.abs((i - j) / (i - 1))
         aud_filter[i - 1, j - 1] = (1 + (pu * g[j - 1])) * np.exp(-pu * g[j - 1])
-        aud_filter[i - 1, :] = aud_filter[i - 1, :] / (erbhz * (rl + ru) / (2 * 24.7))
+        aud_filter[i - 1, :] = aud_filter[i - 1, :] / (erb_hz * (rl + ru) / (2 * 24.7))
     return aud_filter
 
 
@@ -165,14 +165,14 @@ def __init__(
         audiometric,
         sr=44100,
         spl_cali=True,
-        src_posn="ff",
+        src_position="ff",
         kernel_size=1025,
         device=None,
     ):
         super().__init__()
         self.sr = sr
         self.spl_cali = spl_cali
-        self.src_posn = src_posn
+        self.src_position = src_position
         self.kernel_size = kernel_size
         if device is None:
             self.device = "cuda" if torch.cuda.is_available() else "cpu"
@@ -193,16 +193,16 @@ def __init__(
             np.array([0.125, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 6, 8, 10, 12, 14, 16])
             * 1000
         )
-        interpf = interp1d(audiometric, audiogram)
-        audiogram = interpf(audiogram_cfs)
+        interp_f = interp1d(audiometric, audiogram)
+        audiogram = interp_f(audiogram_cfs)
 
         # settings for src_to_cochlea_filt
 
-        if src_posn == "ff":
+        if src_position == "ff":
             src_corrn = FF_ED
-        elif src_posn == "df":
+        elif src_position == "df":
             src_corrn = DF_ED
-        elif src_posn == "ITU":
+        elif src_position == "ITU":
             interf_itu = interp1d(ITU_HZ, ITU_ERP_DRP)
             src_corrn = interf_itu(HZ)
         nyquist = sr / 2
@@ -215,20 +215,24 @@ def __init__(
         corrn_forward = 10 ** (0.05 * corrn_used)
         corrn_backward = 10 ** (0.05 * -1 * corrn_used)
         n_wdw = np.int(2 * np.floor((sr / 16e3) * 368 / 2))
-        coch_filter_forward = firwin2(
+        cochlear_filter_forward = firwin2(
             n_wdw + 1, hz_used / nyquist, corrn_forward, window=("kaiser", 4)
         )
-        coch_filter_backward = firwin2(
+        cochlear_filter_backward = firwin2(
             n_wdw + 1, hz_used / nyquist, corrn_backward, window=("kaiser", 4)
         )
-        self.coch_padding = len(coch_filter_forward) // 2
-        self.coch_filter_forward = (
-            torch.tensor(coch_filter_forward, dtype=torch.float32, device=self.device)
+        self.cochlear_padding = len(cochlear_filter_forward) // 2
+        self.cochlear_filter_forward = (
+            torch.tensor(
+                cochlear_filter_forward, dtype=torch.float32, device=self.device
+            )
             .unsqueeze(0)
             .unsqueeze(1)
         )
-        self.coch_filter_backward = (
-            torch.tensor(coch_filter_backward, dtype=torch.float32, device=self.device)
+        self.cochlear_filter_backward = (
+            torch.tensor(
+                cochlear_filter_backward, dtype=torch.float32, device=self.device
+            )
             .unsqueeze(0)
             .unsqueeze(1)
         )
@@ -248,44 +252,36 @@ def __init__(
         # impairment degree affects smearing simulation, and now recruitment,
         # (assuming we do not have too much SEVERE losses present)
 
-        current_dir = os.path.dirname(os.path.abspath(__file__))
-        gtf_dir = os.path.join(current_dir, "../evaluator/msbg/msbg_hparams")
+        current_dir = Path(__file__).parent
+        gtf_dir = current_dir / "../evaluator/msbg/msbg_hparams"
         if impaired_degree > 56:
             severe_not_moderate = 1
-            GT4Bankfile = os.path.join(
-                gtf_dir, "GT4FBank_Brd3.0E_Spaced2.3E_44100Fs.json"
-            )
+            gt4_bank_file = gtf_dir / "GT4FBank_Brd3.0E_Spaced2.3E_44100Fs.json"
             bw_broaden_coef = 3
         elif impaired_degree > 35:
             severe_not_moderate = 0
-            GT4Bankfile = os.path.join(
-                gtf_dir, "GT4FBank_Brd2.0E_Spaced1.5E_44100Fs.json"
-            )
+            gt4_bank_file = gtf_dir / "GT4FBank_Brd2.0E_Spaced1.5E_44100Fs.json"
             bw_broaden_coef = 2
         elif impaired_degree > 15:
             severe_not_moderate = -1
-            GT4Bankfile = os.path.join(
-                gtf_dir, "GT4FBank_Brd1.5E_Spaced1.1E_44100Fs.json"
-            )
+            gt4_bank_file = gtf_dir / "GT4FBank_Brd1.5E_Spaced1.1E_44100Fs.json"
             bw_broaden_coef = 1
         else:
             severe_not_moderate = -2
-            GT4Bankfile = os.path.join(
-                gtf_dir, "GT4FBank_Brd1.5E_Spaced1.1E_44100Fs.json"
-            )
+            gt4_bank_file = gtf_dir / "GT4FBank_Brd1.5E_Spaced1.1E_44100Fs.json"
             bw_broaden_coef = 1
-        # GT4Bank = loadmat(GT4Bankfile)
-        with open(GT4Bankfile, "r", encoding="utf-8") as fp:
-            GT4Bank = json.load(fp)
+        # gt4_bank = loadmat(gt4_bank_file)
+        with gt4_bank_file.open("r", encoding="utf-8") as fp:
+            gt4_bank = json.load(fp)
 
         if severe_not_moderate > 0:
-            fsmear = makesmearmat3(4, 2, sr)
+            f_smear = makesmearmat3(4, 2, sr)
         elif severe_not_moderate == 0:
-            fsmear = makesmearmat3(2.4, 1.6, sr)
+            f_smear = makesmearmat3(2.4, 1.6, sr)
         elif severe_not_moderate == -1:
-            fsmear = makesmearmat3(1.6, 1.1, sr)
+            f_smear = makesmearmat3(1.6, 1.1, sr)
         elif severe_not_moderate == -2:
-            fsmear = makesmearmat3(1.001, 1.001, sr)
+            f_smear = makesmearmat3(1.001, 1.001, sr)
 
         self.smear_nfft = 512
         self.smear_win_len = 256
@@ -303,83 +299,85 @@ def __init__(
         self.smear_window = torch.tensor(
             smear_window, dtype=torch.float32, device=self.device
         )
-        self.fsmear = torch.tensor(fsmear, dtype=torch.float32, device=self.device)
+        self.f_smear = torch.tensor(f_smear, dtype=torch.float32, device=self.device)
 
         """ settings for recruitment"""
-        cf_expnsn = 0 * np.array(GT4Bank["GTn_CentFrq"])
-        eq_loud_db = 0 * np.array(GT4Bank["GTn_CentFrq"])
-        for ix_cfreq in range(len(GT4Bank["GTn_CentFrq"])):
-            if GT4Bank["GTn_CentFrq"][ix_cfreq] < audiogram_cfs[0]:
-                cf_expnsn[ix_cfreq] = catch_up / (catch_up - audiogram[0])
-            elif GT4Bank["GTn_CentFrq"][ix_cfreq] > audiogram_cfs[-1]:
-                cf_expnsn[ix_cfreq] = catch_up / (catch_up - audiogram[-1])
+        cf_expansion = 0 * np.array(gt4_bank["GTn_CentFrq"])
+        eq_loud_db = 0 * np.array(gt4_bank["GTn_CentFrq"])
+        for ix_cfreq in range(len(gt4_bank["GTn_CentFrq"])):
+            if gt4_bank["GTn_CentFrq"][ix_cfreq] < audiogram_cfs[0]:
+                cf_expansion[ix_cfreq] = catch_up / (catch_up - audiogram[0])
+            elif gt4_bank["GTn_CentFrq"][ix_cfreq] > audiogram_cfs[-1]:
+                cf_expansion[ix_cfreq] = catch_up / (catch_up - audiogram[-1])
             else:
-                interp_audiog = interp1d(audiogram_cfs, audiogram)
-                audiog_cf = interp_audiog(GT4Bank["GTn_CentFrq"][ix_cfreq])
-                cf_expnsn[ix_cfreq] = catch_up / (catch_up - audiog_cf)
+                interp_audiogram = interp1d(audiogram_cfs, audiogram)
+                audiog_cf = interp_audiogram(gt4_bank["GTn_CentFrq"][ix_cfreq])
+                cf_expansion[ix_cfreq] = catch_up / (catch_up - audiog_cf)
             eq_loud_db[ix_cfreq] = catch_up
 
-        self.nchans = GT4Bank["NChans"]
+        self.n_chans = gt4_bank["NChans"]
         self.gtn_denoms = torch.tensor(
-            GT4Bank["GTn_denoms"], dtype=torch.float32, device=self.device
+            gt4_bank["GTn_denoms"], dtype=torch.float32, device=self.device
         )
         self.gtn_nums = torch.tensor(
-            GT4Bank["GTn_nums"], dtype=torch.float32, device=self.device
+            gt4_bank["GTn_nums"], dtype=torch.float32, device=self.device
         )
         self.hp_denoms = torch.tensor(
-            GT4Bank["HP_denoms"], dtype=torch.float32, device=self.device
+            gt4_bank["HP_denoms"], dtype=torch.float32, device=self.device
         )
         self.hp_nums = torch.tensor(
-            GT4Bank["HP_nums"], dtype=torch.float32, device=self.device
+            gt4_bank["HP_nums"], dtype=torch.float32, device=self.device
         )
-        self.ngamma = int(GT4Bank["NGAMMA"])
-        self.gtn_delays = GT4Bank["GTnDelays"]
-        self.start2polehp = GT4Bank["Start2PoleHP"]
+        self.ngamma = int(gt4_bank["NGAMMA"])
+        self.gtn_delays = gt4_bank["GTnDelays"]
+        self.start_2_pole_hp = gt4_bank["Start2PoleHP"]
 
-        erbn_centfrq = GT4Bank["ERBn_CentFrq"]
-        chan_lpfB = []
-        chan_lpfA = []
+        erbn_centre_freq = gt4_bank["ERBn_CentFrq"]
+        chan_lpf_b = []
+        chan_lpf_a = []
         fir_lpf = []
-        for ixch in range(self.nchans):
-            fc_envlp = (30 / 40) * np.min([100, erbn_centfrq[ixch]])
-            chan_lpfB_ch, chan_lpfA_ch = ellip(2, 0.25, 35, fc_envlp / (self.sr / 2))
-            chan_lpfB.append(chan_lpfB_ch)
-            chan_lpfA.append(chan_lpfA_ch)
+        for ixch in range(self.n_chans):
+            fc_envelope = (30 / 40) * np.min([100, erbn_centre_freq[ixch]])
+            chan_lpf_b_ch, chan_lpf_a_ch = ellip(
+                2, 0.25, 35, fc_envelope / (self.sr / 2)
+            )
+            chan_lpf_b.append(chan_lpf_b_ch)
+            chan_lpf_a.append(chan_lpf_a_ch)
             fir_lpf_ch = firwin(
-                self.kernel_size, fc_envlp / (self.sr / 2), pass_zero="lowpass"
+                self.kernel_size, fc_envelope / (self.sr / 2), pass_zero="lowpass"
             ) / np.sqrt(
                 2
             )  # sqrt(2) is for the consistency with IIR
             fir_lpf.append(fir_lpf_ch)
-        self.chan_lpfB = torch.tensor(
-            np.array(chan_lpfB), dtype=torch.float32, device=self.device
+        self.chan_lpf_b = torch.tensor(
+            np.array(chan_lpf_b), dtype=torch.float32, device=self.device
         )
-        self.chan_lpfA = torch.tensor(
-            np.array(chan_lpfA), dtype=torch.float32, device=self.device
+        self.chan_lpf_a = torch.tensor(
+            np.array(chan_lpf_a), dtype=torch.float32, device=self.device
         )
         self.fir_lpf = torch.tensor(
             np.array(fir_lpf), dtype=torch.float32, device=self.device
         ).unsqueeze(1)
 
-        self.expnsn_m1 = torch.tensor(
-            cf_expnsn - 1, dtype=torch.float32, device=self.device
+        self.expansion_m1 = torch.tensor(
+            cf_expansion - 1, dtype=torch.float32, device=self.device
         )
         # self.envlp_max = torch.tensor(10 ** (0.05 * (eq_loud_db - equiv0dBfileSPL)),
         # dtype=torch.float32, device=self.device)
-        self.envlp_max = torch.tensor(
-            10 ** (0.05 * (eq_loud_db - equiv_0dB_SPL)),
+        self.envelope_max = torch.tensor(
+            10 ** (0.05 * (eq_loud_db - EQUIV_0_DB_SPL)),
             dtype=torch.float32,
             device=self.device,
         )
 
-        recombination_dB = GT4Bank["Recombination_dB"]
-        self.recruitmnet_out_coef = torch.tensor(
-            10 ** (-0.05 * recombination_dB), dtype=torch.float32, device=self.device
+        recombination_db = gt4_bank["Recombination_dB"]
+        self.recruitment_out_coef = torch.tensor(
+            10 ** (-0.05 * recombination_db), dtype=torch.float32, device=self.device
         )
 
         "settings for FIR Gammatone Filters"
-        gt_cfreq = np.array(GT4Bank["GTn_CentFrq"])
-        gt_bw = np.array(GT4Bank["ERBn_CentFrq"]) * 1.1019 * bw_broaden_coef
+        gt_cfreq = np.array(gt4_bank["GTn_CentFrq"])
+        gt_bw = np.array(gt4_bank["ERBn_CentFrq"]) * 1.1019 * bw_broaden_coef
 
         self.padding = (self.kernel_size - 1) // 2
         n_lin = torch.linspace(
@@ -414,20 +412,20 @@ def __init__(
         self.peaks = torch.argmax(gammatone, dim=1)  # for gammatone delay calibration
 
         gammatone_sin = kernel * carrier_sin
-        filters = (gammatone * window_).view(self.nchans, 1, self.kernel_size)
+        filters = (gammatone * window_).view(self.n_chans, 1, self.kernel_size)
         # To get the normalised amplitude
         filters = filters.squeeze(1).cpu().numpy()
-        fr_max = np.zeros(self.nchans)
-        for i in range(self.nchans):
+        fr_max = np.zeros(self.n_chans)
+        for i in range(self.n_chans):
             fr = np.abs(fft(filters[i]))
             fr_ = fr[: int(self.kernel_size / 2)]
             fr_max[i] = np.max(fr_)
         amp = torch.tensor(fr_max, dtype=torch.float32, device=self.device)
         gammatone = gammatone / amp.unsqueeze(1)
         gammatone_sin = gammatone_sin / amp.unsqueeze(1)
-        self.gt_fir = (gammatone * window_).view(self.nchans, 1, self.kernel_size)
+        self.gt_fir = (gammatone * window_).view(self.n_chans, 1, self.kernel_size)
         self.gt_fir_sin = (gammatone_sin * window_).view(
-            self.nchans, 1, self.kernel_size
+            self.n_chans, 1, self.kernel_size
         )
 
         "settings for spl calibration"
@@ -437,8 +435,8 @@ def __init__(
 
     def measure_rms(self, wav):
         bs = wav.shape[0]
-        ave_rms = torch.sqrt(torch.mean(wav**2, dim=1) + EPS)
-        thr_db = 20 * torch.log10(ave_rms + EPS) + self.db_relative_rms
+        average_rms = torch.sqrt(torch.mean(wav**2, dim=1) + EPS)
+        threshold_db = 20 * torch.log10(average_rms + EPS) + self.db_relative_rms
 
         num_frames = wav.shape[1] // self.win_len
         wav_reshaped = torch.reshape(
@@ -448,7 +446,7 @@ def measure_rms(self, wav):
 
         key_frames = (
             torch.where(
-                db_frames > thr_db.unsqueeze(1),
+                db_frames > threshold_db.unsqueeze(1),
                 torch.tensor(1, dtype=torch.float32, device=self.device),
                 torch.tensor(0, dtype=torch.float32, device=self.device),
             )
@@ -465,15 +463,17 @@ def measure_rms(self, wav):
 
     def calibrate_spl(self, x):
         if self.spl_cali:
-            levelreFS = 10 * torch.log10(torch.mean(x**2, dim=1, keepdim=True) + EPS)
-            leveldBSPL = equiv_0dB_SPL + levelreFS
+            level_re_fs = 10 * torch.log10(
+                torch.mean(x**2, dim=1, keepdim=True) + EPS
+            )
+            level_db_spl = EQUIV_0_DB_SPL + level_re_fs
             rms = self.measure_rms(x)
-            change_dB = leveldBSPL - (equiv_0dB_SPL + 20 * torch.log10(rms + EPS))
-            x = x * 10 ** (0.05 * change_dB)
+            change_db = level_db_spl - (EQUIV_0_DB_SPL + 20 * torch.log10(rms + EPS))
+            x = x * 10 ** (0.05 * change_db)
         return x
 
-    def src_to_cochlea_filt(self, x, coch_filter):
-        return F.conv1d(x, coch_filter, padding=self.coch_padding)
+    def src_to_cochlea_filt(self, x, cochlear_filter):
+        return F.conv1d(x, cochlear_filter, padding=self.cochlear_padding)
 
     def smear(self, x):
         """Padding issue needs to be worked out"""
@@ -493,7 +493,7 @@ def smear(self, x):
         phasor = spec[:, : self.smear_nfft // 2, :, :] / (mag + EPS)
 
         smeared_power = (
-            torch.matmul(power.transpose(-1, -2), self.fsmear.transpose(0, 1))
+            torch.matmul(power.transpose(-1, -2), self.f_smear.transpose(0, 1))
             .transpose(-1, -2)
             .unsqueeze(-1)
             + EPS
@@ -517,10 +517,10 @@ def smear(self, x):
         return smeared_wav.unsqueeze(1)
 
     def recruitment(self, x):
-        nsamps = x.shape[-1]
+        n_samples = x.shape[-1]
         ixhp = 0
         outputs = []
-        for ixch in range(self.nchans):
+        for ixch in range(self.n_chans):
             # Gammaton filtering
             pass_n = torchaudio.functional.lfilter(
                 x, self.gtn_denoms[ixch, :], self.gtn_nums[ixch, :]
@@ -532,71 +532,77 @@ def recruitment(self, x):
             dly = self.gtn_delays[ixch]
 
             pass_n_cali = torch.zeros_like(pass_n)
-            pass_n_cali[:, :, : nsamps - dly] = pass_n[:, :, dly:nsamps]
+            pass_n_cali[:, :, : n_samples - dly] = pass_n[:, :, dly:n_samples]
             # Tail control
-            if ixch >= self.start2polehp:
+            if ixch >= self.start_2_pole_hp:
                 ixhp += 1
                 pass_n_cali = torchaudio.functional.lfilter(
                     pass_n_cali, self.hp_denoms[ixhp - 1, :], self.hp_nums[ixhp - 1, :]
                 )
 
             # Get the envelope
-            envlp_out = torchaudio.functional.lfilter(
-                torch.abs(pass_n_cali), self.chan_lpfA[ixch, :], self.chan_lpfB[ixch, :]
+            envelope_out = torchaudio.functional.lfilter(
+                torch.abs(pass_n_cali),
+                self.chan_lpf_a[ixch, :],
+                self.chan_lpf_b[ixch, :],
             )
-            envlp_out = torch.flip(envlp_out, dims=[-1])
-            envlp_out = torchaudio.functional.lfilter(
-                envlp_out, self.chan_lpfA[ixch, :], self.chan_lpfB[ixch, :]
+            envelope_out = torch.flip(envelope_out, dims=[-1])
+            envelope_out = torchaudio.functional.lfilter(
+                envelope_out, self.chan_lpf_a[ixch, :], self.chan_lpf_b[ixch, :]
             )
-            envlp_out = torch.flip(envlp_out, dims=[-1])
+            envelope_out = torch.flip(envelope_out, dims=[-1])
 
-            envlp_out = torch.clamp(envlp_out, min=EPS, max=self.envlp_max[ixch])
-            gain = (envlp_out / self.envlp_max[ixch]) ** self.expnsn_m1[ixch]
+            envelope_out = torch.clamp(
+                envelope_out, min=EPS, max=self.envelope_max[ixch]
+            )
+            gain = (envelope_out / self.envelope_max[ixch]) ** self.expansion_m1[ixch]
             outputs.append(gain * pass_n_cali)
 
         y = torch.stack(outputs, dim=-1).sum(dim=-1)
-        y = y * self.recruitmnet_out_coef
+        y = y * self.recruitment_out_coef
         return y
 
     def recruitment_fir(self, x):
-        nsamps = x.shape[-1]
-        x = x.repeat([1, self.nchans, 1])
+        n_samples = x.shape[-1]
+        x = x.repeat([1, self.n_chans, 1])
         real = F.conv1d(
-            x, self.gt_fir, bias=None, padding=self.padding, groups=self.nchans
+            x, self.gt_fir, bias=None, padding=self.padding, groups=self.n_chans
         )
         imag = F.conv1d(
-            x, self.gt_fir_sin, bias=None, padding=self.padding, groups=self.nchans
+            x, self.gt_fir_sin, bias=None, padding=self.padding, groups=self.n_chans
         )
         real_cali = torch.zeros_like(real)
         imag_cali = torch.zeros_like(imag)
-        for i in range(self.nchans):
-            real_cali[:, i, : nsamps - self.peaks[i]] = real[
-                :, i, self.peaks[i] : nsamps
+        for i in range(self.n_chans):
+            real_cali[:, i, : n_samples - self.peaks[i]] = real[
+                :, i, self.peaks[i] : n_samples
             ]
-            imag_cali[:, i, : nsamps - self.peaks[i]] = imag[
-                :, i, self.peaks[i] : nsamps
+            imag_cali[:, i, : n_samples - self.peaks[i]] = imag[
+                :, i, self.peaks[i] : n_samples
             ]
 
         env = torch.sqrt(real_cali * real_cali + imag_cali * imag_cali + EPS)
         env = F.conv1d(
-            env, self.fir_lpf, bias=None, padding=self.padding, groups=self.nchans
+            env, self.fir_lpf, bias=None, padding=self.padding, groups=self.n_chans
         )
 
-        env_max = self.envlp_max.unsqueeze(0).unsqueeze(-1).repeat([1, 1, nsamps])
+        env_max = self.envelope_max.unsqueeze(0).unsqueeze(-1).repeat([1, 1, n_samples])
         gain = torch.clamp(env / env_max, min=EPS, max=1)
-        gain = gain ** self.expnsn_m1.unsqueeze(0).unsqueeze(-1).repeat([1, 1, nsamps])
+        gain = gain ** self.expansion_m1.unsqueeze(0).unsqueeze(-1).repeat(
+            [1, 1, n_samples]
+        )
         y = torch.sum(gain * real_cali, dim=1, keepdim=True)
-        y = y * self.recruitmnet_out_coef
+        y = y * self.recruitment_out_coef
         return y
 
     def forward(self, x):
         x = self.calibrate_spl(x)
         x = x.unsqueeze(1)
-        x = self.src_to_cochlea_filt(x, self.coch_filter_forward)
+        x = self.src_to_cochlea_filt(x, self.cochlear_filter_forward)
         x = self.smear(x)
         # x = self.recruitment(x)
         x = self.recruitment_fir(x)
-        y = self.src_to_cochlea_filt(x, self.coch_filter_backward)
+        y = self.src_to_cochlea_filt(x, self.cochlear_filter_backward)
         return y.squeeze(1)
 
 
diff --git a/clarity/utils/car_noise_simulator/carnoise_parameters_generator.py b/clarity/utils/car_noise_simulator/carnoise_parameters_generator.py
index 83dc83a92..9891f1f40 100644
--- a/clarity/utils/car_noise_simulator/carnoise_parameters_generator.py
+++ b/clarity/utils/car_noise_simulator/carnoise_parameters_generator.py
@@ -3,7 +3,7 @@
 
 These are 2 separated class to keep the logic separated
 """
-from typing import Dict, Optional, Tuple
+from __future__ import annotations
 
 import numpy as np
 
@@ -55,7 +55,7 @@ def __init__(
         secondary_filter_cutoff: float = 0.8,
         secondary_filter_constant_hz: float = 200,
         random_flag: bool = True,
-        random_seed: Optional[int] = None,
+        random_seed: int | None = None,
     ) -> None:
         """
         Constructor takes a boolean flag to indicate whether
@@ -114,7 +114,7 @@ def set_new_randomisationrange_freqmultiplier(self):
             np.arange(0.9, 1.1, 0.001) if self.random_flag else [1]
         )
 
-    def gen_parameters(self, speed_kph: float) -> Dict:
+    def gen_parameters(self, speed_kph: float) -> dict:
         """
         Generate a dictionary of noise parameters for a given speed.
 
@@ -175,7 +175,7 @@ def _get_rpm(self, gear: int, speed_kph: float) -> float:
         rpm = self.RPM_LOOKUP[gear] * speed_kph * 60
         return rpm
 
-    def _generate_primary_filter(self, speed_kph: float) -> Dict:
+    def _generate_primary_filter(self, speed_kph: float) -> dict:
         return {
             "order": 1,
             "btype": "lowpass",
@@ -186,7 +186,7 @@ def _generate_primary_filter(self, speed_kph: float) -> Dict:
             * np.random.choice(self.randomisationrange_freqmultiplier),
         }
 
-    def _generate_secondary_filter(self, speed_kph: float) -> Dict:
+    def _generate_secondary_filter(self, speed_kph: float) -> dict:
         return {
             "order": 2,
             "btype": "lowpass",
@@ -197,7 +197,7 @@ def _generate_secondary_filter(self, speed_kph: float) -> Dict:
             * np.random.choice(self.randomisationrange_freqmultiplier),
         }
 
-    def _generate_bump_filter(self) -> Dict:
+    def _generate_bump_filter(self) -> dict:
         bumpfilter_order = 1 if not self.random_flag else np.random.choice([1, 2])
         bumpfilter_f1_hz = (
             30 if not self.random_flag else np.random.randint(20, 101, size=1)[0]
@@ -214,7 +214,7 @@ def _generate_bump_filter(self) -> Dict:
         }
         return filter_dict
 
-    def _generate_dip_filter(self) -> Tuple[Dict, Dict]:
+    def _generate_dip_filter(self) -> tuple[dict, dict]:
         dipfilter_order = 2 if not self.random_flag else np.random.choice([1, 2])
         dipfilter_f1_hz = (
             200 if not self.random_flag else np.random.choice(range(30, 201, 10))
diff --git a/clarity/utils/car_noise_simulator/carnoise_signal_generator.py b/clarity/utils/car_noise_simulator/carnoise_signal_generator.py
index 7d87f0f33..f8f8e4c46 100644
--- a/clarity/utils/car_noise_simulator/carnoise_signal_generator.py
+++ b/clarity/utils/car_noise_simulator/carnoise_signal_generator.py
@@ -1,15 +1,15 @@
 """
 Class to generate the car noise signal.
 """
-from typing import Dict, Tuple, Union
+from __future__ import annotations
 
 import numpy as np
 from scipy.signal import butter, lfilter
 
 
 def _butter_filter(
-    order: int, cutoff_hz: Union[list, np.ndarray], btype: str, sample_rate: int
-) -> Tuple[np.ndarray, np.ndarray]:
+    order: int, cutoff_hz: list | np.ndarray, btype: str, sample_rate: int
+) -> tuple[np.ndarray, np.ndarray]:
     """
     Function that creates a butter filter
     Args:
@@ -99,7 +99,7 @@ def generate_car_noise(
         # .. reference level = no speed dependence  plus a small randomization
         referencelevel_db = noise_parameters["reference_level_db"]
 
-        filter_numer_denom: Dict = {}
+        filter_numer_denom: dict = {}
         for filter_type in [
             "primary_filter",
             "secondary_filter",
@@ -162,11 +162,11 @@ def generate_car_noise(
     def generate_source_noise(
         self,
         reference_level_db: float,
-        primary_filter: Dict[str, np.ndarray],
-        secondary_filter: Dict[str, np.ndarray],
-        bump_filter: Dict[str, np.ndarray],
-        dip_low_filter: Dict[str, np.ndarray],
-        dip_high_filter: Dict[str, np.ndarray],
+        primary_filter: dict[str, np.ndarray],
+        secondary_filter: dict[str, np.ndarray],
+        bump_filter: dict[str, np.ndarray],
+        dip_low_filter: dict[str, np.ndarray],
+        dip_high_filter: dict[str, np.ndarray],
     ) -> np.ndarray:
         """
         Method that generates the noise of a single source.
@@ -230,8 +230,8 @@ def generate_engine_noise(
         rpm: float,
         reference_level_db: float,
         engine_num_harmonics: int,
-        primary_filter: Dict[str, np.ndarray],
-        secondary_filter: Dict[str, np.ndarray],
+        primary_filter: dict[str, np.ndarray],
+        secondary_filter: dict[str, np.ndarray],
     ) -> np.ndarray:
         """
         Method that generates the noise of the engine.
@@ -286,7 +286,7 @@ def generate_engine_noise(
 
         return engine_noise
 
-    def get_bump_params(self, reference_level_db: float) -> Tuple[float, float]:
+    def get_bump_params(self, reference_level_db: float) -> tuple[float, float]:
         """
         Method that gets the parameters of the bump noise
         Args:
@@ -314,7 +314,7 @@ def get_engine_params(
         rpm: float,
         reference_level_db: float,
         engine_num_harmonics: int,
-    ) -> Tuple[np.ndarray, np.ndarray]:
+    ) -> tuple[np.ndarray, np.ndarray]:
         """
         Method that gets the parameters of the engine noise
 
diff --git a/clarity/utils/file_io.py b/clarity/utils/file_io.py
index a6b63a8c8..42c208ada 100644
--- a/clarity/utils/file_io.py
+++ b/clarity/utils/file_io.py
@@ -4,7 +4,7 @@
 
 def read_jsonl(filename: str) -> list:
     """Read a jsonl file into a list of dictionaries."""
-    with open(filename, "r", encoding="utf-8") as fp:
+    with open(filename, encoding="utf-8") as fp:
         records = [json.loads(line) for line in fp]
     return records
 
diff --git a/clarity/utils/signal_processing.py b/clarity/utils/signal_processing.py
index 1b6e038df..d3eae4e57 100644
--- a/clarity/utils/signal_processing.py
+++ b/clarity/utils/signal_processing.py
@@ -1,9 +1,9 @@
-from typing import Tuple
+from __future__ import annotations
 
 import numpy as np
 
 
-def normalize_signal(signal: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
+def normalize_signal(signal: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
     """Standardize the signal to have zero mean and unit variance.
 
     Args:
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index c436f0af1..97f9fd7ea 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -60,35 +60,58 @@ Once you have created an issue you can
 [fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) the
 pyClarity repository to your own account and create a branch on which to undertake development.
 
-### Coding Style
+## Coding Style
 
-We ask that you adhere to the [PEP8](https://pep8.org/) coding style when writing your code. To facilitate this we use
-[flake8](https://flake8.pycqa.org/en/latest/) and [isort](https://pycqa.github.io/isort/). Most popular Integrated
-Development Environments (IDEs) include plugins that will apply flake8 to your code on saving a file.
+We ask that you adhere to the [PEP8](https://pep8.org/) coding style when writing your code. To facilitate this we use a
+number of linting tools.
 
-For information on how to configure some popular IDEs see the following links.
+* [flake8](https://flake8.pycqa.org/en/latest/)
+* [black](https://black.readthedocs.io/en/stable/index.html)
+* [isort](https://pycqa.github.io/isort/)
+* [mypy](https://mypy.readthedocs.io/en/stable/)
+* [pylint](pylint.org)
 
-* [Linting Python in Visual Studio Code](https://code.visualstudio.com/docs/python/linting)
-* [Flake8 support - PyCharm Plugin](https://plugins.jetbrains.com/plugin/11563-flake8-support)
-* [Emacs flymake-python-pyflakes](https://github.com/purcell/flymake-python-pyflakes/)
+### pre-commit
+
+To ensure these coding conventions are applied to code that is submitted to the `main` branch PyClarity uses
+[`pre-commit`](https://pre-commit.com/) and [`pre-commit.ci`](https://pre-commit.ci) which run Git Hooks before each
+commit is made (the former locally, the latter on pull requests) for the above linting (and more e.g. Markdown style is
+also checked).
 
-Further we have implemented a [`pre-commit`](https://pre-commit.com/) hook to ensure flake8 and isort are applied each
-time you make a commit. Whilst the `pre-commit` package will have been installed in your environment when you need to
-install the configured hooks (which are defined in
+Whilst the `pre-commit` Python package will have been installed in your environment when you `pip install .[dev]` you
+need to install the configured hooks (which are defined in
 [`.precommit-config.yaml`](https://github.com/claritychallenge/clarity/blob/main/.pre-commit-config.yaml)) in your local
-repository. To do so run the following from the `clarity` directory...
+copy of the PyClarity repository. To do so run the following from the `clarity` directory...
 
 ``` bash
 pre-commit install
 ```
 
-This installs `.git/hooks/pre-commit` commit hook which is triggered each time you make a new commit. When run this will
-automatically lint your code with flake8 and isort so please check the changes carefully.
+This installs `.git/hooks/pre-commit` commit hook which is triggered each time you make a new commit.
 
-If your commit fails to pass the checks please read the error messages carefully to find out what has changed and what
-you need to manually fix.
+If your commit fails to pass the checks please read the error messages carefully. Some changes are fixed automatically
+(e.g. `black` will format files in place where it can), but not all changes can be fixed and you should read the output
+carefully  to find out what you need to manually fix.
 
-### Testing
+### `pathlib` over `os`
+
+PyClarity uses the object-orientated package [`pathlib`](https://docs.python.org/3/library/pathlib.html)
+throughout the code base for any operations involving the file system and path components as it makes code easier to
+read. We ask that any contributions follow this convention and use `pathlib` rather than `os`. For more on `pathlib` see
+[1] and [2].
+
+### IDEs
+
+Most popular Integrated Development Environments (IDEs) include plugins that will apply `flake8` / `black` to your code
+on saving a file.
+
+For information on how to configure some popular IDEs see the following links.
+
+* [Linting Python in Visual Studio Code](https://code.visualstudio.com/docs/python/linting)
+* [Flake8 support - PyCharm Plugin](https://plugins.jetbrains.com/plugin/11563-flake8-support)
+* [Emacs flymake-python-pyflakes](https://github.com/purcell/flymake-python-pyflakes/)
+
+## Testing
 
 All new code should be covered by [unit
 tests](https://carpentries-incubator.github.io/python-testing/04-units/index.html) with at least 70% coverage and where
@@ -103,4 +126,13 @@ The Continuous Integration in place on GitHub Actions runs `pytest` on newly cre
 pass successfully before merging so it is useful to ensure they pass before you create pull requests at the very
 least. Sometimes it may be sensible to run them against commits too.
 
-To run the tests simply call `pytest` from the root of the project folder.
+To run the tests you should install the additional dependencies and then call `pytest` from the root of the project
+folder.
+
+``` bash
+pip install .[tests]
+pytest
+```
+
+[1]: https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/
+[2]: https://treyhunner.com/2019/01/no-really-pathlib-is-great/
diff --git a/recipes/cad1/README.md b/recipes/cad1/README.md
index 9b991ac21..dca206dca 100644
--- a/recipes/cad1/README.md
+++ b/recipes/cad1/README.md
@@ -21,7 +21,9 @@ The performance of each system on the validation set is reported below.
 
 ### Task 1 - Listening music via headphones
 
-The average HAAQI score per song is:
+**The overall HAAQI score is 0.3608.**
+
+#### Average HAAQI score per song
 
 | Song                                            |   HAAQI    |
 |:------------------------------------------------|:----------:|
@@ -37,8 +39,19 @@ The average HAAQI score per song is:
 | Patrick Talbot - A Reason To Leave              |   0.4545   |
 | Skelpolu - Human Mistakes                       |   0.3055   |
 | Triviul - Angelsaint                            |   0.2883   |
-| **Total Result**                                | **0.3608** |
 
-### Task 2 - Listening music in a car wearing hearing aids
+### Task 2 - Listening music in a car with presence of noise
+
+**The overall HAAQI score is 0.1248.**
+
+#### Average HAAQI score per genre
 
-TODO - add Table with results
+| Genre          |   HAAQI    |
+|:---------------|:----------:|
+| Classical      |   0.1240   |
+| Hip-Hop        |   0.1271   |
+| Instrumental   |   0.1250   |
+| International  |   0.1267   |
+| Orchestral     |   0.1121   |
+| Pop            |   0.1339   |
+| Rock           |   0.1252   |
diff --git a/recipes/cad1/task1/baseline/README.md b/recipes/cad1/task1/baseline/README.md
index 0f45ae941..7732be324 100644
--- a/recipes/cad1/task1/baseline/README.md
+++ b/recipes/cad1/task1/baseline/README.md
@@ -176,7 +176,7 @@ Please note: you will not get identical HAAQI scores for the same signals if the
 (in the given recipe, the random seed for each signal is set as the last eight digits of the song md5).
 As there are random noises generated within HAAQI, but the differences should be sufficiently small.
 
-The score for the baseline enhancement is 0.3608 HAAQI overall.
+The score for the baseline is 0.3608 HAAQI overall.
 
 ## References
 
diff --git a/recipes/cad1/task1/baseline/enhance.py b/recipes/cad1/task1/baseline/enhance.py
index 17a7fd29a..18154a7d4 100644
--- a/recipes/cad1/task1/baseline/enhance.py
+++ b/recipes/cad1/task1/baseline/enhance.py
@@ -1,11 +1,9 @@
 """ Run the dummy enhancement. """
-# pylint: disable=too-many-locals
-# pylint: disable=import-error
+from __future__ import annotations
 
 import json
 import logging
 from pathlib import Path
-from typing import Dict, List, Union
 
 import hydra
 import numpy as np
@@ -21,16 +19,20 @@
 from clarity.utils.signal_processing import denormalize_signals, normalize_signal
 from recipes.cad1.task1.baseline.evaluate import make_song_listener_list
 
+# pylint: disable=too-many-locals
+# pylint: disable=import-error
+
+
 logger = logging.getLogger(__name__)
 
 
 def separate_sources(
     model: torch.nn.Module,
-    mix: Union[torch.Tensor, np.ndarray],
+    mix: torch.Tensor | np.ndarray,
     sample_rate: int,
     segment: float = 10.0,
     overlap: float = 0.1,
-    device: Union[torch.device, str] = None,
+    device: torch.device | str = None,
 ):
     """
     Apply model to a given mixture.
@@ -117,7 +119,7 @@ def get_device(device: str) -> tuple:
     raise ValueError(f"Unsupported device type: {device}")
 
 
-def map_to_dict(sources: np.ndarray, sources_list: List[str]) -> Dict:
+def map_to_dict(sources: np.ndarray, sources_list: list[str]) -> dict:
     """Map sources to a dictionary separating audio into left and right channels.
 
     Args:
@@ -146,7 +148,7 @@ def decompose_signal(
     device: torch.device,
     left_audiogram: np.ndarray,
     right_audiogram: np.ndarray,
-) -> Dict[str, np.ndarray]:
+) -> dict[str, np.ndarray]:
     """
     Decompose signal into 8 stems.
 
@@ -286,10 +288,10 @@ def enhance(config: DictConfig) -> None:
 
     # Processing Validation Set
     # Load listener audiograms and songs
-    with open(config.path.listeners_valid_file, "r", encoding="utf-8") as file:
+    with open(config.path.listeners_valid_file, encoding="utf-8") as file:
         listener_valid_audiograms = json.load(file)
 
-    with open(config.path.music_valid_file, "r", encoding="utf-8") as file:
+    with open(config.path.music_valid_file, encoding="utf-8") as file:
         song_data = json.load(file)
     songs_valid = pd.DataFrame.from_dict(song_data)
 
diff --git a/recipes/cad1/task1/baseline/evaluate.py b/recipes/cad1/task1/baseline/evaluate.py
index a3e82408f..266d09bc0 100644
--- a/recipes/cad1/task1/baseline/evaluate.py
+++ b/recipes/cad1/task1/baseline/evaluate.py
@@ -1,6 +1,5 @@
 """Evaluate the enhanced signals using the HAAQI metric."""
-# pylint: disable=too-many-locals
-# pylint: disable=import-error
+from __future__ import annotations
 
 import csv
 import hashlib
@@ -8,7 +7,7 @@
 import json
 import logging
 from pathlib import Path
-from typing import Any, Dict, List, Tuple
+from typing import Any
 
 import hydra
 import numpy as np
@@ -18,6 +17,10 @@
 
 from clarity.evaluator.haaqi import compute_haaqi
 
+# pylint: disable=too-many-locals
+# pylint: disable=import-error
+
+
 logger = logging.getLogger(__name__)
 
 
@@ -63,7 +66,7 @@ def add_result(
         listener: str,
         song: str,
         score: float,
-        instruments_scores: Dict[str, float],
+        instruments_scores: dict[str, float],
     ):
         """Add a result to the CSV file.
 
@@ -105,8 +108,8 @@ def set_song_seed(song: str) -> None:
 
 
 def make_song_listener_list(
-    songs: List[str], listeners: Dict[str, Any], small_test: bool = False
-) -> List[Tuple[str, str]]:
+    songs: list[str], listeners: dict[str, Any], small_test: bool = False
+) -> list[tuple[str, str]]:
     """Make the list of scene-listener pairing to process"""
     song_listener_pairs = list(itertools.product(songs, listeners.keys()))
 
@@ -123,7 +126,7 @@ def _evaluate_song_listener(
     split_dir: str,
     listener_audiograms: dict,
     enhanced_folder: Path,
-) -> Tuple[float, dict]:
+) -> tuple[float, dict]:
     """Evaluate a single song-listener pair
 
     Args:
@@ -211,12 +214,12 @@ def _evaluate_song_listener(
 def run_calculate_aq(config: DictConfig) -> None:
     """Evaluate the enhanced signals using the HAAQI metric."""
     # Load test songs
-    with open(config.path.music_valid_file, "r", encoding="utf-8") as fp:
+    with open(config.path.music_valid_file, encoding="utf-8") as fp:
         songs = json.load(fp)
     songs = pd.DataFrame.from_dict(songs)
 
     # Load listener data
-    with open(config.path.listeners_valid_file, "r", encoding="utf-8") as fp:
+    with open(config.path.listeners_valid_file, encoding="utf-8") as fp:
         listener_audiograms = json.load(fp)
 
     enhanced_folder = Path("enhanced_signals")
diff --git a/recipes/cad1/task2/baseline/README.md b/recipes/cad1/task2/baseline/README.md
index 9ff7779f5..0dfe7aaaa 100644
--- a/recipes/cad1/task2/baseline/README.md
+++ b/recipes/cad1/task2/baseline/README.md
@@ -138,7 +138,7 @@ Please note: you will not get identical HAAQI scores for the same signals if the
 (in the given recipe, the random seed for each signal is set as the last eight digits of the song md5).
 As there are random noises generated within HAAQI, but the differences should be sufficiently small.
 
-**Baseline performance on the validation set will be updated soon.**
+The overall HAAQI score for baseline is 0.1248.
 
 ## References
 
diff --git a/recipes/cad1/task2/baseline/audio_manager.py b/recipes/cad1/task2/baseline/audio_manager.py
index 650a2f86c..c665b6b9e 100644
--- a/recipes/cad1/task2/baseline/audio_manager.py
+++ b/recipes/cad1/task2/baseline/audio_manager.py
@@ -1,9 +1,9 @@
 """A utility class for managing audio files."""
+from __future__ import annotations
 
 import logging
 import warnings
 from pathlib import Path
-from typing import Dict, Tuple, Union
 
 import numpy as np
 import pyloudnorm as pyln
@@ -18,11 +18,11 @@ class AudioManager:
     def __init__(
         self,
         sample_rate: int = 44100,
-        output_audio_path: Union[str, Path] = "",
+        output_audio_path: str | Path = "",
         soft_clip: bool = False,
     ):
         """Initialize the AudioManager instance."""
-        self.audios_to_save: Dict[str, np.adarray] = {}
+        self.audios_to_save: dict[str, np.adarray] = {}
         self.sample_rate = sample_rate
         self.soft_clip = soft_clip
         self.output_audio_path = Path(output_audio_path)
@@ -76,7 +76,7 @@ def _save_audio(self, file_name: str, waveform: np.ndarray) -> None:
 
     def clip_audio(
         self, signal: np.ndarray, min_val: float = -1, max_val: float = 1
-    ) -> Tuple[int, np.ndarray]:
+    ) -> tuple[int, np.ndarray]:
         """Clip a WAV file to the given range.
 
         Args:
diff --git a/recipes/cad1/task2/baseline/baseline_utils.py b/recipes/cad1/task2/baseline/baseline_utils.py
index c5b4e293f..5ea78bd3a 100644
--- a/recipes/cad1/task2/baseline/baseline_utils.py
+++ b/recipes/cad1/task2/baseline/baseline_utils.py
@@ -1,23 +1,25 @@
 """Utility functions for the baseline model."""
-# pylint: disable=import-error
+from __future__ import annotations
 
 import json
 import logging
 import warnings
 from pathlib import Path
-from typing import Optional, Tuple, Union
 
 import librosa
 import numpy as np
 import pandas as pd
 from omegaconf import DictConfig
 
+# pylint: disable=import-error
+
+
 logger = logging.getLogger(__name__)
 
 
 def read_mp3(
-    file_path: Union[str, Path], sample_rate: Optional[int] = None
-) -> Tuple[np.ndarray, Optional[int]]:
+    file_path: str | Path, sample_rate: int | None = None
+) -> tuple[np.ndarray, int | None]:
     """Read a MP3 file and return its signal.
 
     Args:
@@ -65,12 +67,12 @@ def load_hrtf(config: DictConfig) -> dict:
         hrtf_data (dict): A dictionary containing the HRTF data for the dataset.
 
     """
-    with open(config.path.hrtf_file, "r", encoding="utf-8") as fp:
+    with open(config.path.hrtf_file, encoding="utf-8") as fp:
         hrtf_data = json.load(fp)
     return hrtf_data[config.evaluate.split]
 
 
-def load_listeners_and_scenes(config: DictConfig) -> Tuple[dict, dict, dict]:
+def load_listeners_and_scenes(config: DictConfig) -> tuple[dict, dict, dict]:
     """Load listener and scene data
 
     Args:
@@ -85,21 +87,21 @@ def load_listeners_and_scenes(config: DictConfig) -> Tuple[dict, dict, dict]:
 
     """
     # Load listener data
-    with open(config.path.scenes_file, "r", encoding="utf-8") as fp:
+    with open(config.path.scenes_file, encoding="utf-8") as fp:
         df_scenes = pd.read_json(fp, orient="index")
 
     # Load audiograms and scence data for the corresponding split
     if config.evaluate.split == "train":
-        with open(config.path.listeners_train_file, "r", encoding="utf-8") as fp:
+        with open(config.path.listeners_train_file, encoding="utf-8") as fp:
             listener_audiograms = json.load(fp)
         scenes = df_scenes[df_scenes["split"] == "train"].to_dict("index")
 
     elif config.evaluate.split == "valid":
-        with open(config.path.listeners_valid_file, "r", encoding="utf-8") as fp:
+        with open(config.path.listeners_valid_file, encoding="utf-8") as fp:
             listener_audiograms = json.load(fp)
         scenes = df_scenes[df_scenes["split"] == "valid"].to_dict("index")
 
-    with open(config.path.scenes_listeners_file, "r", encoding="utf-8") as fp:
+    with open(config.path.scenes_listeners_file, encoding="utf-8") as fp:
         scenes_listeners = json.load(fp)
         scenes_listeners = {
             k: v for k, v in scenes_listeners.items() if k in scenes.keys()
diff --git a/recipes/cad1/task2/baseline/car_scene_acoustics.py b/recipes/cad1/task2/baseline/car_scene_acoustics.py
index fd6f6a3e3..1d1d6bc61 100644
--- a/recipes/cad1/task2/baseline/car_scene_acoustics.py
+++ b/recipes/cad1/task2/baseline/car_scene_acoustics.py
@@ -1,11 +1,11 @@
 """A class for the car acoustic environment."""
 # pylint: disable=import-error
 # pylint: disable=too-many-instance-attributes
+from __future__ import annotations
 
 import logging
 import warnings
 from pathlib import Path
-from typing import Optional
 
 import numpy as np
 import pyloudnorm as pyln
@@ -223,7 +223,7 @@ def scale_signal_to_snr(
         self,
         signal: np.ndarray,
         reference_signal: np.ndarray = None,
-        snr: Optional[float] = 0,
+        snr: float | None = 0,
     ) -> np.ndarray:
         """
         Scales the target signal to the desired SNR.
diff --git a/recipes/cad1/task2/baseline/enhance.py b/recipes/cad1/task2/baseline/enhance.py
index 9e8139604..27506f4ff 100644
--- a/recipes/cad1/task2/baseline/enhance.py
+++ b/recipes/cad1/task2/baseline/enhance.py
@@ -1,11 +1,11 @@
 """ Run the dummy enhancement. """
 # pylint: disable=too-many-locals
 # pylint: disable=import-error
+from __future__ import annotations
 
 import logging
 import warnings
 from pathlib import Path
-from typing import Tuple
 
 import hydra
 import numpy as np
@@ -52,7 +52,7 @@ def enhance_song(
     waveform: np.ndarray,
     listener_audiograms: dict,
     config: DictConfig,
-) -> Tuple[np.ndarray, np.ndarray]:
+) -> tuple[np.ndarray, np.ndarray]:
     """
     Enhance a single song for a listener.
 
diff --git a/recipes/cad1/task2/baseline/evaluate.py b/recipes/cad1/task2/baseline/evaluate.py
index bca2f3c55..5137d3ac4 100644
--- a/recipes/cad1/task2/baseline/evaluate.py
+++ b/recipes/cad1/task2/baseline/evaluate.py
@@ -1,12 +1,12 @@
 """Evaluate the enhanced signals using the HAAQI metric."""
 # pylint: disable=too-many-locals
 # pylint: disable=import-error
+from __future__ import annotations
 
 import csv
 import hashlib
 import logging
 from pathlib import Path
-from typing import Tuple
 
 import hydra
 import numpy as np
@@ -50,7 +50,9 @@ def write_header(self):
             )
             csv_writer.writerow(
                 [
+                    "scene",
                     "song",
+                    "genre",
                     "listener",
                     "score",
                     "haaqi_left",
@@ -58,10 +60,12 @@ def write_header(self):
                 ]
             )
 
+    # pylint: disable=too-many-arguments
     def add_result(
         self,
         scene: str,
         song: str,
+        genre: str,
         listener: str,
         score: float,
         haaqi_left: float,
@@ -72,6 +76,7 @@ def add_result(
         Args:
             scene (str): The name of the scene that the result is for.
             song (str): The name of the song that the result is for.
+            genre (str): The genre of the song that the result is for.
             listener (str): The name of the listener who submitted the result.
             score (float): The combined score for the result.
             haaqi_left (float): The HAAQI score for the left channel.
@@ -88,6 +93,7 @@ def add_result(
                 [
                     scene,
                     song,
+                    genre,
                     listener,
                     str(score),
                     str(haaqi_left),
@@ -117,7 +123,7 @@ def evaluate_scene(
     car_scene_acoustic: CarSceneAcoustics,
     hrtf: dict,
     config: DictConfig,
-) -> Tuple[float, float]:
+) -> tuple[float, float]:
     """Evaluate a single scene and return HAAQI scores for left and right ears
 
     Args:
@@ -150,7 +156,8 @@ def evaluate_scene(
     """
     audio_manager = AudioManager(
         output_audio_path=Path("evaluation_signals")
-        / f"{listener_audiogram['name']} / {current_scene['song']}",
+        / f"{listener_audiogram['name']}"
+        / f"{current_scene['song']}",
         sample_rate=sample_rate,
         soft_clip=config.soft_clip,
     )
@@ -286,6 +293,7 @@ def run_calculate_audio_quality(config: DictConfig) -> None:
         results_file.add_result(
             scene_id,
             current_scene["song"],
+            current_scene["song_path"].split("/")[-2],
             listener_id,
             score=float(score),
             haaqi_left=aq_score_l,
diff --git a/recipes/cad1/task2/baseline/merge_batches_results.py b/recipes/cad1/task2/baseline/merge_batches_results.py
index d5803f7ef..e5f2e58c3 100644
--- a/recipes/cad1/task2/baseline/merge_batches_results.py
+++ b/recipes/cad1/task2/baseline/merge_batches_results.py
@@ -14,7 +14,9 @@ def join_batches(config: DictConfig) -> None:
     batches_results = []
     for batch in range(config.evaluate.batch_size):
         batches_results.append(
-            pd.read_csv(f"scores_{batch}-{config.evaluate.batch_size}.csv")
+            pd.read_csv(
+                f"scores_{batch}-{config.evaluate.batch_size}.csv", index_col=False
+            )
         )
     df_res = pd.concat(batches_results, ignore_index=True)
     df_res.to_csv("scores.csv", index=False)
diff --git a/recipes/cad1/task2/data_preparation/build_scene_metadata.py b/recipes/cad1/task2/data_preparation/build_scene_metadata.py
index 3e7db24cc..9cab5811e 100644
--- a/recipes/cad1/task2/data_preparation/build_scene_metadata.py
+++ b/recipes/cad1/task2/data_preparation/build_scene_metadata.py
@@ -1,9 +1,10 @@
 """Module to Generate the metadata for the scenes in the CAD-1 Task-2 challenge."""
 # pylint: disable=import-error
+from __future__ import annotations
 
 import json
 import logging
-from typing import Any, Dict
+from typing import Any
 
 import hydra
 import numpy as np
@@ -35,7 +36,7 @@ def get_random_head_rotation(input_dict: dict) -> Any:
     return float(np.random.choice(list(input_dict.keys()), size=1, replace=False)[0])
 
 
-def get_random_car_params(min_speed: int = 50, max_speed: int = 120) -> Dict:
+def get_random_car_params(min_speed: int = 50, max_speed: int = 120) -> dict:
     """
     Returns a dictionary with the parameters for a car noise.
     These parameters are generated randomly, based on the car speed.
@@ -53,7 +54,7 @@ def get_random_car_params(min_speed: int = 50, max_speed: int = 120) -> Dict:
     return car_params
 
 
-def read_json(path_file) -> Dict:
+def read_json(path_file) -> dict:
     """Function the read a json file and return the data as a dictionary
     or only the keys if ```return_keys``` is True.
 
@@ -61,7 +62,7 @@ def read_json(path_file) -> Dict:
         path_file (str): Path to the json file.
     """
 
-    with open(path_file, "r", encoding="utf-8") as file:
+    with open(path_file, encoding="utf-8") as file:
         json_data = json.load(file)
     return json_data
 
@@ -113,9 +114,9 @@ def run(cfg: DictConfig) -> None:
     brir = read_json(cfg.path.brir_file)
 
     # Start generating scenes for training
-    train_scenes: Dict = {}
-    valid_scenes: Dict = {}
-    scene_listener: Dict[str, list] = {}
+    train_scenes: dict = {}
+    valid_scenes: dict = {}
+    scene_listener: dict[str, list] = {}
 
     logger.info("... training metadata")
     scene_id = 100000
diff --git a/recipes/cec1/baseline/enhance.py b/recipes/cec1/baseline/enhance.py
index 5ae0e339c..0ed7aa764 100644
--- a/recipes/cec1/baseline/enhance.py
+++ b/recipes/cec1/baseline/enhance.py
@@ -1,5 +1,5 @@
 import json
-import os
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -12,11 +12,11 @@
 
 @hydra.main(config_path=".", config_name="config")
 def enhance(cfg: DictConfig) -> None:
-    enhanced_folder = os.path.join(cfg["path"]["exp_folder"], "enhanced_signals")
-    os.makedirs(enhanced_folder, exist_ok=True)
-    with open(cfg["path"]["scenes_listeners_file"], "r", encoding="utf-8") as fp:
+    enhanced_folder = Path(cfg.path.exp_folder) / "enhanced_signals"
+    enhanced_folder.mkdir(parents=True, exist_ok=True)
+    with open(cfg.path.scenes_listeners_file, encoding="utf-8") as fp:
         scenes_listeners = json.load(fp)
-    with open(cfg["path"]["listeners_file"], "r", encoding="utf-8") as fp:
+    with open(cfg.path.listeners_file, encoding="utf-8") as fp:
         listener_audiograms = json.load(fp)
 
     enhancer = GHAHearingAid(**cfg["GHAHearingAid"])
@@ -35,11 +35,13 @@ def enhance(cfg: DictConfig) -> None:
                 cfs=cfs, levels_l=audiogram_left, levels_r=audiogram_right
             )
 
+            infile_names = [
+                f"{cfg.path.scenes_folder}/{scene}_mixed_CH{ch}.wav"
+                for ch in range(1, cfg["num_channels"] + 1)
+            ]
+
             enhancer.process_files(
-                infile_names=[
-                    f"{cfg['path']['scenes_folder']}/{scene}_mixed_CH{ch}.wav"
-                    for ch in range(1, cfg["num_channels"] + 1)
-                ],
+                infile_names=infile_names,
                 outfile_name=f"{enhanced_folder}/{scene}_{listener}_HA-output.wav",
                 audiogram=audiogram,
                 listener=listener,
diff --git a/recipes/cec1/baseline/evaluate.py b/recipes/cec1/baseline/evaluate.py
index 2394fe522..d73e2b468 100644
--- a/recipes/cec1/baseline/evaluate.py
+++ b/recipes/cec1/baseline/evaluate.py
@@ -1,7 +1,7 @@
 import csv
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -39,28 +39,24 @@ def listen(ear, signal, audiogram_l, audiogram_r):
 
 @hydra.main(config_path=".", config_name="config")
 def run_HL_processing(cfg: DictConfig) -> None:
-    output_path = os.path.join(cfg["path"]["exp_folder"], "eval_signals")
-    os.makedirs(output_path, exist_ok=True)
-    with open(cfg["path"]["scenes_listeners_file"], "r", encoding="utf-8") as fp:
+    output_path = Path(cfg.path.exp_folder) / "eval_signals"
+    output_path.mkdir(parents=True, exist_ok=True)
+    with open(cfg.path.scenes_listeners_file, encoding="utf-8") as fp:
         scenes_listeners = json.load(fp)
-    with open(cfg["path"]["listeners_file"], "r", encoding="utf-8") as fp:
+    with open(cfg.path.listeners_file, encoding="utf-8") as fp:
         listener_audiograms = json.load(fp)
-    enhanced_folder = cfg["path"]["enhanced_signals"]
+    enhanced_folder = Path(cfg.path.enhanced_signals)
 
     # initialize ear
     ear = Ear(**cfg["MSBGEar"])
 
     for scene in tqdm(scenes_listeners):
         for listener in scenes_listeners[scene]:
-            if os.path.exists(enhanced_folder):
-                signal_file = os.path.join(
-                    enhanced_folder, f"{scene}_{listener}_HA-output.wav"
-                )
+            if enhanced_folder.exists():
+                signal_file = enhanced_folder / f"{scene}_{listener}_HA-output.wav"
             # if no enhanced signals, use the unprocessed signal for si calculation
             else:
-                signal_file = os.path.join(
-                    cfg["path"]["scenes_folder"], f"{scene}_mixed_CH0.wav"
-                )
+                signal_file = Path(cfg.path.scenes_folder) / f"{scene}_mixed_CH0.wav"
 
             # signals to write
             outfile_stem = f"{output_path}/{scene}_{listener}"
@@ -71,12 +67,12 @@ def run_HL_processing(cfg: DictConfig) -> None:
                 f"{outfile_stem}_HL-mixoutput.wav",
             ]
             # if all signals to write exist, pass
-            if all(os.path.isfile(f) for f in signal_files_to_write):
+            if all(Path(f).exists() for f in signal_files_to_write):
                 continue
 
             signal = read_signal(signal_file)
             mixture_signal = read_signal(
-                os.path.join(cfg["path"]["scenes_folder"], f"{scene}_mixed_CH0.wav")
+                Path(cfg.path.scenes_folder) / f"{scene}_mixed_CH0.wav"
             )
 
             # retrieve audiograms
@@ -91,12 +87,12 @@ def run_HL_processing(cfg: DictConfig) -> None:
             right_audiogram = Audiogram(cfs=cfs, levels=audiogram_right)
 
             # Create discrete delta function (DDF) signal for time alignment
-            ddf_signal = np.zeros((np.shape(signal)))
+            ddf_signal = np.zeros(np.shape(signal))
             ddf_signal[:, 0] = unit_impulse(len(signal), int(MSBG_FS / 2))
             ddf_signal[:, 1] = unit_impulse(len(signal), int(MSBG_FS / 2))
 
             # Get flat-0dB ear audiograms
-            flat0dB_audiogram = Audiogram(cfs=cfs, levels=np.zeros((np.shape(cfs))))
+            flat0dB_audiogram = Audiogram(cfs=cfs, levels=np.zeros(np.shape(cfs)))
 
             signals_to_write = [
                 listen(ear, ddf_signal, flat0dB_audiogram, flat0dB_audiogram),
@@ -105,9 +101,9 @@ def run_HL_processing(cfg: DictConfig) -> None:
                 listen(ear, mixture_signal, left_audiogram, right_audiogram),
             ]
 
-            for signal, signal_file in zip(signals_to_write, signal_files_to_write):
+            for signal, out_signal_file in zip(signals_to_write, signal_files_to_write):
                 write_signal(
-                    signal_file,
+                    out_signal_file,
                     signal,
                     MSBG_FS,
                     floating_point=True,
@@ -116,60 +112,54 @@ def run_HL_processing(cfg: DictConfig) -> None:
 
 @hydra.main(config_path=".", config_name="config")
 def run_calculate_SI(cfg: DictConfig) -> None:
-    with open(cfg["path"]["scenes_listeners_file"], "r", encoding="utf-8") as fp:
+    with open(cfg.path.scenes_listeners_file, encoding="utf-8") as fp:
         scenes_listeners = json.load(fp)
-    proc_folder = os.path.join(cfg["path"]["exp_folder"], "eval_signals")
-    sii_file = os.path.join(cfg["path"]["exp_folder"], "sii.csv")
+    proc_folder = Path(cfg.path.exp_folder) / "eval_signals"
+    sii_file = Path(cfg.path.exp_folder) / "sii.csv"
     csv_lines = [["scene", "listener", "sii"]]
 
     for scene in tqdm(scenes_listeners):
         for listener in scenes_listeners[scene]:
             logging.info(f"Running SI calculation: scene {scene}, listener {listener}")
-            proc = read_signal(
-                os.path.join(proc_folder, f"{scene}_{listener}_HL-output.wav")
-            )
+            proc = read_signal(proc_folder / f"{scene}_{listener}_HL-output.wav")
             clean = read_signal(
-                os.path.join(
-                    cfg["path"]["scenes_folder"], f"{scene}_target_anechoic.wav"
-                )
-            )
-            ddf = read_signal(
-                os.path.join(proc_folder, f"{scene}_{listener}_HLddf-output.wav")
+                Path(cfg.path.scenes_folder) / f"{scene}_target_anechoic.wav"
             )
+            ddf = read_signal(proc_folder / f"{scene}_{listener}_HLddf-output.wav")
 
             # Calculate channel-specific unit impulse delay due to HL model
             # and audiograms
             delay = find_delay_impulse(ddf, initial_value=int(MSBG_FS / 2))
             if delay[0] != delay[1]:
                 logging.info(f"Difference in delay of {delay[0] - delay[1]}.")
-            maxdelay = int(np.max(delay))
+            max_delay = int(np.max(delay))
 
             # Allow for value lower than 1000 samples in case of unimpaired hearing
-            if maxdelay > 2000:
+            if max_delay > 2000:
                 logging.error("Error in delay calculation for signal time-alignment.")
 
             # Correct for delays by padding clean signals
-            cleanpad = np.zeros((len(clean) + maxdelay, 2))
-            procpad = np.zeros((len(clean) + maxdelay, 2))
+            clean_pad = np.zeros((len(clean) + max_delay, 2))
+            proc_pad = np.zeros((len(clean) + max_delay, 2))
 
-            if len(procpad) < len(proc):
+            if len(proc_pad) < len(proc):
                 raise ValueError("Padded processed signal is too short.")
 
-            cleanpad[int(delay[0]) : int(len(clean) + int(delay[0])), 0] = clean[:, 0]
-            cleanpad[int(delay[1]) : int(len(clean) + int(delay[1])), 1] = clean[:, 1]
-            procpad[: len(proc)] = proc
+            clean_pad[int(delay[0]) : int(len(clean) + int(delay[0])), 0] = clean[:, 0]
+            clean_pad[int(delay[1]) : int(len(clean) + int(delay[1])), 1] = clean[:, 1]
+            proc_pad[: len(proc)] = proc
 
             sii = mbstoi(
-                cleanpad[:, 0],
-                cleanpad[:, 1],
-                procpad[:, 0],
-                procpad[:, 1],
-                cfg["mbstoi"]["fs"],
-                cfg["mbstoi"]["gridcoarseness"],
+                clean_pad[:, 0],
+                clean_pad[:, 1],
+                proc_pad[:, 0],
+                proc_pad[:, 1],
+                cfg.mbstoi.fs,
+                cfg.mbstoi.gridcoarseness,
             )
             csv_lines.append([scene, listener, sii])  # type: ignore
 
-    with open(sii_file, "w", encoding="utf-8") as csv_f:
+    with sii_file.open("w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
diff --git a/recipes/cec1/data_preparation/prepare_cec1_data.py b/recipes/cec1/data_preparation/prepare_cec1_data.py
index 3fe6522bf..0e35f86b6 100644
--- a/recipes/cec1/data_preparation/prepare_cec1_data.py
+++ b/recipes/cec1/data_preparation/prepare_cec1_data.py
@@ -1,6 +1,6 @@
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 from omegaconf import DictConfig
@@ -9,7 +9,9 @@
 from clarity.data.scene_renderer_cec1 import Renderer, check_scene_exists
 
 
-def prepare_data(root_path, metafile_path, scene_folder, num_channels):
+def prepare_data(
+    root_path: str, metafile_path: str, scene_folder: str, num_channels: int
+):
     """
     Generate scene data given dataset (train or dev)
     Args:
@@ -18,10 +20,10 @@ def prepare_data(root_path, metafile_path, scene_folder, num_channels):
         scene_folder: folder containing generated scenes
         num_channels: number of channels
     """
-    with open(metafile_path, "r", encoding="utf-8") as f:
-        scenes = json.load(f)
+    with open(metafile_path, encoding="utf-8") as fp:
+        scenes = json.load(fp)
 
-    os.makedirs(scene_folder, exist_ok=True)
+    Path(scene_folder).mkdir(parents=True, exist_ok=True)
 
     renderer = Renderer(input_path=root_path, output_path=scene_folder, num_channels=3)
     for scene in tqdm(scenes):
@@ -46,10 +48,10 @@ def prepare_data(root_path, metafile_path, scene_folder, num_channels):
 def run(cfg: DictConfig) -> None:
     for dataset in cfg["datasets"]:
         prepare_data(
-            cfg["input_path"],
+            cfg.input_path,
             cfg["datasets"][dataset]["metafile_path"],
             cfg["datasets"][dataset]["scene_folder"],
-            cfg["num_channels"],
+            cfg.num_channels,
         )
 
 
diff --git a/recipes/cec1/e009_sheffield/train.py b/recipes/cec1/e009_sheffield/train.py
index 8734ff4da..175cd2887 100644
--- a/recipes/cec1/e009_sheffield/train.py
+++ b/recipes/cec1/e009_sheffield/train.py
@@ -1,6 +1,6 @@
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -77,8 +77,8 @@ def common_step(self, batch, batch_nb, train=True):
 
 
 def train_den(cfg, ear):
-    exp_dir = os.path.join(cfg.path.exp_folder, ear + "_den")
-    if os.path.exists(os.path.join(exp_dir, "best_model.pth")):
+    exp_dir = Path(cfg.path.exp_folder) / "{ear}_den"
+    if (exp_dir / "best_model.pth").exists():
         logger.info("Enhancement module exist")
         return
 
@@ -111,9 +111,9 @@ def train_den(cfg, ear):
 
     # callbacks
     callbacks = []
-    checkpoint_dir = os.path.join(exp_dir, "checkpoints/")
+    checkpoint_dir = exp_dir / "checkpoints/"
     checkpoint = ModelCheckpoint(
-        checkpoint_dir, monitor="val_loss", mode="min", save_top_k=5, verbose=True
+        str(checkpoint_dir), monitor="val_loss", mode="min", save_top_k=5, verbose=True
     )
     callbacks.append(checkpoint)
 
@@ -123,7 +123,7 @@ def train_den(cfg, ear):
     trainer = pl.Trainer(
         max_epochs=cfg.den_trainer.epochs,
         callbacks=callbacks,
-        default_root_dir=exp_dir,
+        default_root_dir=str(exp_dir),
         gpus=gpus,
         limit_train_batches=1.0,  # Useful for fast experiment
         gradient_clip_val=cfg.den_trainer.gradient_clip_val,
@@ -131,17 +131,17 @@ def train_den(cfg, ear):
     trainer.fit(den_module)
 
     best_k = {k: v.item() for k, v in checkpoint.best_k_models.items()}
-    with open(os.path.join(exp_dir, "best_k_models.json"), "w", encoding="utf-8") as fp:
+    with (exp_dir / "best_k_models.json").open("w", encoding="utf-8") as fp:
         json.dump(best_k, fp, indent=0)
     state_dict = torch.load(checkpoint.best_model_path)
     den_module.load_state_dict(state_dict=state_dict["state_dict"])
     den_module.cpu()
-    torch.save(den_module.model.state_dict(), os.path.join(exp_dir, "best_model.pth"))
+    torch.save(den_module.model.state_dict(), str(exp_dir / "best_model.pth"))
 
 
 def train_amp(cfg, ear):
-    exp_dir = os.path.join(cfg.path.exp_folder, ear + "_amp")
-    if os.path.exists(os.path.join(exp_dir, "best_model.pth")):
+    exp_dir = Path(cfg.path.exp_folder) / "{ear}_amp"
+    if (exp_dir / "best_model.pth").exists():
         logger.info("Amplification module exist")
         return
 
@@ -152,9 +152,8 @@ def train_amp(cfg, ear):
 
     # load denoising module
     den_model = ConvTasNet(**cfg.mc_conv_tasnet)
-    den_model_path = os.path.join(
-        os.path.join(cfg.path.exp_folder, ear + "_den"), "best_model.pth"
-    )
+    den_model_path = exp_dir / "{ear}_den/best_model.pth"
+
     den_model.load_state_dict(torch.load(den_model_path))
 
     # amplification module
@@ -187,7 +186,7 @@ def train_amp(cfg, ear):
         )
 
     # build normal hearing and hearing loss ears
-    with open(cfg.listener.metafile, "r", encoding="utf-8") as fp:
+    with open(cfg.listener.metafile, encoding="utf-8") as fp:
         listeners_file = json.load(fp)
         audiogram_cfs = listeners_file[cfg.listener.id]["audiogram_cfs"]
         audiogram_lvl_l = listeners_file[cfg.listener.id]["audiogram_levels_l"]
@@ -203,9 +202,9 @@ def train_amp(cfg, ear):
 
     # callbacks
     callbacks = []
-    checkpoint_dir = os.path.join(exp_dir, "checkpoints/")
+    checkpoint_dir = exp_dir / "checkpoints/"
     checkpoint = ModelCheckpoint(
-        checkpoint_dir, monitor="val_loss", mode="min", save_top_k=5, verbose=True
+        str(checkpoint_dir), monitor="val_loss", mode="min", save_top_k=5, verbose=True
     )
     callbacks.append(checkpoint)
 
@@ -223,12 +222,12 @@ def train_amp(cfg, ear):
     trainer.fit(amp_module)
 
     best_k = {k: v.item() for k, v in checkpoint.best_k_models.items()}
-    with open(os.path.join(exp_dir, "best_k_models.json"), "w", encoding="utf-8") as fp:
+    with (exp_dir / "best_k_models.json").open("w", encoding="utf-8") as fp:
         json.dump(best_k, fp, indent=0)
     state_dict = torch.load(checkpoint.best_model_path)
     amp_module.load_state_dict(state_dict=state_dict["state_dict"])
     amp_module.cpu()
-    torch.save(amp_module.model.state_dict(), os.path.join(exp_dir, "best_model.pth"))
+    torch.save(amp_module.model.state_dict(), str(exp_dir / "best_model.pth"))
 
 
 @hydra.main(config_path=".", config_name="config")
diff --git a/recipes/cec2/baseline/data_generation/build_additional_scenes.py b/recipes/cec2/baseline/data_generation/build_additional_scenes.py
index ba8793560..e37889bee 100644
--- a/recipes/cec2/baseline/data_generation/build_additional_scenes.py
+++ b/recipes/cec2/baseline/data_generation/build_additional_scenes.py
@@ -1,5 +1,5 @@
 import logging
-import os
+from pathlib import Path
 
 import hydra
 from omegaconf import DictConfig
@@ -10,15 +10,15 @@
 
 
 def instantiate_scenes(cfg):
-    rb = RoomBuilder()
+    room_builder = RoomBuilder()
     set_random_seed(cfg.random_seed)
-    room_file = os.path.join(cfg.path.metadata_dir, "rooms.train.json")
+    room_file = Path(cfg.path.metadata_dir) / "rooms.train.json"
     for dataset in cfg.scene_datasets:
-        if not os.path.exists(cfg.path.additional_data_file):
+        if not Path(cfg.path.additional_data_file).exists():
             logger.info(f"instantiate scenes for {dataset} set")
-            rb.load(room_file)
-            sb = SceneBuilder(
-                rb=rb,
+            room_builder.load(str(room_file))
+            scene_builder = SceneBuilder(
+                rb=room_builder,
                 scene_datasets=cfg.scene_datasets[dataset],
                 target=cfg.target,
                 interferer=cfg.interferer,
@@ -26,8 +26,8 @@ def instantiate_scenes(cfg):
                 listener=cfg.listener,
                 shuffle_rooms=cfg.shuffle_rooms,
             )
-            sb.instantiate_scenes(dataset=dataset)
-            sb.save_scenes(cfg.path.additional_data_file)
+            scene_builder.instantiate_scenes(dataset=dataset)
+            scene_builder.save_scenes(cfg.path.additional_data_file)
         else:
             logger.info(f"scenes.{dataset}.json has existed, skip")
 
diff --git a/recipes/cec2/baseline/data_generation/render_additional_scenes.py b/recipes/cec2/baseline/data_generation/render_additional_scenes.py
index b409141fa..4788736b6 100644
--- a/recipes/cec2/baseline/data_generation/render_additional_scenes.py
+++ b/recipes/cec2/baseline/data_generation/render_additional_scenes.py
@@ -13,7 +13,7 @@ def render_scenes(cfg):
     for dataset in cfg.scene_renderer:
         logger.info(f"Beginning scene generation for {dataset} set...")
         file_path = cfg.scene_renderer[dataset].metadata.scene_definitions
-        with open(file_path, "r", encoding="utf-8") as fp:
+        with open(file_path, encoding="utf-8") as fp:
             scenes = json.load(fp)
 
         starting_scene = (
diff --git a/recipes/cec2/baseline/enhance.py b/recipes/cec2/baseline/enhance.py
index 36da45bfc..14883f28b 100644
--- a/recipes/cec2/baseline/enhance.py
+++ b/recipes/cec2/baseline/enhance.py
@@ -1,6 +1,6 @@
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -16,11 +16,11 @@
 
 @hydra.main(config_path=".", config_name="config")
 def enhance(cfg: DictConfig) -> None:
-    enhanced_folder = os.path.join(cfg.path.exp_folder, "enhanced_signals")
-    os.makedirs(enhanced_folder, exist_ok=True)
-    with open(cfg.path.scenes_listeners_file, "r", encoding="utf-8") as fp:
+    enhanced_folder = Path(cfg.path.exp_folder) / "enhanced_signals"
+    enhanced_folder.mkdir(parents=True, exist_ok=True)
+    with Path(cfg.path.scenes_listeners_file).open("r", encoding="utf-8") as fp:
         scenes_listeners = json.load(fp)
-    with open(cfg.path.listeners_file, "r", encoding="utf-8") as fp:
+    with Path(cfg.path.listeners_file).open("r", encoding="utf-8") as fp:
         listener_audiograms = json.load(fp)
 
     enhancer = NALR(**cfg.nalr)
@@ -38,7 +38,7 @@ def enhance(cfg: DictConfig) -> None:
             )
 
             fs, signal = wavfile.read(
-                os.path.join(cfg.path.scenes_folder, f"{scene}_mix_CH1.wav")
+                Path(cfg.path.scenes_folder) / f"{scene}_mix_CH1.wav"
             )
             signal = signal / 32768.0
             assert fs == cfg.nalr.fs
@@ -60,7 +60,7 @@ def enhance(cfg: DictConfig) -> None:
                 logger.warning(f"Writing {filename}: {n_clipped} samples clipped")
             np.clip(enhanced, -1.0, 1.0, out=enhanced)
             signal_16 = (32768.0 * enhanced).astype(np.int16)
-            wavfile.write(os.path.join(enhanced_folder, filename), fs, signal_16)
+            wavfile.write(enhanced_folder / filename, fs, signal_16)
 
 
 # pylint: disable=no-value-for-parameter
diff --git a/recipes/cec2/baseline/evaluate.py b/recipes/cec2/baseline/evaluate.py
index e22a9df7b..1cd50f8f4 100644
--- a/recipes/cec2/baseline/evaluate.py
+++ b/recipes/cec2/baseline/evaluate.py
@@ -2,7 +2,7 @@
 import hashlib
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -15,10 +15,10 @@
 logger = logging.getLogger(__name__)
 
 
-def read_csv_scores(file):
+def read_csv_scores(file: Path):
     score_dict = {}
-    with open(file, "r", encoding="utf-8") as f:
-        reader = csv.reader(f)
+    with file.open("r", encoding="utf-8") as fp:
+        reader = csv.reader(fp)
         _ = next(reader)
         for row in reader:
             score_dict[row[0] + "_" + row[1]] = float(row[2])
@@ -27,18 +27,18 @@ def read_csv_scores(file):
 
 @hydra.main(config_path=".", config_name="config")
 def run_calculate_SI(cfg: DictConfig) -> None:
-    with open(cfg.path.scenes_listeners_file, "r", encoding="utf-8") as fp:
+    with Path(cfg.path.scenes_listeners_file).open("r", encoding="utf-8") as fp:
         scenes_listeners = json.load(fp)
-    with open(cfg.path.listeners_file, "r", encoding="utf-8") as fp:
+    with Path(cfg.path.listeners_file).open("r", encoding="utf-8") as fp:
         listener_audiograms = json.load(fp)
-    os.makedirs(cfg.path.exp_folder, exist_ok=True)
+    Path(cfg.path.exp_folder).mkdir(parents=True, exist_ok=True)
 
-    enhanced_folder = os.path.join(cfg.path.exp_folder, "enhanced_signals")
+    enhanced_folder = Path(cfg.path.exp_folder) / "enhanced_signals"
 
     if cfg.evaluate.cal_unprocessed_si:
-        unproc_si_file = os.path.join(cfg.path.exp_folder, "si_unproc.csv")
+        unproc_si_file = Path(cfg.path.exp_folder) / "si_unproc.csv"
         unproc_csv_lines = [["scene", "listener", "haspi"]]
-        if os.path.exists(unproc_si_file):
+        if unproc_si_file.exists():
             score_dict = read_csv_scores(unproc_si_file)
             ave_score = np.mean(list(score_dict.values()))
             logger.info(
@@ -47,9 +47,9 @@ def run_calculate_SI(cfg: DictConfig) -> None:
                 ave_score,
             )
 
-    si_file = os.path.join(cfg.path.exp_folder, "si.csv")
+    si_file = Path(cfg.path.exp_folder) / "si.csv"
     csv_lines = [["scene", "listener", "haspi"]]
-    if os.path.exists(si_file):
+    if si_file.exists():
         score_dict = read_csv_scores(si_file)
         ave_score = np.mean(list(score_dict.values()))
         logger.info("si.csv exists, and the average HASPI scores is %4f", ave_score)
@@ -75,15 +75,15 @@ def run_calculate_SI(cfg: DictConfig) -> None:
 
             # retrieve signals
             fs_proc, proc = wavfile.read(
-                os.path.join(enhanced_folder, f"{scene}_{listener}_HA-output.wav")
+                enhanced_folder / f"{scene}_{listener}_HA-output.wav"
             )
 
             fs_ref_anechoic, ref_anechoic = wavfile.read(
-                os.path.join(cfg.path.scenes_folder, f"{scene}_target_anechoic_CH1.wav")
+                Path(cfg.path.scenes_folder) / f"{scene}_target_anechoic_CH1.wav"
             )
 
             fs_ref_target, ref_target = wavfile.read(
-                os.path.join(cfg.path.scenes_folder, f"{scene}_target_CH1.wav")
+                Path(cfg.path.scenes_folder) / f"{scene}_target_CH1.wav"
             )
 
             assert fs_ref_anechoic == fs_ref_target == fs_proc
@@ -117,7 +117,7 @@ def run_calculate_SI(cfg: DictConfig) -> None:
                     np.random.seed(scene_md5)
 
                 _fs_unproc, unproc = wavfile.read(
-                    os.path.join(cfg.path.scenes_folder, f"{scene}_mix_CH1.wav")
+                    Path(cfg.path.scenes_folder) / f"{scene}_mix_CH1.wav"
                 )
                 unproc = unproc / 32768.0
                 si_unproc = haspi_v2_be(
@@ -133,7 +133,7 @@ def run_calculate_SI(cfg: DictConfig) -> None:
                 logger.info(f"The unprocessed signal HASPI score is {si_unproc}")
                 unproc_csv_lines.append([scene, listener, str(si_unproc)])
 
-    with open(si_file, "w", encoding="utf-8") as csv_f:
+    with si_file.open("w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
@@ -144,7 +144,7 @@ def run_calculate_SI(cfg: DictConfig) -> None:
     logger.info("si.csv exists, and the average HASPI scores is %.4f", ave_score)
 
     if cfg.evaluate.cal_unprocessed_si:
-        with open(unproc_si_file, "w", encoding="utf-8") as csv_f:
+        with unproc_si_file.open("w", encoding="utf-8") as csv_f:
             csv_writer = csv.writer(
                 csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
             )
diff --git a/recipes/cec2/data_preparation/build_scenes.py b/recipes/cec2/data_preparation/build_scenes.py
index 43559ed7e..09581959d 100644
--- a/recipes/cec2/data_preparation/build_scenes.py
+++ b/recipes/cec2/data_preparation/build_scenes.py
@@ -1,5 +1,5 @@
 import logging
-import os
+from pathlib import Path
 
 import hydra
 from omegaconf import DictConfig
@@ -10,27 +10,27 @@
 
 
 def build_rooms_from_rpf(cfg):
-    rb = RoomBuilder()
+    room_builder = RoomBuilder()
     for dataset in cfg.room_datasets:
-        room_file = os.path.join(cfg.path.metadata_dir, f"rooms.{dataset}.json")
-        if not os.path.exists(room_file):
-            rb.build_from_rpf(**cfg.room_datasets[dataset])
-            rb.save_rooms(room_file)
+        room_file = Path(cfg.path.metadata_dir) / f"rooms.{dataset}.json"
+        if not room_file.exists():
+            room_builder.build_from_rpf(**cfg.room_datasets[dataset])
+            room_builder.save_rooms(str(room_file))
         else:
             logger.info(f"rooms.{dataset}.json exists, skip")
 
 
 def instantiate_scenes(cfg):
-    rb = RoomBuilder()
+    room_builder = RoomBuilder()
     set_random_seed(cfg.random_seed)
     for dataset in cfg.scene_datasets:
-        scene_file = os.path.join(cfg.path.metadata_dir, f"scenes.{dataset}.json")
-        if not os.path.exists(scene_file):
+        scene_file = Path(cfg.path.metadata_dir) / f"scenes.{dataset}.json"
+        if not scene_file.exists():
             logger.info(f"instantiate scenes for {dataset} set")
-            room_file = os.path.join(cfg.path.metadata_dir, f"rooms.{dataset}.json")
-            rb.load(room_file)
-            sb = SceneBuilder(
-                rb=rb,
+            room_file = Path(cfg.path.metadata_dir) / f"rooms.{dataset}.json"
+            room_builder.load(str(room_file))
+            scene_builder = SceneBuilder(
+                rb=room_builder,
                 scene_datasets=cfg.scene_datasets[dataset],
                 target=cfg.target,
                 interferer=cfg.interferer,
@@ -38,8 +38,8 @@ def instantiate_scenes(cfg):
                 listener=cfg.listener,
                 shuffle_rooms=cfg.shuffle_rooms,
             )
-            sb.instantiate_scenes(dataset=dataset)
-            sb.save_scenes(scene_file)
+            scene_builder.instantiate_scenes(dataset=dataset)
+            scene_builder.save_scenes(str(scene_file))
         else:
             logger.info(f"scenes.{dataset}.json exists, skip")
 
diff --git a/recipes/cec2/data_preparation/render_scenes.py b/recipes/cec2/data_preparation/render_scenes.py
index b213a5401..faee13442 100644
--- a/recipes/cec2/data_preparation/render_scenes.py
+++ b/recipes/cec2/data_preparation/render_scenes.py
@@ -14,7 +14,6 @@ def render_scenes(cfg):
         logger.info(f"Beginning scene generation for {dataset} set...")
         with open(
             cfg.scene_renderer[dataset].metadata.scene_definitions,
-            "r",
             encoding="utf-8",
         ) as fp:
             scenes = json.load(fp)
diff --git a/recipes/cpc1/baseline/compute_scores.py b/recipes/cpc1/baseline/compute_scores.py
index 8bf76b630..5d8a7b61f 100644
--- a/recipes/cpc1/baseline/compute_scores.py
+++ b/recipes/cpc1/baseline/compute_scores.py
@@ -1,6 +1,6 @@
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -69,7 +69,7 @@ def compute_scores(predictions, labels):
     }
 
 
-def read_data(pred_csv, label_json):
+def read_data(pred_csv: Path, label_json: Path):
     df_pred = pd.read_csv(pred_csv).rename(
         columns={"signal_ID": "signal", "intelligibility_score": "prediction"}
     )
@@ -85,12 +85,12 @@ def read_data(pred_csv, label_json):
 def run(cfg: DictConfig) -> None:
     logger.info("Run evaluation on the closed set.")
     data_tr = read_data(
-        pred_csv=os.path.join(cfg.train_path.exp_folder, "sii.csv"),
-        label_json=cfg.train_path.scenes_file,
+        pred_csv=Path(cfg.train_path.exp_folder) / "sii.csv",
+        label_json=Path(cfg.train_path.scenes_file),
     )
     data_eval = read_data(
-        pred_csv=os.path.join(cfg.test_path.exp_folder, "sii.csv"),
-        label_json="../test_listener_responses/CPC1.test.json",
+        pred_csv=Path(cfg.test_path.exp_folder) / "sii.csv",
+        label_json=Path("../test_listener_responses/CPC1.test.json"),
     )
     logger.info("Apply logistic fitting.")
     model = Model()
@@ -100,12 +100,12 @@ def run(cfg: DictConfig) -> None:
 
     logger.info("Run evaluation on the open set.")
     data_tr = read_data(
-        pred_csv=os.path.join(cfg.train_indep_path.exp_folder, "sii.csv"),
-        label_json=cfg.train_indep_path.scenes_file,
+        pred_csv=Path(cfg.train_indep_path.exp_folder) / "sii.csv",
+        label_json=Path(cfg.train_indep_path.scenes_file),
     )
     data_eval = read_data(
-        pred_csv=os.path.join(cfg.test_indep_path.exp_folder, "sii.csv"),
-        label_json="../test_listener_responses/CPC1.test_indep.json",
+        pred_csv=Path(cfg.test_indep_path.exp_folder) / "sii.csv",
+        label_json=Path("../test_listener_responses/CPC1.test_indep.json"),
     )
     logger.info("Apply logistic fitting.")
     model = Model()
diff --git a/recipes/cpc1/baseline/run.py b/recipes/cpc1/baseline/run.py
index 1ff44edb2..01477dbef 100644
--- a/recipes/cpc1/baseline/run.py
+++ b/recipes/cpc1/baseline/run.py
@@ -1,7 +1,7 @@
 import csv
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -40,13 +40,13 @@ def listen(ear, signal, audiogram_l, audiogram_r):
 
 
 def run_HL_processing(cfg, path):
-    output_path = os.path.join(path.exp_folder, "eval_signals")
-    os.makedirs(output_path, exist_ok=True)
-    with open(path.scenes_file, "r", encoding="utf-8") as fp:
+    output_path = Path(path.exp_folder) / "eval_signals"
+    output_path.mkdir(parents=True, exist_ok=True)
+    with open(path.scenes_file, encoding="utf-8") as fp:
         scenes = json.load(fp)
-    with open(path.listeners_file, "r", encoding="utf-8") as fp:
+    with open(path.listeners_file, encoding="utf-8") as fp:
         listener_audiograms = json.load(fp)
-    enhanced_folder = path.scenes_folder
+    enhanced_folder = Path(path.scenes_folder)
 
     # initialize ear
     ear = Ear(**cfg["MSBGEar"])
@@ -55,7 +55,7 @@ def run_HL_processing(cfg, path):
         scene = scene_dict["scene"]
         listener = scene_dict["listener"]
         system = scene_dict["system"]
-        signal_file = os.path.join(enhanced_folder, f"{scene}_{listener}_{system}.wav")
+        signal_file = enhanced_folder / f"{scene}_{listener}_{system}.wav"
 
         # signals to write
         outfile_stem = f"{output_path}/{scene}_{listener}_{system}"
@@ -66,13 +66,11 @@ def run_HL_processing(cfg, path):
             f"{outfile_stem}_HL-mixoutput.wav",
         ]
         # if all signals to write exist, pass
-        if all(os.path.isfile(f) for f in signal_files_to_write):
+        if all(Path(f).exists() for f in signal_files_to_write):
             continue
 
         signal = read_signal(signal_file)
-        mixture_signal = read_signal(
-            os.path.join(path.ref_folder, f"{scene}_mixed_CH0.wav")
-        )
+        mixture_signal = read_signal(Path(path.ref_folder) / f"{scene}_mixed_CH0.wav")
 
         # retrieve audiograms
         cfs = np.array(listener_audiograms[listener]["audiogram_cfs"])
@@ -82,12 +80,12 @@ def run_HL_processing(cfg, path):
         right_audiogram = Audiogram(cfs=cfs, levels=audiogram_right)
 
         # Create discrete delta function (DDF) signal for time alignment
-        ddf_signal = np.zeros((np.shape(signal)))
+        ddf_signal = np.zeros(np.shape(signal))
         ddf_signal[:, 0] = unit_impulse(len(signal), int(MSBG_FS / 2))
         ddf_signal[:, 1] = unit_impulse(len(signal), int(MSBG_FS / 2))
 
         # Get flat-0dB ear audiograms
-        flat0dB_audiogram = Audiogram(cfs=cfs, levels=np.zeros((np.shape(cfs))))
+        flat0dB_audiogram = Audiogram(cfs=cfs, levels=np.zeros(np.shape(cfs)))
 
         signals_to_write = [
             listen(ear, ddf_signal, flat0dB_audiogram, flat0dB_audiogram),
@@ -105,25 +103,20 @@ def run_HL_processing(cfg, path):
 
 
 def run_calculate_SI(cfg, path) -> None:
-    with open(path.scenes_file, "r", encoding="utf-8") as fp:
+    with open(path.scenes_file, encoding="utf-8") as fp:
         scenes = json.load(fp)
-    proc_folder = os.path.join(path.exp_folder, "eval_signals")
-    sii_file = os.path.join(path.exp_folder, "sii.csv")
+    proc_folder = Path(path.exp_folder) / "eval_signals"
+    ref_folder = Path(path.ref_folder)
+    sii_file = Path(path.exp_folder) / "sii.csv"
     csv_lines = [["signal_ID", "intelligibility_score"]]
 
     for scene_dict in tqdm(scenes):
         scene = scene_dict["scene"]
         listener = scene_dict["listener"]
         system = scene_dict["system"]
-        proc = read_signal(
-            os.path.join(proc_folder, f"{scene}_{listener}_{system}_HL-output.wav")
-        )
-        clean = read_signal(
-            os.path.join(path.ref_folder, f"{scene}_target_anechoic.wav")
-        )
-        ddf = read_signal(
-            os.path.join(proc_folder, f"{scene}_{listener}_{system}_HLddf-output.wav")
-        )
+        proc = read_signal(proc_folder / f"{scene}_{listener}_{system}_HL-output.wav")
+        clean = read_signal(ref_folder / f"{scene}_target_anechoic.wav")
+        ddf = read_signal(proc_folder / f"{scene}_{listener}_{system}_HLddf-output.wav")
 
         # Calculate channel-specific unit impulse delay due to HL model and audiograms
         delay = find_delay_impulse(ddf, initial_value=int(MSBG_FS / 2))
diff --git a/recipes/cpc1/e029_sheffield/evaluate.py b/recipes/cpc1/e029_sheffield/evaluate.py
index abb3323c7..fbfb60198 100644
--- a/recipes/cpc1/e029_sheffield/evaluate.py
+++ b/recipes/cpc1/e029_sheffield/evaluate.py
@@ -1,6 +1,6 @@
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -68,22 +68,18 @@ def compute_scores(predictions, labels):
     }
 
 
-def read_data(pred_json, label_json):
-    prediction = []
-    label = []
-
+def read_data(pred_json: Path, label_json: Path):
     # read label_json to dict
-    label_dict = {}
-    with open(label_json, "r", encoding="utf-8") as fp:
-        label_json = json.load(fp)
-    for item in label_json:
-        label_dict[item["signal"]] = item["correctness"] / 100.0
+    with label_json.open("r", encoding="utf-8") as fp:
+        labels = json.load(fp)
+
+    label_dict = {item["signal"]: item["correctness"] for item in labels}
 
-    with open(pred_json, "r", encoding="utf-8") as fp:
+    with pred_json.open("r", encoding="utf-8") as fp:
         pred_dict = json.load(fp)
-    for signal, pred in pred_dict.items():
-        prediction.append(pred * 100.0)
-        label.append(label_dict[signal])
+
+    prediction = [pred * 100.0 for pred in pred_dict.values()]
+    label = [label_dict[signal] for signal in pred_dict]
 
     return np.array(prediction), np.array(label)
 
@@ -99,12 +95,12 @@ def run(cfg: DictConfig) -> None:
 
     # encoder representation evaluation
     prediction_dev, label_dev = read_data(
-        os.path.join(cfg.path.exp_folder, "dev_conf.json"),
-        os.path.join(cfg.path.cpc1_train_data, f"metadata/CPC1.{'train'+track}.json"),
+        Path(cfg.path.exp_folder) / "dev_conf.json",
+        Path(cfg.path.cpc1_train_data) / f"metadata/CPC1.{'train'+track}.json",
     )
     prediction_test, label_test = read_data(
-        os.path.join(cfg.path.exp_folder, "test_conf.json"),
-        f"../test_listener_responses/CPC1.{'test'+track}.json",
+        Path(cfg.path.exp_folder) / "test_conf.json",
+        Path(f"../test_listener_responses/CPC1.{'test'+track}.json"),
     )
 
     logger.info("Apply logistic fitting.")
@@ -115,12 +111,12 @@ def run(cfg: DictConfig) -> None:
 
     # decoder representation evaluation
     prediction_dev, label_dev = read_data(
-        os.path.join(cfg.path.exp_folder, "dev_negent.json"),
-        os.path.join(cfg.path.cpc1_train_data, f"metadata/CPC1.{'train'+track}.json"),
+        Path(cfg.path.exp_folder) / "dev_negent.json",
+        Path(cfg.path.cpc1_train_data) / f"metadata/CPC1.{'train'+track}.json",
     )
     prediction_test, label_test = read_data(
-        os.path.join(cfg.path.exp_folder, "test_negent.json"),
-        f"../test_listener_responses/CPC1.{'test'+track}.json",
+        Path(cfg.path.exp_folder) / "test_negent.json",
+        Path(f"../test_listener_responses/CPC1.{'test'+track}.json"),
     )
 
     logger.info("Apply logistic fitting.")
@@ -129,9 +125,8 @@ def run(cfg: DictConfig) -> None:
     fit_pred = model.predict(prediction_test)
     negent_scores = compute_scores(fit_pred * 100, label_test * 100)
 
-    with open(
-        os.path.join(cfg.path.exp_folder, "results.json"), "w", encoding="utf-8"
-    ) as fp:
+    results_file = Path(cfg.path.exp_folder) / "results.json"
+    with results_file.open("w", encoding="utf-8") as fp:
         json.dump(
             {
                 "confidence_results": conf_scores,
diff --git a/recipes/cpc1/e029_sheffield/infer.py b/recipes/cpc1/e029_sheffield/infer.py
index 5832be503..ab97fb4d3 100644
--- a/recipes/cpc1/e029_sheffield/infer.py
+++ b/recipes/cpc1/e029_sheffield/infer.py
@@ -1,7 +1,7 @@
 import copy
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 import speechbrain as sb
@@ -94,7 +94,7 @@ def init_evaluation(self, max_key=None, min_key=None):
 
 def init_asr(asr_config):
     hparams_file, run_opts, overrides = sb.parse_arguments([asr_config])
-    with open(hparams_file, "r", encoding="utf-8") as fp:
+    with open(hparams_file, encoding="utf-8") as fp:
         hparams = load_hyperpyyaml(fp, overrides)
 
     tokenizer = hparams["tokenizer"]
@@ -148,56 +148,51 @@ def run(cfg: DictConfig) -> None:
         track = ""
     else:
         logger.error("cpc1_track has to be closed or open")
+        raise ValueError("cpc1_track has to be closed or open")
 
     asr_model, tokenizer, bos_index = init_asr(cfg.asr_config)
 
     left_dev_csv = sb.dataio.dataio.load_data_csv(
-        os.path.join(cfg.path.exp_folder, "cpc1_asr_data" + track, "left_dev_msbg.csv")
+        Path(cfg.path.exp_folder) / f"cpc1_asr_data{track}/left_dev_msbg.csv"
     )  # using left ear csvfile for data loading
     left_test_csv = sb.dataio.dataio.load_data_csv(
-        os.path.join(cfg.path.exp_folder, "cpc1_asr_data" + track, "left_test_msbg.csv")
+        Path(cfg.path.exp_folder) / f"cpc1_asr_data{track}/left_test_msbg.csv"
     )  # using left ear csvfile for data loading
 
-    dev_conf = {}
-    dev_negent = {}
+    exp_path = Path(cfg.path.exp_folder)
+
     # dev set uncertainty
+    dev_conf = {}
+    dev_neg_entropy = {}
     for wav_id, wav_obj in tqdm(left_dev_csv.items()):
         left_proc_path = wav_obj["wav"]
         uncertainty = compute_uncertainty(
             left_proc_path, asr_model, bos_index, tokenizer
         )
         dev_conf[wav_id] = uncertainty[0].tolist()
-        dev_negent[wav_id] = uncertainty[1].tolist()
+        dev_neg_entropy[wav_id] = uncertainty[1].tolist()
 
-        with open(
-            os.path.join(cfg.path.exp_folder, "dev_conf.json"), "w", encoding="utf-8"
-        ) as fp:
+        with (exp_path / "dev_conf.json").open("w", encoding="utf-8") as fp:
             json.dump(dev_conf, fp)
-        with open(
-            os.path.join(cfg.path.exp_folder, "dev_negent.json"), "w", encoding="utf-8"
-        ) as fp:
-            json.dump(dev_negent, fp)
+        with (exp_path / "dev_negent.json").open("w", encoding="utf-8") as fp:
+            json.dump(dev_neg_entropy, fp)
 
     # test set similarity
     test_conf = {}
-    test_negent = {}
+    test_neg_entropy = {}
     for wav_id, wav_obj in tqdm(left_test_csv.items()):
         left_proc_path = wav_obj["wav"]
         uncertainty = compute_uncertainty(
             left_proc_path, asr_model, bos_index, tokenizer
         )
         test_conf[wav_id] = uncertainty[0].tolist()
-        test_negent[wav_id] = uncertainty[1].tolist()
+        test_neg_entropy[wav_id] = uncertainty[1].tolist()
 
-        with open(
-            os.path.join(cfg.path.exp_folder, "test_conf.json"), "w", encoding="utf-8"
-        ) as fp:
+        with (exp_path / "test_conf.json").open("w", encoding="utf-8") as fp:
             json.dump(test_conf, fp)
 
-        with open(
-            os.path.join(cfg.path.exp_folder, "test_negent.json"), "w", encoding="utf-8"
-        ) as fp:
-            json.dump(test_negent, fp)
+        with (exp_path / "test_negent.json").open("w", encoding="utf-8") as fp:
+            json.dump(test_neg_entropy, fp)
 
 
 # pylint: disable=no-value-for-parameter
diff --git a/recipes/cpc1/e029_sheffield/prepare_data.py b/recipes/cpc1/e029_sheffield/prepare_data.py
index 34e5db155..aeb39065c 100644
--- a/recipes/cpc1/e029_sheffield/prepare_data.py
+++ b/recipes/cpc1/e029_sheffield/prepare_data.py
@@ -2,8 +2,8 @@
 import csv
 import json
 import logging
-import os
 import random
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -22,23 +22,21 @@
 
 
 def run_data_split(cfg, track):
-    os.makedirs(os.path.join(cfg.path.exp_folder, "data_split"), exist_ok=True)
-    scene_train_json = os.path.join(
-        cfg.path.exp_folder, "data_split", f"scene_train_list{track}.json"
-    )
-    scene_dev_json = os.path.join(
-        cfg.path.exp_folder, "data_split", f"scene_dev_list{track}.json"
-    )
-    if os.path.isfile(scene_train_json) and os.path.isfile(scene_dev_json):
+    data_split_dir = Path(cfg.path.exp_folder) / "data_split"
+    data_split_dir.mkdir(parents=True, exist_ok=True)
+    scene_train_json = data_split_dir / f"scene_train_list{track}.json"
+    scene_dev_json = data_split_dir / f"scene_dev_list{track}.json"
+
+    if scene_train_json.exists() and scene_dev_json.exists():
         logger.info("Train set and dev set lists exist...")
         return
 
-    file_path = os.path.join(
-        cfg.path.root,
-        "clarity_CPC1_data_train/metadata",
-        f"CPC1.{'train'+track}.json",
+    file_path = (
+        Path(cfg.path.root)
+        / f"clarity_CPC1_data_train/metadata/CPC1.{'train'+track}.json"
     )
-    with open(file_path, "r", encoding="utf-8") as fp:
+
+    with file_path.open("r", encoding="utf-8") as fp:
         scenes_dict = json.load(fp)
     scene_list = []
     for item in scenes_dict:
@@ -49,9 +47,9 @@ def run_data_split(cfg, track):
     )
     scene_train_list = list(set(scene_list) - set(scene_dev_list))
 
-    with open(scene_train_json, "w", encoding="utf-8") as fp:
+    with scene_train_json.open("w", encoding="utf-8") as fp:
         json.dump(scene_train_list, fp)
-    with open(scene_dev_json, "w", encoding="utf-8") as fp:
+    with scene_dev_json.open("w", encoding="utf-8") as fp:
         json.dump(scene_dev_list, fp)
 
 
@@ -79,22 +77,18 @@ def listen(ear, signal, audiogram_l, audiogram_r):
 def run_msbg_simulation(cfg, track):
     for split in ["train", "test"]:
         dataset = split + track
-        dataset_folder = os.path.join(cfg.path.root, "clarity_CPC1_data_" + split)
-        output_path = os.path.join(dataset_folder, "clarity_data/HA_outputs", dataset)
-        file_path = os.path.join(dataset_folder, "metadata", f"CPC1.{dataset}.json")
-        with open(file_path, "r", encoding="utf-8") as fp:
+        dataset_folder = Path(cfg.path.root) / f"clarity_CPC1_data_{split}"
+        output_path = dataset_folder / "clarity_data/HA_outputs" / dataset
+        file_path = dataset_folder / f"metadata/CPC1.{dataset}.json"
+        with file_path.open("r", encoding="utf-8") as fp:
             scenes = json.load(fp)
         if split == "train":
-            file_path = os.path.join(
-                dataset_folder, "metadata", "listeners.CPC1_train.json"
-            )
-            with open(file_path, "r", encoding="utf-8") as fp:
+            file_path = dataset_folder / "metadata/listeners.CPC1_train.json"
+            with file_path.open("r", encoding="utf-8") as fp:
                 listener_audiograms = json.load(fp)
         else:
-            file_path = os.path.join(
-                dataset_folder, "metadata", "listeners.CPC1_all.json"
-            )
-            with open(file_path, "r", encoding="utf-8") as fp:
+            file_path = Path(dataset_folder) / "metadata/listeners.CPC1_all.json"
+            with file_path.open("r", encoding="utf-8") as fp:
                 listener_audiograms = json.load(fp)
 
         # initialize ear
@@ -104,15 +98,15 @@ def run_msbg_simulation(cfg, track):
             scene = scene_dict["scene"]
             listener = scene_dict["listener"]
             system = scene_dict["system"]
-            signal_file = os.path.join(output_path, f"{scene}_{listener}_{system}.wav")
+            signal_file = output_path / f"{scene}_{listener}_{system}.wav"
 
             # signals to write
-            outfile_stem = f"{output_path}/{scene}_{listener}_{system}"
+            outfile_stem = output_path / f"{scene}_{listener}_{system}"
             signal_files_to_write = [
-                f"{outfile_stem}_HL-output.wav",
+                Path(f"{outfile_stem}_HL-output.wav"),
             ]
             # if all signals to write exist, pass
-            if all(os.path.isfile(f) for f in signal_files_to_write):
+            if all(f.exists() for f in signal_files_to_write):
                 continue
             signal = read_signal(signal_file)
 
@@ -141,68 +135,56 @@ def run_msbg_simulation(cfg, track):
 
 
 def generate_data_split(
-    orig_data_json,
-    orig_signal_folder,
-    target_data_folder,
+    orig_data_json: Path,
+    orig_signal_folder: Path,
+    target_data_folder: Path,
     data_split,
     split_data_list,
     if_msbg=False,
     if_ref=False,
 ):
-    with open(orig_data_json, "r", encoding="utf-8") as fp:
+    with orig_data_json.open("r", encoding="utf-8") as fp:
         all_data_list = json.load(fp)
 
-    left_tgt_signal_folder = os.path.join(
-        target_data_folder, orig_signal_folder.split("/")[-2] + "_left"
+    ext = ""
+    if if_msbg:
+        ext = "_msbg"
+    elif if_ref:
+        ext = "_ref"
+
+    left_tgt_signal_folder = target_data_folder / (
+        str(orig_signal_folder).split("/")[-2] + "_left" + ext
     )
-    right_tgt_signal_folder = os.path.join(
-        target_data_folder, orig_signal_folder.split("/")[-2] + "_right"
+
+    right_tgt_signal_folder = target_data_folder / (
+        str(orig_signal_folder).split("/")[-2] + "_right" + ext
     )
-    if if_msbg:
-        left_tgt_signal_folder = left_tgt_signal_folder + "_msbg"
-        right_tgt_signal_folder = right_tgt_signal_folder + "_msbg"
-    if if_ref:
-        left_tgt_signal_folder = left_tgt_signal_folder + "_ref"
-        right_tgt_signal_folder = right_tgt_signal_folder + "_ref"
-    os.makedirs(left_tgt_signal_folder, exist_ok=True)
-    os.makedirs(right_tgt_signal_folder, exist_ok=True)
+
+    left_tgt_signal_folder.mkdir(parents=True, exist_ok=True)
+    right_tgt_signal_folder.mkdir(parents=True, exist_ok=True)
 
     csv_lines_left = [["ID", "duration", "wav", "spk_id", "wrd"]]
     csv_lines_right = [["ID", "duration", "wav", "spk_id", "wrd"]]
     csv_lines_binaural = [["ID", "duration", "wav", "spk_id", "wrd"]]
 
-    left_csvfile = "left_" + data_split
-    right_csvfile = "right_" + data_split
-    binaural_csvfile = "binaural_" + data_split
-    if if_msbg:
-        left_csvfile += "_msbg"
-        right_csvfile += "_msbg"
-        binaural_csvfile += "_msbg"
-    if if_ref:
-        left_csvfile += "_ref"
-        right_csvfile += "_ref"
-        binaural_csvfile += "_ref"
-    left_csvfile = os.path.join(target_data_folder, left_csvfile + ".csv")
-    right_csvfile = os.path.join(target_data_folder, right_csvfile + ".csv")
-    binaural_csvfile = os.path.join(target_data_folder, binaural_csvfile + ".csv")
+    left_csvfile = target_data_folder / f"left_{data_split}{ext}.csv"
+    right_csvfile = target_data_folder / f"right_{data_split}{ext}.csv"
+    binaural_csvfile = target_data_folder / f"binaural_{data_split}{ext}.csv"
 
     for item in tqdm(all_data_list):
         snt_id = item["signal"]
         if snt_id.split("_")[0] in split_data_list or data_split == "test":
             if if_msbg:
-                wav_file = os.path.join(orig_signal_folder, snt_id + "_HL-output.wav")
+                wav_file = orig_signal_folder / f"{snt_id}_HL-output.wav"
             elif if_ref:
-                wav_file = os.path.join(
-                    orig_signal_folder,
-                    "../..",
-                    "scenes",
-                    snt_id.split("_")[0] + "_target_anechoic.wav",
+                wav_file = orig_signal_folder / (
+                    f"../../scenes/{snt_id.split('_')[0]}_target_anechoic.wav"
                 )
             else:
-                wav_file = os.path.join(orig_signal_folder, snt_id + ".wav")
-            wav_file_left = os.path.join(left_tgt_signal_folder, snt_id + ".wav")
-            wav_file_right = os.path.join(right_tgt_signal_folder, snt_id + ".wav")
-            if os.path.isfile(wav_file_left) and os.path.isfile(wav_file_right):
+                wav_file = orig_signal_folder / f"{snt_id}.wav"
+            wav_file_left = left_tgt_signal_folder / f"{snt_id}.wav"
+            wav_file_right = right_tgt_signal_folder / f"{snt_id}.wav"
+            if wav_file_left.exists() and wav_file_right.exists():
                 continue
 
             spk_id = item["listener"]  # should be listener_id
@@ -218,32 +200,34 @@ def generate_data_split(
             sf.write(wav_file_left, utt_16k[2 * targ_fs :, 0], targ_fs)
             sf.write(wav_file_right, utt_16k[2 * targ_fs :, 1], targ_fs)
 
-            csv_lines_left.append([snt_id, str(duration), wav_file_left, spk_id, wrds])
+            csv_lines_left.append(
+                [snt_id, str(duration), str(wav_file_left), spk_id, wrds]
+            )
             csv_lines_right.append(
-                [snt_id, str(duration), wav_file_right, spk_id, wrds]
+                [snt_id, str(duration), str(wav_file_right), spk_id, wrds]
             )
             csv_lines_binaural.append(
-                ["left_" + snt_id, str(duration), wav_file_left, spk_id, wrds]
+                ["left_" + snt_id, str(duration), str(wav_file_left), spk_id, wrds]
             )
             csv_lines_binaural.append(
-                ["right_" + snt_id, str(duration), wav_file_right, spk_id, wrds]
+                ["right_" + snt_id, str(duration), str(wav_file_right), spk_id, wrds]
             )
 
-    with open(left_csvfile, mode="w", encoding="utf-8") as csv_f:
+    with left_csvfile.open(mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
         for line in csv_lines_left:
             csv_writer.writerow(line)
 
-    with open(right_csvfile, mode="w", encoding="utf-8") as csv_f:
+    with right_csvfile.open(mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
         for line in csv_lines_right:
             csv_writer.writerow(line)
 
-    with open(binaural_csvfile, mode="w", encoding="utf-8") as csv_f:
+    with binaural_csvfile.open(mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
@@ -252,23 +236,23 @@ def generate_data_split(
 
 
 def run_signal_generation_train(cfg, track):
-    train_json_path = os.path.join(
-        cfg.path.cpc1_train_data, f"metadata/CPC1.{'train'+track}.json"
+    train_json_path = (
+        Path(cfg.path.cpc1_train_data) / f"metadata/CPC1.{'train'+track}.json"
     )
-    train_signal_folder = os.path.join(
-        cfg.path.cpc1_train_data, f"clarity_data/HA_outputs/{'train'+track}/"
+
+    train_signal_folder = (
+        Path(cfg.path.cpc1_train_data) / f"clarity_data/HA_outputs/{'train'+track}/"
     )
-    target_folder = os.path.join(cfg.path.exp_folder, "cpc1_asr_data" + track)
+
+    target_folder = Path(cfg.path.exp_folder) / f"cpc1_asr_data{track}"
     lists_to_generate = [
-        os.path.join(
-            cfg.path.exp_folder, "data_split", f"scene_train_list{track}.json"
-        ),
-        os.path.join(cfg.path.exp_folder, "data_split", f"scene_dev_list{track}.json"),
+        Path(cfg.path.exp_folder) / f"data_split/scene_train_list{track}.json",
+        Path(cfg.path.exp_folder) / f"data_split/scene_dev_list{track}.json",
     ]
 
     datasets_to_generate = []
     for list_to_generate in lists_to_generate:
-        with open(list_to_generate, "r", encoding="utf-8") as fp:
+        with list_to_generate.open("r", encoding="utf-8") as fp:
             datasets_to_generate.append(json.load(fp))
 
     for list_to_generate, dataset in zip(lists_to_generate, datasets_to_generate):
@@ -276,7 +260,7 @@ def run_signal_generation_train(cfg, track):
         #     train_json_path,
         #     train_signal_folder,
         #     target_folder,
-        #     os.path.basename(list_to_generate).split("_")[1],
+        #     list_to_generate.name.split("_")[1],
         #     datasets_to_generate[i],
         # )
 
@@ -284,7 +268,7 @@ def run_signal_generation_train(cfg, track):
             train_json_path,
             train_signal_folder,
             target_folder,
-            os.path.basename(list_to_generate).split("_")[1],
+            list_to_generate.name.split("_")[1],
             dataset,
             if_msbg=True,
         )
@@ -293,20 +277,21 @@ def run_signal_generation_train(cfg, track):
             train_json_path,
             train_signal_folder,
             target_folder,
-            os.path.basename(list_to_generate).split("_")[1],
+            list_to_generate.name.split("_")[1],
             dataset,
             if_ref=True,
         )
 
 
 def run_signal_generation_test(cfg, track):
-    test_json_path = os.path.join(
-        cfg.path.cpc1_test_data, f"metadata/CPC1.{'test'+track}.json"
+    test_json_path = (
+        Path(cfg.path.cpc1_test_data) / f"metadata/CPC1.{'test'+track}.json"
     )
-    test_signal_folder = os.path.join(
-        cfg.path.cpc1_test_data, f"clarity_data/HA_outputs/{'test'+track}/"
+    test_signal_folder = (
+        Path(cfg.path.cpc1_test_data) / f"clarity_data/HA_outputs/{'test'+track}/"
     )
-    target_folder = os.path.join(cfg.path.exp_folder, "cpc1_asr_data" + track)
+
+    target_folder = Path(cfg.path.exp_folder) / f"cpc1_asr_data{track}"
 
     # generate_data_split(
     #     test_json_path, test_signal_folder, target_folder, "test", [],
@@ -329,6 +314,7 @@ def run(cfg: DictConfig) -> None:
         track = ""
     else:
         logger.error("cpc1_track has to be closed or open")
+        raise ValueError("cpc1_track has to be closed or open")
 
     logger.info("Split all training data into train set and dev set.")
     run_data_split(cfg, track)
diff --git a/recipes/cpc1/e029_sheffield/train_asr.py b/recipes/cpc1/e029_sheffield/train_asr.py
index 0f99a2e8d..8724179f7 100644
--- a/recipes/cpc1/e029_sheffield/train_asr.py
+++ b/recipes/cpc1/e029_sheffield/train_asr.py
@@ -8,7 +8,6 @@
 # pylint: disable=W0201  # code requires attributes defined outside __init__
 
 import logging
-import os
 import sys
 from pathlib import Path
 
@@ -315,7 +314,7 @@ def text_pipeline(wrd):
 def main():
     # CLI:
     hparams_file, run_opts, overrides = sb.parse_arguments(sys.argv[1:])
-    with open(hparams_file, "r", encoding="utf-8") as fp:
+    with open(hparams_file, encoding="utf-8") as fp:
         hparams = load_hyperpyyaml(fp, overrides)
 
     # If distributed_launch=True then
@@ -360,8 +359,8 @@ def main():
 
     # Testing
     for dataset_key, test_dataset in test_datasets.items():
-        asr_brain.hparams.wer_file = os.path.join(
-            hparams["output_folder"], f"wer_{dataset_key}.txt"
+        asr_brain.hparams.wer_file = (
+            Path(hparams["output_folder"]) / f"wer_{dataset_key}.txt"
         )
         asr_brain.evaluate(
             test_dataset,
diff --git a/recipes/cpc1/e032_sheffield/evaluate.py b/recipes/cpc1/e032_sheffield/evaluate.py
index ff66b150e..af917f5a9 100644
--- a/recipes/cpc1/e032_sheffield/evaluate.py
+++ b/recipes/cpc1/e032_sheffield/evaluate.py
@@ -1,6 +1,6 @@
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -69,22 +69,18 @@ def compute_scores(predictions, labels):
     }
 
 
-def read_data(pred_json, label_json):
-    prediction = []
-    label = []
-
+def read_data(pred_json: Path, label_json: Path):
     # read label_json to dict
-    label_dict = {}
-    with open(label_json, "r", encoding="utf-8") as fp:
-        label_json = json.load(fp)
-    for item in label_json:
-        label_dict[item["signal"]] = item["correctness"]
+    with label_json.open("r", encoding="utf-8") as fp:
+        labels = json.load(fp)
+
+    label_dict = {item["signal"]: item["correctness"] for item in labels}
 
-    with open(pred_json, "r", encoding="utf-8") as fp:
+    with pred_json.open("r", encoding="utf-8") as fp:
         pred_dict = json.load(fp)
-    for signal, pred in pred_dict.items():
-        prediction.append(pred * 100.0)
-        label.append(label_dict[signal])
+
+    prediction = [pred * 100.0 for pred in pred_dict.values()]
+    label = [label_dict[signal] for signal in pred_dict]
 
     return np.array(prediction), np.array(label)
 
@@ -100,12 +96,12 @@ def run(cfg: DictConfig) -> None:
 
     # encoder representation evaluation
     prediction_dev, label_dev = read_data(
-        os.path.join(cfg.path.exp_folder, "dev_enc_similarity.json"),
-        os.path.join(cfg.path.cpc1_train_data, f"metadata/CPC1.{'train'+track}.json"),
+        Path(cfg.path.exp_folder) / "dev_enc_similarity.json",
+        Path(cfg.path.cpc1_train_data) / f"metadata/CPC1.{'train'+track}.json",
     )
     prediction_test, label_test = read_data(
-        os.path.join(cfg.path.exp_folder, "test_enc_similarity.json"),
-        f"../test_listener_responses/CPC1.{'test'+track}.json",
+        Path(cfg.path.exp_folder) / "test_enc_similarity.json",
+        Path(f"../test_listener_responses/CPC1.{'test'+track}.json"),
     )
 
     logger.info("Apply logistic fitting.")
@@ -116,12 +112,12 @@ def run(cfg: DictConfig) -> None:
 
     # decoder representation evaluation
     prediction_dev, label_dev = read_data(
-        os.path.join(cfg.path.exp_folder, "dev_dec_similarity.json"),
-        os.path.join(cfg.path.cpc1_train_data, f"metadata/CPC1.{'train'+track}.json"),
+        Path(cfg.path.exp_folder) / "dev_dec_similarity.json",
+        Path(cfg.path.cpc1_train_data) / f"metadata/CPC1.{'train'+track}.json",
     )
     prediction_test, label_test = read_data(
-        os.path.join(cfg.path.exp_folder, "test_dec_similarity.json"),
-        f"../test_listener_responses/CPC1.{'test'+track}.json",
+        Path(cfg.path.exp_folder) / "test_dec_similarity.json",
+        Path(f"../test_listener_responses/CPC1.{'test'+track}.json"),
     )
 
     logger.info("Apply logistic fitting.")
@@ -130,10 +126,9 @@ def run(cfg: DictConfig) -> None:
     fit_pred = model.predict(prediction_test)
     dec_scores = compute_scores(fit_pred, label_test)
 
-    with open(
-        os.path.join(cfg.path.exp_folder, "results.json"), "w", encoding="utf-8"
-    ) as f:
-        json.dump({"enc_results": enc_scores, "dec_results": dec_scores}, f)
+    results_file = Path(cfg.path.exp_folder) / "results.json"
+    with results_file.open("w", encoding="utf-8") as fp:
+        json.dump({"enc_results": enc_scores, "dec_results": dec_scores}, fp)
 
 
 # pylint: disable=no-value-for-parameter
diff --git a/recipes/cpc1/e032_sheffield/infer.py b/recipes/cpc1/e032_sheffield/infer.py
index 56d0edca8..9a8270c63 100644
--- a/recipes/cpc1/e032_sheffield/infer.py
+++ b/recipes/cpc1/e032_sheffield/infer.py
@@ -1,6 +1,6 @@
 import json
 import logging
-import os
+from pathlib import Path
 
 import hydra
 import speechbrain as sb
@@ -84,7 +84,7 @@ def init_evaluation(self, max_key=None, min_key=None):
 
 def init_asr(asr_config):
     hparams_file, run_opts, overrides = sb.parse_arguments([asr_config])
-    with open(hparams_file, "r", encoding="utf-8") as fp:
+    with open(hparams_file, encoding="utf-8") as fp:
         hparams = load_hyperpyyaml(fp, overrides)
 
     tokenizer = hparams["tokenizer"]
@@ -227,14 +227,16 @@ def run(cfg: DictConfig) -> None:
         track = ""
     else:
         logger.error("cpc1_track has to be closed or open")
+        raise ValueError("cpc1_track has to be closed or open")
 
     asr_model, tokenizer, bos_index = init_asr(cfg.asr_config)
 
+    exp_path = Path(cfg.path.exp_folder)
     left_dev_csv = sb.dataio.dataio.load_data_csv(
-        os.path.join(cfg.path.exp_folder, "cpc1_asr_data" + track, "left_dev_msbg.csv")
+        exp_path / f"cpc1_asr_data{track}/left_dev_msbg.csv"
     )  # using left ear csvfile for data loading
     left_test_csv = sb.dataio.dataio.load_data_csv(
-        os.path.join(cfg.path.exp_folder, "cpc1_asr_data" + track, "left_test_msbg.csv")
+        exp_path / f"cpc1_asr_data{track}/left_test_msbg.csv"
     )  # using left ear csvfile for data loading
 
     # dev set similarity
@@ -249,17 +251,9 @@ def run(cfg: DictConfig) -> None:
         dev_enc_similarity[wav_id] = similarity[0].tolist()
         dev_dec_similarity[wav_id] = similarity[1].tolist()
 
-        with open(
-            os.path.join(cfg.path.exp_folder, "dev_enc_similarity.json"),
-            "w",
-            encoding="utf-8",
-        ) as fp:
+        with (exp_path / "dev_enc_similarity.json").open("w", encoding="utf-8") as fp:
             json.dump(dev_enc_similarity, fp)
-        with open(
-            os.path.join(cfg.path.exp_folder, "dev_dec_similarity.json"),
-            "w",
-            encoding="utf-8",
-        ) as fp:
+        with (exp_path / "dev_dec_similarity.json").open("w", encoding="utf-8") as fp:
             json.dump(dev_dec_similarity, fp)
 
     # test set similarity
@@ -274,18 +268,10 @@ def run(cfg: DictConfig) -> None:
         test_enc_similarity[wav_id] = similarity[0].tolist()
         test_dec_similarity[wav_id] = similarity[1].tolist()
 
-        with open(
-            os.path.join(cfg.path.exp_folder, "test_enc_similarity.json"),
-            "w",
-            encoding="utf-8",
-        ) as fp:
+        with (exp_path / "test_enc_similarity.json").open("w", encoding="utf-8") as fp:
             json.dump(test_enc_similarity, fp)
 
-        with open(
-            os.path.join(cfg.path.exp_folder, "test_dec_similarity.json"),
-            "w",
-            encoding="utf-8",
-        ) as fp:
+        with (exp_path / "test_dec_similarity.json").open("w", encoding="utf-8") as fp:
             json.dump(test_dec_similarity, fp)
 
 
diff --git a/recipes/cpc1/e032_sheffield/prepare_data.py b/recipes/cpc1/e032_sheffield/prepare_data.py
index 6bf2fabb7..2b9ee7d0e 100644
--- a/recipes/cpc1/e032_sheffield/prepare_data.py
+++ b/recipes/cpc1/e032_sheffield/prepare_data.py
@@ -2,8 +2,8 @@
 import csv
 import json
 import logging
-import os
 import random
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -22,23 +22,21 @@
 
 
 def run_data_split(cfg, track):
-    os.makedirs(os.path.join(cfg.path.exp_folder, "data_split"), exist_ok=True)
-    scene_train_json = os.path.join(
-        cfg.path.exp_folder, "data_split", f"scene_train_list{track}.json"
-    )
-    scene_dev_json = os.path.join(
-        cfg.path.exp_folder, "data_split", f"scene_dev_list{track}.json"
-    )
-    if os.path.isfile(scene_train_json) and os.path.isfile(scene_dev_json):
+    data_split_dir = Path(cfg.path.exp_folder) / "data_split"
+    data_split_dir.mkdir(parents=True, exist_ok=True)
+    scene_train_json = data_split_dir / f"scene_train_list{track}.json"
+    scene_dev_json = data_split_dir / f"scene_dev_list{track}.json"
+
+    if scene_train_json.exists() and scene_dev_json.exists():
         logger.info("Train set and dev set lists exist...")
         return
 
-    filepath = os.path.join(
-        cfg.path.root,
-        "clarity_CPC1_data_train/metadata",
-        f"CPC1.{'train'+track}.json",
+    file_path = (
+        Path(cfg.path.root)
+        / f"clarity_CPC1_data_train/metadata/CPC1.{'train'+track}.json"
     )
-    with open(filepath, "r", encoding="utf-8") as fp:
+
+    with file_path.open("r", encoding="utf-8") as fp:
         scenes_dict = json.load(fp)
     scene_list = []
     for item in scenes_dict:
@@ -49,9 +47,9 @@ def run_data_split(cfg, track):
     )
     scene_train_list = list(set(scene_list) - set(scene_dev_list))
 
-    with open(scene_train_json, "w", encoding="utf-8") as fp:
+    with scene_train_json.open("w", encoding="utf-8") as fp:
         json.dump(scene_train_list, fp)
-    with open(scene_dev_json, "w", encoding="utf-8") as fp:
+    with scene_dev_json.open("w", encoding="utf-8") as fp:
         json.dump(scene_dev_list, fp)
 
 
@@ -79,22 +77,18 @@ def listen(ear, signal, audiogram_l, audiogram_r):
 def run_msbg_simulation(cfg, track):
     for split in ["train", "test"]:
         dataset = split + track
-        dataset_folder = os.path.join(cfg.path.root, "clarity_CPC1_data_" + split)
-        output_path = os.path.join(dataset_folder, "clarity_data/HA_outputs", dataset)
-        file_path = os.path.join(dataset_folder, "metadata", f"CPC1.{dataset}.json")
-        with open(file_path, "r", encoding="utf-8") as fp:
+        dataset_folder = Path(cfg.path.root) / f"clarity_CPC1_data_{split}"
+        output_path = dataset_folder / "clarity_data/HA_outputs" / dataset
+        file_path = dataset_folder / f"metadata/CPC1.{dataset}.json"
+        with file_path.open("r", encoding="utf-8") as fp:
             scenes = json.load(fp)
         if split == "train":
-            file_path = os.path.join(
-                dataset_folder, "metadata", "listeners.CPC1_train.json"
-            )
-            with open(file_path, "r", encoding="utf-8") as fp:
+            file_path = dataset_folder / "metadata/listeners.CPC1_train.json"
+            with file_path.open("r", encoding="utf-8") as fp:
                 listener_audiograms = json.load(fp)
         else:
-            file_path = os.path.join(
-                dataset_folder, "metadata", "listeners.CPC1_all.json"
-            )
-            with open(file_path, "r", encoding="utf-8") as fp:
+            file_path = Path(dataset_folder) / "metadata/listeners.CPC1_all.json"
+            with file_path.open("r", encoding="utf-8") as fp:
                 listener_audiograms = json.load(fp)
 
         # initialize ear
@@ -104,15 +98,15 @@ def run_msbg_simulation(cfg, track):
             scene = scene_dict["scene"]
             listener = scene_dict["listener"]
             system = scene_dict["system"]
-            signal_file = os.path.join(output_path, f"{scene}_{listener}_{system}.wav")
+            signal_file = output_path / f"{scene}_{listener}_{system}.wav"
 
             # signals to write
-            outfile_stem = f"{output_path}/{scene}_{listener}_{system}"
+            outfile_stem = output_path / f"{scene}_{listener}_{system}"
             signal_files_to_write = [
-                f"{outfile_stem}_HL-output.wav",
+                Path(f"{outfile_stem}_HL-output.wav"),
             ]
             # if all signals to write exist, pass
-            if all(os.path.isfile(f) for f in signal_files_to_write):
+            if all(f.exists() for f in signal_files_to_write):
                 continue
             signal = read_signal(signal_file)
 
@@ -141,68 +135,56 @@ def run_msbg_simulation(cfg, track):
 
 
 def generate_data_split(
-    orig_data_json,
-    orig_signal_folder,
-    target_data_folder,
+    orig_data_json: Path,
+    orig_signal_folder: Path,
+    target_data_folder: Path,
     data_split,
     split_data_list,
     if_msbg=False,
     if_ref=False,
 ):
-    with open(orig_data_json, "r", encoding="utf-8") as fp:
+    with orig_data_json.open("r", encoding="utf-8") as fp:
         all_data_list = json.load(fp)
 
-    left_tgt_signal_folder = os.path.join(
-        target_data_folder, orig_signal_folder.split("/")[-2] + "_left"
+    ext = ""
+    if if_msbg:
+        ext = "_msbg"
+    elif if_ref:
+        ext = "_ref"
+
+    left_tgt_signal_folder = target_data_folder / (
+        str(orig_signal_folder).split("/")[-2] + "_left" + ext
     )
-    right_tgt_signal_folder = os.path.join(
-        target_data_folder, orig_signal_folder.split("/")[-2] + "_right"
+
+    right_tgt_signal_folder = target_data_folder / (
+        str(orig_signal_folder).split("/")[-2] + "_right" + ext
     )
-    if if_msbg:
-        left_tgt_signal_folder = left_tgt_signal_folder + "_msbg"
-        right_tgt_signal_folder = right_tgt_signal_folder + "_msbg"
-    if if_ref:
-        left_tgt_signal_folder = left_tgt_signal_folder + "_ref"
-        right_tgt_signal_folder = right_tgt_signal_folder + "_ref"
-    os.makedirs(left_tgt_signal_folder, exist_ok=True)
-    os.makedirs(right_tgt_signal_folder, exist_ok=True)
+
+    left_tgt_signal_folder.mkdir(parents=True, exist_ok=True)
+    right_tgt_signal_folder.mkdir(parents=True, exist_ok=True)
 
     csv_lines_left = [["ID", "duration", "wav", "spk_id", "wrd"]]
     csv_lines_right = [["ID", "duration", "wav", "spk_id", "wrd"]]
     csv_lines_binaural = [["ID", "duration", "wav", "spk_id", "wrd"]]
 
-    left_csvfile = "left_" + data_split
-    right_csvfile = "right_" + data_split
-    binaural_csvfile = "binaural_" + data_split
-    if if_msbg:
-        left_csvfile += "_msbg"
-        right_csvfile += "_msbg"
-        binaural_csvfile += "_msbg"
-    if if_ref:
-        left_csvfile += "_ref"
-        right_csvfile += "_ref"
-        binaural_csvfile += "_ref"
-    left_csvfile = os.path.join(target_data_folder, left_csvfile + ".csv")
-    right_csvfile = os.path.join(target_data_folder, right_csvfile + ".csv")
-    binaural_csvfile = os.path.join(target_data_folder, binaural_csvfile + ".csv")
+    left_csvfile = target_data_folder / f"left_{data_split}{ext}.csv"
+    right_csvfile = target_data_folder / f"right_{data_split}{ext}.csv"
+    binaural_csvfile = target_data_folder / f"binaural_{data_split}{ext}.csv"
 
     for item in tqdm(all_data_list):
         snt_id = item["signal"]
         if snt_id.split("_")[0] in split_data_list or data_split == "test":
             if if_msbg:
-                wav_file = os.path.join(orig_signal_folder, snt_id + "_HL-output.wav")
+                wav_file = orig_signal_folder / f"{snt_id}_HL-output.wav"
             elif if_ref:
-                wav_file = os.path.join(
-                    orig_signal_folder,
-                    "../..",
-                    "scenes",
-                    snt_id.split("_")[0] + "_target_anechoic.wav",
+                wav_file = orig_signal_folder / (
+                    f"../../scenes/{snt_id.split('_')[0]}_target_anechoic.wav"
                 )
             else:
-                wav_file = os.path.join(orig_signal_folder, snt_id + ".wav")
-            wav_file_left = os.path.join(left_tgt_signal_folder, snt_id + ".wav")
-            wav_file_right = os.path.join(right_tgt_signal_folder, snt_id + ".wav")
-            if os.path.isfile(wav_file_left) and os.path.isfile(wav_file_right):
+                wav_file = orig_signal_folder / f"{snt_id}.wav"
+            wav_file_left = left_tgt_signal_folder / f"{snt_id}.wav"
+            wav_file_right = right_tgt_signal_folder / f"{snt_id}.wav"
+            if wav_file_left.exists() and wav_file_right.exists():
                 continue
 
             spk_id = item["listener"]  # should be listener_id
@@ -218,32 +200,34 @@ def generate_data_split(
             sf.write(wav_file_left, utt_16k[2 * targ_fs :, 0], targ_fs)
             sf.write(wav_file_right, utt_16k[2 * targ_fs :, 1], targ_fs)
 
-            csv_lines_left.append([snt_id, str(duration), wav_file_left, spk_id, wrds])
+            csv_lines_left.append(
+                [snt_id, str(duration), str(wav_file_left), spk_id, wrds]
+            )
             csv_lines_right.append(
-                [snt_id, str(duration), wav_file_right, spk_id, wrds]
+                [snt_id, str(duration), str(wav_file_right), spk_id, wrds]
             )
             csv_lines_binaural.append(
-                ["left_" + snt_id, str(duration), wav_file_left, spk_id, wrds]
+                ["left_" + snt_id, str(duration), str(wav_file_left), spk_id, wrds]
             )
             csv_lines_binaural.append(
-                ["right_" + snt_id, str(duration), wav_file_right, spk_id, wrds]
+                ["right_" + snt_id, str(duration), str(wav_file_right), spk_id, wrds]
             )
 
-    with open(left_csvfile, mode="w", encoding="utf-8") as csv_f:
+    with left_csvfile.open(mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
         for line in csv_lines_left:
             csv_writer.writerow(line)
 
-    with open(right_csvfile, mode="w", encoding="utf-8") as csv_f:
+    with right_csvfile.open(mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
         for line in csv_lines_right:
             csv_writer.writerow(line)
 
-    with open(binaural_csvfile, mode="w", encoding="utf-8") as csv_f:
+    with binaural_csvfile.open(mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
@@ -252,23 +236,23 @@ def generate_data_split(
 
 
 def run_signal_generation_train(cfg, track):
-    train_json_path = os.path.join(
-        cfg.path.cpc1_train_data, f"metadata/CPC1.{'train'+track}.json"
+    train_json_path = (
+        Path(cfg.path.cpc1_train_data) / f"metadata/CPC1.{'train'+track}.json"
     )
-    train_signal_folder = os.path.join(
-        cfg.path.cpc1_train_data, f"clarity_data/HA_outputs/{'train'+track}/"
+
+    train_signal_folder = (
+        Path(cfg.path.cpc1_train_data) / f"clarity_data/HA_outputs/{'train'+track}/"
     )
-    target_folder = os.path.join(cfg.path.exp_folder, "cpc1_asr_data" + track)
+
+    target_folder = Path(cfg.path.exp_folder) / f"cpc1_asr_data{track}"
     lists_to_generate = [
-        os.path.join(
-            cfg.path.exp_folder, "data_split", f"scene_train_list{track}.json"
-        ),
-        os.path.join(cfg.path.exp_folder, "data_split", f"scene_dev_list{track}.json"),
+        Path(cfg.path.exp_folder) / f"data_split/scene_train_list{track}.json",
+        Path(cfg.path.exp_folder) / f"data_split/scene_dev_list{track}.json",
     ]
 
     datasets_to_generate = []
     for list_to_generate in lists_to_generate:
-        with open(list_to_generate, "r", encoding="utf-8") as fp:
+        with list_to_generate.open("r", encoding="utf-8") as fp:
             datasets_to_generate.append(json.load(fp))
 
     for list_to_generate, dataset in zip(lists_to_generate, datasets_to_generate):
@@ -276,7 +260,7 @@ def run_signal_generation_train(cfg, track):
         #     train_json_path,
         #     train_signal_folder,
         #     target_folder,
-        #     os.path.basename(list_to_generate).split("_")[1],
+        #     list_to_generate.name.split("_")[1],
         #     datasets_to_generate[i],
         # )
 
@@ -284,7 +268,7 @@ def run_signal_generation_train(cfg, track):
             train_json_path,
             train_signal_folder,
             target_folder,
-            os.path.basename(list_to_generate).split("_")[1],
+            list_to_generate.name.split("_")[1],
             dataset,
             if_msbg=True,
         )
@@ -293,20 +277,21 @@ def run_signal_generation_train(cfg, track):
             train_json_path,
             train_signal_folder,
             target_folder,
-            os.path.basename(list_to_generate).split("_")[1],
+            list_to_generate.name.split("_")[1],
             dataset,
             if_ref=True,
         )
 
 
 def run_signal_generation_test(cfg, track):
-    test_json_path = os.path.join(
-        cfg.path.cpc1_test_data, f"metadata/CPC1.{'test'+track}.json"
+    test_json_path = (
+        Path(cfg.path.cpc1_test_data) / f"metadata/CPC1.{'test'+track}.json"
     )
-    test_signal_folder = os.path.join(
-        cfg.path.cpc1_test_data, f"clarity_data/HA_outputs/{'test'+track}/"
+    test_signal_folder = (
+        Path(cfg.path.cpc1_test_data) / f"clarity_data/HA_outputs/{'test'+track}/"
     )
-    target_folder = os.path.join(cfg.path.exp_folder, "cpc1_asr_data" + track)
+
+    target_folder = Path(cfg.path.exp_folder) / f"cpc1_asr_data{track}"
 
     # generate_data_split(
     #     test_json_path, test_signal_folder, target_folder, "test", [],
@@ -329,6 +314,7 @@ def run(cfg: DictConfig) -> None:
         track = ""
     else:
         logger.error("cpc1_track has to be closed or open")
+        raise ValueError("cpc1_track has to be closed or open")
 
     logger.info("Split all training data into train set and dev set.")
     run_data_split(cfg, track)
diff --git a/recipes/cpc1/e032_sheffield/train_asr.py b/recipes/cpc1/e032_sheffield/train_asr.py
index eac380b43..3f910799e 100644
--- a/recipes/cpc1/e032_sheffield/train_asr.py
+++ b/recipes/cpc1/e032_sheffield/train_asr.py
@@ -6,7 +6,6 @@
 """
 
 import logging
-import os
 import sys
 from pathlib import Path
 
@@ -330,7 +329,7 @@ def text_pipeline(wrd):
 def main():
     # CLI:
     hparams_file, run_opts, overrides = sb.parse_arguments(sys.argv[1:])
-    with open(hparams_file, "r", encoding="utf-8") as fp:
+    with open(hparams_file, encoding="utf-8") as fp:
         hparams = load_hyperpyyaml(fp, overrides)
 
     # If distributed_launch=True then
@@ -376,8 +375,8 @@ def main():
     # Testing
     for dataset_key, test_dataset in test_datasets.items():
         # dataset_keys are test_clean, test_other etc
-        asr_brain.hparams.wer_file = os.path.join(
-            hparams["output_folder"], f"wer_{dataset_key}.txt"
+        asr_brain.hparams.wer_file = (
+            Path(hparams["output_folder"]) / f"wer_{dataset_key}.txt"
         )
         asr_brain.evaluate(
             test_dataset,
diff --git a/recipes/cpc2/baseline/compute_haspi.py b/recipes/cpc2/baseline/compute_haspi.py
index de9973147..b17f4bd8c 100644
--- a/recipes/cpc2/baseline/compute_haspi.py
+++ b/recipes/cpc2/baseline/compute_haspi.py
@@ -1,10 +1,10 @@
 """ Compute the HASPI scores. """
+from __future__ import annotations
 
 import hashlib
 import json
 import logging
 from pathlib import Path
-from typing import Tuple
 
 import hydra
 import numpy as np
@@ -24,7 +24,7 @@ def set_seed_with_string(seed_string: str) -> None:
     np.random.seed(md5_int)
 
 
-def parse_cec2_signal_name(signal_name: str) -> Tuple[str, str, str]:
+def parse_cec2_signal_name(signal_name: str) -> tuple[str, str, str]:
     """Parse the CEC2 signal name."""
     # e.g. S0001_L0001_E001_hr -> S0001, L0001, E001_hr
     scene, listener, *system = signal_name.split("_")
@@ -47,9 +47,7 @@ def compute_haspi_for_signal(signal_name: str, path: dict) -> float:
     scene, listener, _ = parse_cec2_signal_name(signal_name)
 
     # Retrieve audiograms
-    with open(
-        Path(path["metadata_dir"]) / "listeners.json", "r", encoding="utf-8"
-    ) as fp:
+    with open(Path(path["metadata_dir"]) / "listeners.json", encoding="utf-8") as fp:
         listener_audiograms = json.load(fp)
         audiogram = listener_audiograms[listener]
 
diff --git a/recipes/cpc2/baseline/evaluate.py b/recipes/cpc2/baseline/evaluate.py
index 4cc00fea2..fd52909b6 100644
--- a/recipes/cpc2/baseline/evaluate.py
+++ b/recipes/cpc2/baseline/evaluate.py
@@ -49,7 +49,7 @@ def evaluate(cfg: DictConfig) -> None:
 
     # Load the intelligibility dataset records
     dataset_filename = Path(cfg.path.metadata_dir) / f"{cfg.dataset}.json"
-    with open(dataset_filename, "r", encoding="utf-8") as fp:
+    with open(dataset_filename, encoding="utf-8") as fp:
         records = json.load(fp)
     record_index = {record["signal"]: record for record in records}
 
diff --git a/recipes/cpc2/baseline/predict.py b/recipes/cpc2/baseline/predict.py
index 5adff89a4..6b3ee9b49 100644
--- a/recipes/cpc2/baseline/predict.py
+++ b/recipes/cpc2/baseline/predict.py
@@ -1,8 +1,9 @@
 """Make intelligibility predictions from HASPI scores."""
+from __future__ import annotations
+
 import json
 import logging
 from pathlib import Path
-from typing import Optional
 
 import hydra
 import numpy as np
@@ -21,7 +22,7 @@ class LogisticModel:
     Fits a logistic mapping from input values x to output values y.
     """
 
-    params: Optional[np.ndarray] = None  # The model params
+    params: np.ndarray | None = None  # The model params
 
     def _logistic_mapping(self, x, x_0, k):
         """Logistic function
diff --git a/recipes/icassp_2023/baseline/enhance.py b/recipes/icassp_2023/baseline/enhance.py
index c8e62d3d5..073d806a8 100644
--- a/recipes/icassp_2023/baseline/enhance.py
+++ b/recipes/icassp_2023/baseline/enhance.py
@@ -20,11 +20,11 @@ def enhance(cfg: DictConfig) -> None:
     enhanced_folder = pathlib.Path("enhanced_signals")
     enhanced_folder.mkdir(parents=True, exist_ok=True)
 
-    with open(cfg.path.scenes_listeners_file, "r", encoding="utf-8") as fp:
+    with open(cfg.path.scenes_listeners_file, encoding="utf-8") as fp:
         scenes_listeners = json.load(fp)
 
     # pylint: disable=unused-variable
-    with open(cfg.path.listeners_file, "r", encoding="utf-8") as fp:
+    with open(cfg.path.listeners_file, encoding="utf-8") as fp:
         listener_audiograms = json.load(fp)  # noqa: F841
 
     # Make list of all scene listener pairs that will be run
diff --git a/recipes/icassp_2023/baseline/evaluate.py b/recipes/icassp_2023/baseline/evaluate.py
index c99eca8ee..be8269398 100644
--- a/recipes/icassp_2023/baseline/evaluate.py
+++ b/recipes/icassp_2023/baseline/evaluate.py
@@ -96,10 +96,10 @@ def run_calculate_si(cfg: DictConfig) -> None:
     """Evaluate the enhanced signals using a combination of HASPI and HASQI metrics"""
 
     # Load listener data
-    with open(cfg.path.scenes_listeners_file, "r", encoding="utf-8") as fp:
+    with open(cfg.path.scenes_listeners_file, encoding="utf-8") as fp:
         scenes_listeners = json.load(fp)
 
-    with open(cfg.path.listeners_file, "r", encoding="utf-8") as fp:
+    with open(cfg.path.listeners_file, encoding="utf-8") as fp:
         listener_audiograms = json.load(fp)
 
     enhancer = NALR(**cfg.nalr)
diff --git a/recipes/icassp_2023/baseline/report_score.py b/recipes/icassp_2023/baseline/report_score.py
index 77c8e98ff..0b4609b7f 100644
--- a/recipes/icassp_2023/baseline/report_score.py
+++ b/recipes/icassp_2023/baseline/report_score.py
@@ -14,7 +14,7 @@
 def report_score(cfg: DictConfig) -> None:
     """Run the dummy enhancement."""
 
-    with open(cfg.path.scenes_listeners_file, "r", encoding="utf-8") as fp:
+    with open(cfg.path.scenes_listeners_file, encoding="utf-8") as fp:
         scenes_listeners = json.load(fp)
 
     results_df = pd.read_csv("scores.csv")


Test patch:
diff --git a/recipes/cec1/e009_sheffield/test.py b/recipes/cec1/e009_sheffield/test.py
index ce5f799b1..ee99ee185 100644
--- a/recipes/cec1/e009_sheffield/test.py
+++ b/recipes/cec1/e009_sheffield/test.py
@@ -1,4 +1,4 @@
-import os
+from pathlib import Path
 
 import hydra
 import numpy as np
@@ -16,12 +16,14 @@
 
 @hydra.main(config_path=".", config_name="config")
 def run(cfg: DictConfig) -> None:
-    output_folder = os.path.join(cfg.path.exp_folder, "enhanced_" + cfg.listener.id)
-    os.makedirs(output_folder, exist_ok=True)
+    exp_folder = Path(cfg.path.exp_folder)
+    output_folder = exp_folder / f"enhanced_{cfg.listener.id}"
+    output_folder.mkdir(parents=True, exist_ok=True)
 
     test_set = CEC1Dataset(**cfg.test_dataset)
     test_loader = DataLoader(dataset=test_set, **cfg.test_loader)
 
+    down_sample = up_sample = None
     if cfg.downsample_factor != 1:
         down_sample = torchaudio.transforms.Resample(
             orig_freq=cfg.sr,
@@ -44,9 +46,8 @@ def run(cfg: DictConfig) -> None:
                 torch.cuda.empty_cache()
                 # load denoising module
                 den_model = ConvTasNet(**cfg.mc_conv_tasnet)
-                den_model_path = os.path.join(
-                    os.path.join(cfg.path.exp_folder, ear + "_den"), "best_model.pth"
-                )
+                den_model_path = exp_folder / f"{ear}_den/best_model.pth"
+
                 den_model.load_state_dict(
                     torch.load(den_model_path, map_location=device)
                 )
@@ -55,9 +56,8 @@ def run(cfg: DictConfig) -> None:
 
                 # load amplification module
                 amp_model = AudiometricFIR(**cfg.fir)
-                amp_model_path = os.path.join(
-                    os.path.join(cfg.path.exp_folder, ear + "_amp"), "best_model.pth"
-                )
+                amp_model_path = exp_folder / f"{ear}_amp/best_model.pth"
+
                 amp_model.load_state_dict(
                     torch.load(amp_model_path, map_location=device)
                 )
@@ -65,20 +65,18 @@ def run(cfg: DictConfig) -> None:
                 _amp_model.eval()
 
                 noisy = noisy.to(device)
-                if cfg.downsample_factor != 1:
+                proc = noisy
+                if down_sample is not None:
                     proc = down_sample(noisy)
                 enhanced = amp_model(den_model(proc)).squeeze(1)
-                if cfg.downsample_factor != 1:
+                if up_sample is not None:
                     enhanced = up_sample(enhanced)
                 enhanced = torch.clamp(enhanced, -1, 1)
                 out.append(enhanced.detach().cpu().numpy()[0])
 
             out = np.stack(out, axis=0).transpose()
             write(
-                os.path.join(
-                    output_folder,
-                    scene[0] + "_" + cfg.listener.id + "_" + "HA-output.wav",
-                ),
+                output_folder / f"{scene[0]}_{cfg.listener.id}_HA-output.wav",
                 out,
                 cfg.sr,
             )
diff --git a/tests/data/test_utils.py b/tests/data/test_utils.py
index 54da9597f..ed8eeea83 100644
--- a/tests/data/test_utils.py
+++ b/tests/data/test_utils.py
@@ -1,5 +1,5 @@
 """Tests for the data.utils module."""
-from typing import List
+from __future__ import annotations
 
 import numpy as np
 import pytest
@@ -111,7 +111,7 @@ def test_speechweighted_snr_unequal_arrays_value_error() -> None:
         ),
     ],
 )
-def test_sum_signals(signals: List[int], expected: np.ndarray) -> None:
+def test_sum_signals(signals: list[int], expected: np.ndarray) -> None:
     """Test of sum_signals()."""
     summed_signals = sum_signals(signals)
     np.testing.assert_array_equal(summed_signals, expected)
diff --git a/tests/evaluator/haspi/test_eb.py b/tests/evaluator/haspi/test_eb.py
new file mode 100644
index 000000000..e8b114b0d
--- /dev/null
+++ b/tests/evaluator/haspi/test_eb.py
@@ -0,0 +1,654 @@
+"""Tests for eb module"""
+
+import numpy as np
+import pytest
+
+from clarity.evaluator.haspi.eb import (
+    ave_covary2,
+    bandwidth_adjust,
+    basilar_membrane_add_noise,
+    bm_covary,
+    center_frequency,
+    convert_rms_to_sl,
+    ear_model,
+    env_compress_basilar_membrane,
+    env_smooth,
+    envelope_align,
+    envelope_sl,
+    gammatone_bandwidth_demodulation,
+    gammatone_basilar_membrane,
+    group_delay_compensate,
+    inner_hair_cell_adaptation,
+    input_align,
+    loss_parameters,
+    mel_cepstrum_correlation,
+    melcor9,
+    melcor9_crosscovmatrix,
+    middle_ear,
+    resample_24khz,
+    spectrum_diff,
+)
+
+# ear_model
+
+
+def test_ear_model():
+    """Test ear model"""
+    np.random.seed(0)
+    sig_len = 600
+    samp_freq = 24000
+    out_sig_len = sig_len * 24000 / samp_freq
+
+    ref = np.random.random(size=sig_len)
+    proc = np.random.random(size=sig_len)
+    ref_db, ref_bm, proc_db, proc_bm, ref_sl, proc_sl, freq_sample = ear_model(
+        reference=ref,
+        reference_freq=samp_freq,
+        processed=ref + proc,
+        processed_freq=samp_freq,
+        hearing_loss=np.array([45, 45, 35, 45, 60, 65]),
+        itype=0,
+        level1=65,
+        nchan=10,
+        m_delay=1,
+        shift=0.0,
+    )
+
+    # check shapes
+    assert ref_db.shape == (10, out_sig_len)
+    assert proc_db.shape == (10, out_sig_len)
+    assert ref_bm.shape == (10, out_sig_len)
+    assert proc_bm.shape == (10, out_sig_len)
+    assert ref_sl.shape == (10,)
+    assert proc_sl.shape == (10,)
+
+    # check values
+    assert freq_sample == 24000
+    assert np.sum(np.abs(ref_db)) == pytest.approx(102596.63767028379)
+    assert np.sum(np.abs(proc_db)) == pytest.approx(4145.3884196835625)
+    assert np.sum(np.abs(ref_bm)) == pytest.approx(65517.72934742906)
+    assert np.sum(np.abs(proc_bm)) == pytest.approx(2366.401656815131)
+    assert np.sum(np.abs(ref_sl)) == pytest.approx(291.3527365691821)
+    assert np.sum(np.abs(proc_sl)) == pytest.approx(13.655317152968216)
+
+
+def test_center_frequency():
+    """Test center frequency"""
+
+    center_freq = center_frequency(
+        nchan=10,
+        shift=None,
+        low_freq=80,
+        high_freq=8000,
+        ear_q=9.26449,
+        min_bw=24.7,
+    )
+    assert center_freq.shape == (10,)
+    assert np.sum(center_freq) == pytest.approx(23935.19626226296)
+
+
+def test_loss_parameters():
+    """Test loss parameters"""
+
+    (
+        attenuated_ohc,
+        bandwith,
+        low_knee,
+        compression_ratio,
+        annenuated_ihc,
+    ) = loss_parameters(
+        hearing_loss=np.array([45, 45, 50, 60, 70, 80]),
+        center_freq=np.array([250, 500, 1000, 2000, 4000, 6000]),
+        audiometric_freq=np.array([250, 500, 1000, 2000, 4000, 6000]),
+    )
+
+    # check shapes
+    assert attenuated_ohc.shape == (6,)
+    assert bandwith.shape == (6,)
+    assert low_knee.shape == (6,)
+    assert compression_ratio.shape == (6,)
+    assert annenuated_ihc.shape == (6,)
+    # check values
+    assert np.sum(attenuated_ohc) == pytest.approx(220.39149328167292)
+    assert np.sum(bandwith) == pytest.approx(15.041134665207498)
+    assert np.sum(low_knee) == pytest.approx(400.3914932816729)
+    assert np.sum(compression_ratio) == pytest.approx(6.0)
+    assert np.sum(annenuated_ihc) == pytest.approx(129.6085067183270)
+
+
+def test_resample():
+    """Test resample"""
+    np.random.seed(0)
+    sig_len = 600
+    reference_signal = np.random.random(size=sig_len)
+    reference_freq = 12000
+    ref_signal_24, freq_sample_hz = resample_24khz(
+        reference_signal, reference_freq, freq_sample_hz=24000
+    )
+
+    # check shapes
+    assert len(ref_signal_24) == len(reference_signal) * 24000 / reference_freq
+    # check values
+    assert np.sum(np.abs(ref_signal_24)) == pytest.approx(604.1522707137393)
+    assert freq_sample_hz == 24000
+
+
+def test_input_align():
+    """Test input align"""
+    np.random.seed(0)
+    sig_len = 600
+    reference_signal = 100 * np.random.random(size=sig_len)
+    processed_signal = reference_signal.copy()
+    processed_signal[50:] = processed_signal[:-50]
+    processed_signal[0:50] = 0
+
+    ref, proc = input_align(reference_signal, processed_signal)
+
+    assert ref.shape == (600,)
+    assert proc.shape == (600,)
+    assert np.sum(np.abs(ref)) == pytest.approx(29892.167176853407)
+    assert np.sum(np.abs(proc)) == pytest.approx(27199.009291096496)
+
+
+def test_middle_ear():
+    """Test middle ear"""
+    np.random.seed(0)
+    sig_len = 600
+    reference_signal = 100 * np.random.random(size=sig_len)
+    filtered_signal = middle_ear(reference_signal, 24000)
+
+    assert filtered_signal.shape == (600,)
+    assert np.sum(np.abs(filtered_signal)) == pytest.approx(9241.220369749171)
+
+
+def test_gammatone_basilar_membrane():
+    """Test gammatone basilar membrane"""
+    np.random.seed(0)
+    sig_len = 600
+    ref = 100 * np.random.random(size=sig_len)
+    proc = ref + 10 * np.random.random(size=sig_len)
+
+    (
+        reference_envelope,
+        reference_basilar_membrane,
+        processed_envelope,
+        processed_basilar_membrane,
+    ) = gammatone_basilar_membrane(
+        reference=ref,
+        reference_bandwidth=1.4,
+        processed=proc,
+        processed_bandwidth=2.0,
+        freq_sample=24000,
+        center_freq=1000,
+        ear_q=9.26449,
+        min_bandwidth=24.7,
+    )
+
+    # check shapes
+    assert reference_envelope.shape == (600,)
+    assert reference_basilar_membrane.shape == (600,)
+    assert processed_envelope.shape == (600,)
+    assert processed_basilar_membrane.shape == (600,)
+    # check values
+    assert np.sum(np.abs(reference_envelope)) == pytest.approx(3605.427313705984)
+    assert np.sum(np.abs(reference_basilar_membrane)) == pytest.approx(2288.3557465)
+    assert np.sum(np.abs(processed_envelope)) == pytest.approx(4426.111706599469)
+    assert np.sum(np.abs(processed_basilar_membrane)) == pytest.approx(2804.93743475)
+
+
+def test_gammatone_bandwidth_demodulation():
+    """Test gammatone bandwidth demodulation"""
+    centre_freq_sin, centre_freq_cos = gammatone_bandwidth_demodulation(
+        npts=100,
+        tpt=0.001,
+        center_freq=1000,
+        center_freq_cos=np.zeros(100),
+        center_freq_sin=np.zeros(100),
+    )
+    assert centre_freq_sin.shape == (100,)
+    assert centre_freq_cos.shape == (100,)
+    assert np.sum(centre_freq_sin) == pytest.approx(-0.3791946274493412)
+    assert np.sum(centre_freq_cos) == pytest.approx(-0.39460748051808026)
+
+
+@pytest.mark.parametrize(
+    "scale, bw_min, bw_max, expected",
+    [
+        (100.0, 1.0, 2.0, 1.0),
+        (1000000.0, 1.0, 2.0, 2.0),
+        (1000.0, 1.0, 2.0, 1.22),
+    ],
+)
+def test_bandwidth_adjust(scale, bw_min, bw_max, expected):
+    """Test bandwidth adjust"""
+    bw_adjusted = bandwidth_adjust(
+        control=scale * np.array([1, -1, 1]),
+        bandwidth_min=bw_min,
+        bandwidth_max=bw_max,
+        level1=1,
+    )
+    assert bw_adjusted == pytest.approx(expected)
+
+
+def test_env_compress_basilar_membrane():
+    """Test env_compress_basilar_membrane"""
+    np.random.seed(0)
+    sig_len = 600
+    env_sig = np.random.random(size=sig_len)
+    bm = np.random.random(size=sig_len) * 0.001
+    control = np.random.random(size=sig_len)
+    compressed_signal, compressed_basilar_membrane = env_compress_basilar_membrane(
+        env_sig,
+        bm,  # pylint: disable=invalid-name
+        control,
+        attn_ohc=0.01,
+        threshold_low=70.0,
+        compression_ratio=0.1,
+        fsamp=24000,
+        level1=140,
+        small=1e-30,
+        threshold_high=120,
+    )
+    # check shapes
+    assert compressed_signal.shape == (600,)
+    assert compressed_basilar_membrane.shape == (600,)
+    # check values
+    assert np.mean(np.abs(compressed_signal)) == pytest.approx(15486012153068.807)
+    assert np.mean(np.abs(compressed_basilar_membrane)) == pytest.approx(
+        15415471156.59357
+    )
+
+
+# envelope_align
+def test_envelope_align():
+    """Test envelope align"""
+
+    np.random.seed(0)
+    sig_len = 600
+    scale = 1.1
+    reference = np.random.random(size=sig_len)
+
+    # Make output look like a shifted copy of the reference
+    output = reference.copy()
+    output[50:] = output[:-50]
+    output[0:50] = 0
+    output *= scale
+
+    aligned_output = envelope_align(
+        reference, output, freq_sample=24000, corr_range=100
+    )
+
+    # check shapes and values
+    assert aligned_output.shape == (600,)
+    assert np.sum(np.abs(aligned_output)) == pytest.approx(299.1891022020615)
+    # Check output is now aligned with the reference
+    assert aligned_output[100] == pytest.approx(scale * reference[100])
+
+
+# envelope_sl
+def test_envelope_sl():
+    """Test envelope sl"""
+
+    np.random.seed(0)
+    sig_len = 600
+    ref = np.random.random(size=sig_len)
+    bm = np.random.random(size=sig_len) * 0.001
+
+    reference, basilar_membrane = envelope_sl(
+        reference=ref,
+        basilar_membrane=bm,
+        attnenuated_ihc=40.0,
+        level1=120,
+        small=1e-30,
+    )
+
+    assert reference.shape == (600,)
+    assert basilar_membrane.shape == (600,)
+    assert np.sum(np.abs(reference)) == pytest.approx(42746.12859151134)
+    assert np.sum(np.abs(basilar_membrane)) == pytest.approx(98.97646233693762)
+
+
+# inner_hair_cell_adaptation
+def test_inner_hair_cell_adaptation():
+    """Test inner hair cell adaptation"""
+
+    np.random.seed(0)
+    sig_len = 600
+    ref = np.random.random(size=sig_len)
+    bm = np.random.random(size=sig_len) * 0.001
+
+    output_db, output_basilar_membrane = inner_hair_cell_adaptation(
+        reference_db=ref, reference_basilar_membrane=bm, delta=1.00, freq_sample=24000
+    )
+
+    # check shapes and values
+    assert output_db.shape == (600,)
+    assert output_basilar_membrane.shape == (600,)
+    assert np.sum(np.abs(output_db)) == pytest.approx(298.9359292744365)
+    assert np.sum(np.abs(output_basilar_membrane)) == pytest.approx(0.2963082865723811)
+
+
+# basilar_membrane_add_noise
+def test_basilar_membrane_add_noise():
+    """Test basilar membrane add noise"""
+
+    np.random.seed(0)
+    sig_len = 600
+    ref = np.random.random(size=sig_len)
+
+    noisy_reference = basilar_membrane_add_noise(
+        reference=ref, threshold=40, level1=120
+    )
+
+    # check shapes and values and that signal has changed
+    assert noisy_reference.shape == (600,)
+    assert np.sum(np.abs(noisy_reference)) == pytest.approx(298.919051930547)
+    assert not np.allclose(noisy_reference, ref)
+
+    # Check that adding on nearly 0 noise (-100 db) doesn't change the signal
+    noisy_reference = basilar_membrane_add_noise(
+        reference=ref, threshold=-100, level1=120
+    )
+    assert np.allclose(noisy_reference, ref)
+
+
+# group_delay_compensate
+def test_group_delay_compensate():
+    """Test group delay compensate"""
+
+    np.random.seed(0)
+    sig_len = 600
+    reference = np.random.random(size=(4, sig_len))
+
+    processed = group_delay_compensate(
+        reference=reference,
+        bandwidths=np.array([30, 60, 90, 120]),
+        center_freq=np.array([100, 200, 300, 400]),
+        freq_sample=24000,
+        ear_q=9.26449,
+        min_bandwidth=24.7,
+    )
+
+    # check shapes and values
+    assert processed.shape == (4, 600)
+    assert np.sum(np.abs(processed)) == pytest.approx(1193.8088344682358)
+
+
+def test_convert_rms_to_sl():
+    """Test convert rms to sl"""
+
+    np.random.seed(0)
+    sig_len = 600
+    reference = np.random.random(size=sig_len)
+    control = np.random.random(size=sig_len)
+
+    ref_db = convert_rms_to_sl(
+        reference=reference,
+        control=control,
+        attnenuated_ohc=0.1,
+        threshold_low=40,
+        compression_ratio=10,
+        attnenuated_ihc=0.1,
+        level1=120,
+        threshold_high=100,
+        small=1e-30,
+    )
+
+    # check shapes and values
+    assert ref_db.shape == (600,)
+    assert np.sum(np.abs(ref_db)) == pytest.approx(34746.74406262155)
+
+
+def test_env_smooth():
+    """Test env smooth"""
+
+    np.random.seed(0)
+    sig_len = 600
+    segment_size = 1  # ms
+    envelopes = np.random.random(size=(4, sig_len))
+    sample_freq = 24000
+
+    smooth = env_smooth(
+        envelopes=envelopes, segment_size=segment_size, freq_sample=sample_freq
+    )
+
+    # check shapes and values
+    expected_length = 2 * sig_len / (sample_freq * segment_size / 1000)
+    assert smooth.shape == (4, expected_length)
+    assert np.sum(np.abs(smooth)) == pytest.approx(100.7397658862719)
+
+
+def test_mel_cepstrum_correlation():
+    """Test mel cepstrum correlation"""
+
+    np.random.seed(0)
+    sig_len = 600
+    reference = np.random.random(size=(4, sig_len))
+    distorted = np.random.random(size=(4, sig_len))
+    addnoise = 0.0001
+
+    # self correlation should produce scores of 1.0
+    (
+        ave_cepstral_correlation,
+        individual_cepstral_correlation,
+    ) = mel_cepstrum_correlation(
+        reference=reference, distorted=reference, threshold=-20, addnoise=addnoise
+    )
+
+    # check shapes and values
+    assert ave_cepstral_correlation == pytest.approx(1.0)
+    assert individual_cepstral_correlation == pytest.approx(1.0)
+
+    # correlation between two random signals should be low
+    (
+        ave_cepstral_correlation,
+        individual_cepstral_correlation,
+    ) = mel_cepstrum_correlation(
+        reference=reference, distorted=distorted, threshold=-20, addnoise=addnoise
+    )
+
+    # check shapes and values
+    assert np.array(individual_cepstral_correlation).shape == (6,)
+    assert ave_cepstral_correlation == pytest.approx(0.04195483905166838)
+    assert individual_cepstral_correlation == pytest.approx(
+        np.array(
+            [0.04582154, 0.04306849, 0.02364514, 0.07634693, 0.02364514, 0.04306849]
+        )
+    )
+
+
+def test_melcor9():
+    """Test melcor9"""
+
+    np.random.seed(0)
+    sig_len = 6000
+    reference = 20 * np.random.random(size=(4, sig_len))
+    distorted = 20 * np.random.random(size=(4, sig_len))  # noqa: F841
+
+    # TODO: This is always returning 0's :-()
+    mel_cep_ave, mel_cep_low, mel_cep_high, mel_cep_mod = melcor9(
+        reference=reference,
+        distorted=distorted,
+        threshold=12,
+        add_noise=0.00,
+        segment_size=4,  # ms
+        n_cepstral_coef=6,
+    )
+
+    assert mel_cep_mod.shape == (8,)
+
+    assert mel_cep_ave == pytest.approx(0.07847183114015181)
+    assert mel_cep_low == pytest.approx(0.08244466051783299)
+    assert mel_cep_high == pytest.approx(0.07449900176247062)
+    assert mel_cep_mod == pytest.approx(
+        np.array(
+            [
+                0.08074834547870297,
+                0.035224248042312015,
+                0.10987800587454441,
+                0.10392804267577263,
+                0.12997273983987667,
+                0.09738887779419407,
+                0.06127608591547042,
+                0.009358303500341338,
+            ]
+        )
+    )
+
+
+def test_melcor9_equal_input():
+    """Test melcor9"""
+
+    np.random.seed(0)
+    sig_len = 6000
+    reference = 20 * np.random.random(size=(4, sig_len))
+
+    mel_cep_ave, mel_cep_low, mel_cep_high, mel_cep_mod = melcor9(
+        reference=reference,
+        distorted=reference,
+        threshold=12,
+        add_noise=0.00,
+        segment_size=4,  # ms
+        n_cepstral_coef=6,
+    )
+
+    assert mel_cep_mod.shape == (8,)
+
+    assert mel_cep_ave == pytest.approx(1.0)
+    assert mel_cep_low == pytest.approx(1.0)
+    assert mel_cep_high == pytest.approx(1.0)
+    assert mel_cep_mod == pytest.approx(
+        np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])
+    )
+
+
+def test_melcor9_crosscovmatrix():
+    """Test melcor9 crosscovmatrix"""
+
+    np.random.seed(0)
+    sig_len = 600
+    n_modulations = 4
+    n_basis = 6
+    reference = np.random.random(size=(n_basis, sig_len))
+    processed = np.random.random(size=(n_basis, sig_len))
+    basilar_membrane = np.random.random(size=(n_modulations, sig_len))
+
+    cross_cov_matrix = melcor9_crosscovmatrix(
+        b=basilar_membrane,
+        nmod=n_modulations,  # n modulation channels
+        nbasis=n_basis,  # n cepstral coefficient
+        nsamp=sig_len,
+        nfir=32,
+        reference_cep=reference,
+        processed_cep=processed,
+    )
+
+    assert cross_cov_matrix.shape == (n_modulations, n_basis)
+    assert np.mean(cross_cov_matrix) == pytest.approx(0.9981200876213588)
+
+
+def test_spectrum_diff():
+    """Test spectrum diff"""
+
+    np.random.seed(0)
+    sig_len = 600
+    reference = np.random.random(size=(4, sig_len))
+    processed = np.random.random(size=(4, sig_len))
+
+    dloud, dnorm, dslope = spectrum_diff(reference_sl=reference, processed_sl=processed)
+
+    # Check shapes
+    assert dloud.shape == (3,)
+    assert dnorm.shape == (3,)
+    assert dslope.shape == (3,)
+
+    # Check values
+    assert dloud == pytest.approx(
+        np.array([0.037373053253378426, 7.671407491067586e-05, 4.762048775653672e-05])
+    )
+    assert dnorm == pytest.approx(
+        np.array([44.84779838405483, 0.09203844008264382, 0.0570982338215042])
+    )
+    assert dslope == pytest.approx(
+        np.array([0.03918063966807504, 0.00010878329523853699, 8.006096597686129e-05])
+    )
+
+
+def test_bm_covary_ok():
+    """Test bm covary"""
+
+    np.random.seed(0)
+    sig_len = 600
+    sample_freq = 24000
+    segment_size = 4
+    reference = np.random.random(size=(4, sig_len))
+    processed = np.random.random(size=(4, sig_len))
+
+    signal_cross_cov, ref_mean_square, proc_mean_square = bm_covary(
+        reference_basilar_membrane=reference,
+        processed_basilar_membrane=reference + 0.4 * processed,
+        segment_size=segment_size,
+        freq_sample=sample_freq,
+    )
+
+    # Check shapes
+    assert signal_cross_cov.shape == (4, 12)
+    assert ref_mean_square.shape == (4, 12)
+    assert proc_mean_square.shape == (4, 12)
+
+    # Check values
+    assert np.sum(np.abs(signal_cross_cov)) == pytest.approx(46.43935095481214)
+    assert np.sum(np.abs(ref_mean_square)) == pytest.approx(16.65809872708418)
+    assert np.sum(np.abs(proc_mean_square)) == pytest.approx(26.110255782462076)
+
+
+def test_bm_covary_error():
+    """Test bm covary fails when segment size too small"""
+
+    np.random.seed(0)
+    sig_len = 600
+    sample_freq = 24000
+    segment_size = 2  # needs to be a little over 2 ms
+    reference = np.random.random(size=(4, sig_len))
+    processed = np.random.random(size=(4, sig_len))
+
+    with pytest.raises(ValueError):
+        _signal_cross_cov, _ref_mean_square, _proc_mean_square = bm_covary(
+            reference_basilar_membrane=reference,
+            processed_basilar_membrane=reference + 0.4 * processed,
+            segment_size=segment_size,
+            freq_sample=sample_freq,
+        )
+
+
+def test_ave_covary2():
+    """Test ave covary2"""
+    np.random.seed(0)
+    sig_len = 600
+
+    signal_cross_cov = np.random.random(size=(4, sig_len))
+    ref_mean_square = np.random.random(size=(4, sig_len))
+
+    ave_covariance, ihc_sync_covariance = ave_covary2(
+        signal_cross_covariance=signal_cross_cov,
+        reference_signal_mean_square=ref_mean_square,
+        threshold_db=0.6,
+        lp_filter_order=np.array([1, 3, 5, 5, 5, 5]),
+        freq_cutoff=1000 * np.array([1.5, 2.0, 2.5, 3.0, 3.5, 4.0]),
+    )
+
+    assert len(ihc_sync_covariance) == 6
+
+    assert ave_covariance == pytest.approx(0.5129961720524688)
+    assert np.sum(ihc_sync_covariance) == pytest.approx(3.057984614887033)
+
+    assert ihc_sync_covariance == pytest.approx(
+        [
+            0.5072428879657319,
+            0.5051636721505166,
+            0.5087880598896098,
+            0.511743935480639,
+            0.5124431322756856,
+            0.5126029271248499,
+        ]
+    )
diff --git a/tests/evaluator/haspi/test_ebm.py b/tests/evaluator/haspi/test_ebm.py
new file mode 100644
index 000000000..92c2e78b2
--- /dev/null
+++ b/tests/evaluator/haspi/test_ebm.py
@@ -0,0 +1,171 @@
+"""Tests for ebm module"""
+import numpy as np
+import pytest
+
+from clarity.evaluator.haspi.ebm import (
+    add_noise,
+    cepstral_correlation_coef,
+    env_filter,
+    fir_modulation_filter,
+    modulation_cross_correlation,
+)
+
+
+def test_env_filter_ok() -> None:
+    """Test for env_filter function"""
+    reference_env, processed_env = env_filter(
+        reference_db=np.array([[80, 80, 80, 80]]),
+        processed_db=np.array([[40, 50, 60, 70]]),
+        filter_cutoff=1000,
+        freq_sub_sample=4000,
+        freq_samp=16000,
+    )
+    assert reference_env == pytest.approx(48.84481105)
+    assert processed_env == pytest.approx(32.32800458)
+
+
+@pytest.mark.parametrize(
+    "ref_db, proc_db, filter_cutoff, freq_sub_sample, freq_sample, expected",
+    [
+        (
+            np.array([[80, 80, 80, 80]]),
+            np.array([[40, 50, 60, 70]]),
+            4000,
+            1000,
+            16000,
+            ValueError,
+        ),
+        (
+            np.array([[80, 80, 80, 80]]),
+            np.array([[40, 50, 60, 70]]),
+            4000,
+            32000,
+            16000,
+            ValueError,
+        ),
+        (
+            np.array([[80, 80, 80, 80]]),
+            np.array([40, 50, 60, 70]),  # Wrong shape
+            1000,
+            4000,
+            16000,
+            ValueError,
+        ),
+    ],
+)
+def test_env_filter_error(
+    ref_db, proc_db, filter_cutoff, freq_sub_sample, freq_sample, expected
+) -> None:
+    """Test for env_filter function"""
+    with pytest.raises(expected):
+        _reference_env, _processed_env = env_filter(
+            reference_db=ref_db,
+            processed_db=proc_db,
+            filter_cutoff=filter_cutoff,
+            freq_sub_sample=freq_sub_sample,
+            freq_samp=freq_sample,
+        )
+
+
+def test_cepstral_correlation_coef_ok() -> None:
+    """Test for cepstral_correlation_coef function"""
+    np.random.seed(0)
+    n_basis = 4
+    sig_len = 100
+    n_bands = 2
+    random_sig1 = 100 * np.random.normal(size=(sig_len, n_bands))
+    random_sig2 = 100 * np.random.normal(size=(sig_len, n_bands))
+    ref_cep, proc_cep = cepstral_correlation_coef(
+        reference_db=random_sig1,
+        processed_db=random_sig1 + random_sig2,
+        thresh_cep=10.0,
+        thresh_nerve=10.0,
+        nbasis=n_basis,
+    )
+    assert ref_cep.shape == (72, n_basis)
+    assert proc_cep.shape == (72, n_basis)
+    assert np.mean(np.abs(ref_cep)) == pytest.approx(82.4395877477838)
+    assert np.mean(np.abs(proc_cep)) == pytest.approx(110.74820232861624)
+
+
+def test_cepstral_correlation_coef_error() -> None:
+    """Test for cepstral_correlation_coef function"""
+    np.random.seed(0)
+    n_basis = 4
+    sig_len = 100
+    n_bands = 2
+    random_sig1 = 100 * np.random.normal(size=(sig_len, n_bands))
+    random_sig2 = 100 * np.random.normal(size=(sig_len, n_bands))
+    with pytest.raises(ValueError):
+        _ref_cep, _proc_cep = cepstral_correlation_coef(
+            reference_db=random_sig1,
+            processed_db=random_sig1 + random_sig2,
+            thresh_cep=1000.0,  # Crazy high threshold to test that error is raised
+            thresh_nerve=1000.0,
+            nbasis=n_basis,
+        )
+
+
+def test_add_noise() -> None:
+    """Test for add_noise function"""
+    np.random.seed(0)
+
+    reference = 100 * np.random.normal(size=(100, 4))
+    noisy_reference = add_noise(reference, 100.0)
+    assert np.mean(np.abs(noisy_reference)) == pytest.approx(109.84025018392005)
+    assert noisy_reference.shape == reference.shape
+
+    # Add 0 dB noise should not change the signal
+    reference = 100 * np.random.normal(size=(100, 4))
+    noisy_reference = add_noise(reference, 0)
+    assert np.allclose(noisy_reference, reference)
+
+
+def test_fir_modulation_filter() -> None:
+    """Test for fir_modulation_filter function"""
+    np.random.seed(0)
+
+    in_center_freqs = np.array([2, 6, 10, 16, 25, 40, 64, 100])
+
+    ref_mod, proc_mod, out_center_freqs = fir_modulation_filter(
+        reference_envelope=100 * np.random.normal(size=(100, 4)),
+        processed_envelope=100 * np.random.normal(size=(100, 4)),
+        freq_sub_sampling=1000,
+        # Adding a couple of invalid center freqs to check that they are removed
+        center_frequencies=np.append(in_center_freqs, [500, 2000]),
+    )
+
+    # Check return value have expected shape
+    assert ref_mod.shape == (4, len(in_center_freqs), 100)
+    assert proc_mod.shape == (4, len(in_center_freqs), 100)
+    assert out_center_freqs.shape == in_center_freqs.shape
+    # Check return value have expected values
+    assert np.allclose(in_center_freqs, out_center_freqs)
+    assert np.mean(np.abs(ref_mod)) == pytest.approx(14.477262943878237)
+    assert np.mean(np.abs(proc_mod)) == pytest.approx(13.85669526424176)
+
+
+def test_modulation_cross_correlation() -> None:
+    """Test for modulation_cross_correlation function"""
+    np.random.seed(0)
+
+    n_cepstral_bands = 5
+    n_mod_filters = 6
+    signal_length = 100
+    signal_shape = (n_cepstral_bands, n_mod_filters, signal_length)
+    ref_mod = 100 * np.random.normal(size=signal_shape)
+    proc_mod = 100 * np.random.normal(size=signal_shape)
+
+    # All channels should be perfectly correlated with themselves
+    cross_corr = modulation_cross_correlation(
+        reference_modulation=ref_mod, processed_modulation=ref_mod
+    )
+    assert cross_corr.shape == (n_mod_filters,)
+    assert cross_corr == pytest.approx(1.0)
+
+    # Check an expected value for a pair of random signals
+    cross_corr = modulation_cross_correlation(
+        reference_modulation=ref_mod, processed_modulation=proc_mod
+    )
+    assert cross_corr.shape == (n_mod_filters,)
+    assert np.sum(cross_corr) == pytest.approx(0.4414165879317118)
diff --git a/tests/evaluator/haspi/test_haspi.py b/tests/evaluator/haspi/test_haspi.py
new file mode 100644
index 000000000..5e8dce1fd
--- /dev/null
+++ b/tests/evaluator/haspi/test_haspi.py
@@ -0,0 +1,48 @@
+"""Tests for hasqi module"""
+import numpy as np
+import pytest
+
+from clarity.evaluator.haspi import haspi_v2, haspi_v2_be
+
+
+def test_haspi_v2() -> None:
+    """Test for hasqi_v2 index"""
+    np.random.seed(0)
+    sample_rate = 16000
+    x = np.random.uniform(-1, 1, int(sample_rate * 0.5))  # i.e. 500 ms of audio
+    y = np.random.uniform(-1, 1, int(sample_rate * 0.5))
+
+    hearing_loss = np.array([45, 45, 35, 45, 60, 65])
+    level1 = 65
+
+    score, _ = haspi_v2(x, sample_rate, y + x, sample_rate, hearing_loss, level1)
+    assert score == pytest.approx(0.043808448934532965, rel=1e-7)
+
+
+def test_haspi_v2_better_ear() -> None:
+    """Test for hasqi_v2_better_ear index"""
+
+    np.random.seed(0)
+    sample_rate = 16000
+    hl_left = np.array([25, 25, 25, 25, 40, 65])
+    hl_right = np.array([45, 45, 35, 45, 60, 65])
+
+    freqs = np.array([250, 500, 1000, 2000, 4000, 6000])
+    ref_left = np.random.uniform(-1, 1, int(sample_rate * 0.5))  # i.e. 500 ms of audio
+    ref_right = np.random.uniform(-1, 1, int(sample_rate * 0.5))
+    proc_left = np.random.uniform(-1, 1, int(sample_rate * 0.5))  # i.e. 500 ms of audio
+    proc_right = np.random.uniform(-1, 1, int(sample_rate * 0.5))
+
+    score = haspi_v2_be(
+        reference_left=ref_left,
+        reference_right=ref_right,
+        processed_left=proc_left + ref_left,
+        processed_right=proc_right,
+        fs_signal=sample_rate,
+        audiogram_left=hl_left,
+        audiogram_right=hl_right,
+        audiogram_cfs=freqs,
+        level=100,
+    )
+
+    assert score == pytest.approx(0.839975335323691)
diff --git a/tests/evaluator/haspi/test_ip.py b/tests/evaluator/haspi/test_ip.py
new file mode 100644
index 000000000..e13885e8e
--- /dev/null
+++ b/tests/evaluator/haspi/test_ip.py
@@ -0,0 +1,94 @@
+"""Tests for ip module"""
+import numpy as np
+import pytest
+
+from clarity.evaluator.haspi.ip import (
+    get_neural_net,
+    nn_feed_forward,
+    nn_feed_forward_ensemble,
+)
+
+
+def test_get_neural_net() -> None:
+    """Test for get_neural_net function"""
+
+    # Retrieve the standard HASPI neural network
+    (
+        neural_net_params,
+        weights_hidden,
+        weights_out,
+        normalization_factor,
+    ) = get_neural_net()
+
+    # Check that the correct parameters exist
+    assert neural_net_params.keys() == {
+        "input_layer",
+        "hidden_layer",
+        "output_layer",
+        "activation_function",
+        "offset_activation",
+        "maximum_activation",
+    }
+
+    n_hidden_nodes = neural_net_params["hidden_layer"]
+    n_output_nodes = neural_net_params["output_layer"]
+    n_input_nodes = neural_net_params["input_layer"]
+
+    # Check weights are correct shape for the number of nodes
+    assert len(weights_out[0]) == n_hidden_nodes + 1
+    assert len(weights_out[0][0]) == n_output_nodes
+    assert len(weights_hidden[0]) == n_input_nodes + 1
+    assert len(weights_hidden[0][0]) == n_hidden_nodes
+    # Check that the number of networks in the ensembles match
+    assert len(weights_out) == len(weights_hidden)
+    # Check the specific values of the weights are correct
+    assert normalization_factor == 0.9508
+    assert np.sum(weights_out) == pytest.approx(-33.8515)
+    assert np.sum(weights_hidden) == pytest.approx(-670.8200)
+
+
+def test_nn_feed_forward_ensemble() -> None:
+    """Test for nn_feed_forward_ensemble function"""
+
+    # Retrieve the standard HASPI neural network
+    (
+        neural_net_params,
+        weights_hidden,
+        weights_out,
+        _normalization_factor,
+    ) = get_neural_net()
+
+    # Make some fake data matching the input layer size
+    data = np.ones((1, neural_net_params["input_layer"]))
+
+    # Check that the output is correct
+    x = nn_feed_forward_ensemble(data, neural_net_params, weights_hidden, weights_out)
+    assert x == pytest.approx(0.95078679)
+
+
+def test_nn_feed_forward() -> None:
+    """Test for nn_feed_forward function"""
+
+    # Retrieve the standard HASPI neural network
+    (
+        neural_net_params,
+        weights_hidden,
+        weights_out,
+        _normalization_factor,
+    ) = get_neural_net()
+
+    # Make some fake data matching the input layer size
+    data = np.ones(neural_net_params["input_layer"])
+
+    # Check that the output is correct for first network in ensemble
+    hidden, output = nn_feed_forward(
+        data, neural_net_params, weights_hidden[0], weights_out[0]
+    )
+    # Note, it adds a constant 1 to the start of the output and hidden layers
+    assert hidden.shape == (neural_net_params["hidden_layer"] + 1,)
+    assert output.shape == (neural_net_params["output_layer"] + 1,)
+    assert hidden[0] == pytest.approx(1.0)
+    assert output[0] == pytest.approx(1.0)
+    # Check that the mean of the hidden and output layers are correct
+    assert np.mean(hidden) == pytest.approx(0.599999906437356)
+    assert np.mean(output) == pytest.approx(0.982229820658455)
diff --git a/tests/evaluator/hasqi/test_hasqi.py b/tests/evaluator/hasqi/test_hasqi.py
index 08b78315b..6d5eb7ec2 100644
--- a/tests/evaluator/hasqi/test_hasqi.py
+++ b/tests/evaluator/hasqi/test_hasqi.py
@@ -2,7 +2,7 @@
 import numpy as np
 import pytest
 
-from clarity.evaluator.hasqi import hasqi_v2
+from clarity.evaluator.hasqi import hasqi_v2, hasqi_v2_better_ear
 
 
 def test_hasqi_v2() -> None:
@@ -20,3 +20,33 @@ def test_hasqi_v2() -> None:
         x, sample_rate, y, sample_rate, hearing_loss, equalisation_mode, level1
     )
     assert score == pytest.approx(0.002525809, rel=1e-7)
+
+
+def test_hasqi_v2_better_ear() -> None:
+    """Test for hasqi_v2_better_ear index"""
+
+    np.random.seed(0)
+    sample_rate = 16000
+    hl_left = np.array([25, 25, 25, 25, 40, 65])
+    hl_right = np.array([45, 45, 35, 45, 60, 65])
+
+    freqs = np.array([250, 500, 1000, 2000, 4000, 6000])
+    ref_left = np.random.uniform(-1, 1, int(sample_rate * 0.5))  # i.e. 500 ms of audio
+    ref_right = np.random.uniform(-1, 1, int(sample_rate * 0.5))
+    proc_left = np.random.uniform(-1, 1, int(sample_rate * 0.5))  # i.e. 500 ms of audio
+    proc_right = np.random.uniform(-1, 1, int(sample_rate * 0.5))
+
+    score = hasqi_v2_better_ear(
+        reference_left=ref_left,
+        reference_right=ref_right,
+        processed_left=proc_left + ref_left,
+        processed_right=proc_right,
+        sample_freq=sample_rate,
+        audiogram_left=hl_left,
+        audiogram_right=hl_right,
+        audiogram_frequencies=freqs,
+        level=100,
+        audiogram_freq=None,
+    )
+
+    assert score == pytest.approx(0.1256893032667640)
diff --git a/tests/evaluator/mbstoi/test_mbstoi.py b/tests/evaluator/mbstoi/test_mbstoi.py
new file mode 100644
index 000000000..6a2c7fcf1
--- /dev/null
+++ b/tests/evaluator/mbstoi/test_mbstoi.py
@@ -0,0 +1,36 @@
+"""Tests for mbstoi module"""
+
+# mbstoi
+
+import numpy as np
+import pytest
+
+from clarity.evaluator.mbstoi import mbstoi
+
+
+def test_mbstoi() -> None:
+    """Test for mbstoi function"""
+    np.random.seed(0)
+    sig_len = 8000
+    sample_freq = 10000
+
+    left_clean = 100 * np.random.random(size=sig_len)
+    right_clean = left_clean.copy()
+    right_clean[4:] = right_clean[:-4]
+    left_noisy = left_clean + 30 * np.random.random(size=sig_len)
+    right_noisy = right_clean + 30 * np.random.random(size=sig_len)
+
+    mbstoi_val = mbstoi(
+        left_ear_clean=left_clean,
+        right_ear_clean=right_clean,
+        left_ear_noisy=left_noisy,
+        right_ear_noisy=right_noisy,
+        fs_signal=sample_freq,  # signal sample rate
+        sample_rate=9000,  # operating sample rate
+        fft_size_in_samples=64,
+        n_third_octave_bands=5,
+        centre_freq_first_third_octave_hz=500,
+        dyn_range=60,
+    )
+
+    assert mbstoi_val == pytest.approx(0.9061193314307591)
diff --git a/tests/evaluator/mbstoi/test_mbstoi_utils.py b/tests/evaluator/mbstoi/test_mbstoi_utils.py
new file mode 100644
index 000000000..daf782fbd
--- /dev/null
+++ b/tests/evaluator/mbstoi/test_mbstoi_utils.py
@@ -0,0 +1,245 @@
+"""Tests for mbstoi_utils module"""
+import numpy as np
+import pytest
+
+from clarity.evaluator.mbstoi.mbstoi_utils import (
+    _firstpartfunc,
+    _fourthpartfunc,
+    _secondpartfunc,
+    _thirdpartfunc,
+    equalisation_cancellation,
+    find_delay_impulse,
+    remove_silent_frames,
+    stft,
+    thirdoct,
+)
+
+
+def test_equalisation_cancellation() -> None:
+    """Test for equalisation_cancellation function"""
+
+    np.random.seed(0)
+    n_freq_channels = 5
+    n_samples = 50
+    n_frames = 30
+    n_taus = 20
+    n_gammas = 40
+    centre_frequencies = np.array([940.0, 1190.0, 1500.0])
+
+    left_ear_clean_hat = np.random.random(size=(n_freq_channels, n_samples))
+    right_ear_clean_hat = np.random.random(size=(n_freq_channels, n_samples))
+    left_ear_noisy_hat = np.random.random(size=(n_freq_channels, n_samples))
+    right_ear_noisy_hat = np.random.random(size=(n_freq_channels, n_samples))
+    frequency_band_edges_indices = np.array([[1, 2], [2, 1], [2, 1]])
+    n_gamma_bands = len(centre_frequencies)
+    taus = np.random.random(size=n_taus)
+    gammas = np.random.random(size=n_gammas)
+    intell_grid = np.random.random(size=(n_taus, n_gammas))
+    p_ec_max = np.random.random(size=(n_gamma_bands, n_gammas))
+    sigma_epsilon = np.random.random(size=n_gammas)
+    sigma_delta = np.random.random(size=n_taus)
+
+    intermediate_grid, p_ec_m = equalisation_cancellation(
+        left_ear_clean_hat=left_ear_clean_hat,
+        right_ear_clean_hat=right_ear_clean_hat,
+        left_ear_noisy_hat=left_ear_noisy_hat,
+        right_ear_noisy_hat=right_ear_noisy_hat,
+        n_third_octave_bands=n_gamma_bands,
+        n_frames=n_frames,
+        frequency_band_edges_indices=frequency_band_edges_indices,
+        centre_frequencies=centre_frequencies,
+        taus=taus,
+        n_taus=n_taus,
+        gammas=gammas,
+        n_gammas=n_gammas,
+        intermediate_intelligibility_measure_grid=intell_grid,
+        p_ec_max=p_ec_max,
+        sigma_epsilon=sigma_epsilon,
+        sigma_delta=sigma_delta,
+    )
+
+    # check shapes
+    assert intermediate_grid.shape == (n_taus, n_gammas)
+    assert p_ec_m.shape == (n_gamma_bands, n_gammas)
+
+    # check values
+    assert np.sum(intermediate_grid) == pytest.approx(277.84700307853785)
+    assert np.sum(p_ec_m) == pytest.approx(73.14293826967146)
+
+
+def test_firstpartfunc() -> None:
+    """Test for firstpartfunc function"""
+
+    np.random.seed(0)
+    n_taus = 100
+    n_gammas = 40
+    result = _firstpartfunc(
+        L1=np.random.random(size=(1, 30)),
+        L2=np.random.random(size=(1, 30)),
+        R1=np.random.random(size=(1, 30)),
+        R2=np.random.random(size=(1, 30)),
+        n_taus=n_taus,
+        gammas=np.random.random(size=(1, n_gammas)),
+        epsexp=np.random.random(size=(1, n_gammas)),
+    )
+    assert result.shape == (n_taus, n_gammas)
+    assert np.sum(result) == pytest.approx(405590.0256254587)
+
+
+def test_secondpartfunc() -> None:
+    """Test for secndpartfunc function"""
+
+    np.random.seed(0)
+    n_taus = 100
+    n_gammas = 40
+
+    result = _secondpartfunc(
+        L1=np.random.random(size=(1, 30)),
+        L2=np.random.random(size=(1, 30)),
+        rho1=np.random.random(size=(1, 30)),
+        rho2=np.random.random(size=(1, 30)),
+        tauexp=np.random.random(size=(1, n_taus)),
+        epsdelexp=np.random.random(size=(n_taus, n_gammas)),
+        gammas=np.random.random(size=(1, n_gammas)),
+    )
+    assert result.shape == (n_taus, n_gammas)
+    assert np.sum(result) == pytest.approx(127394.91897484378)
+
+
+def test_thirdpartfunc() -> None:
+    """Test for thirdpartfunc function"""
+
+    np.random.seed(0)
+    n_taus = 100
+    n_gammas = 40
+
+    result = _thirdpartfunc(
+        R1=np.random.random(size=(1, 30)),
+        R2=np.random.random(size=(1, 30)),
+        rho1=np.random.random(size=(1, 30)),
+        rho2=np.random.random(size=(1, 30)),
+        tauexp=np.random.random(size=(1, n_taus)),
+        epsdelexp=np.random.random(size=(n_taus, n_gammas)),
+        gammas=np.random.random(size=(1, n_gammas)),
+    )
+    assert result.shape == (n_taus, n_gammas)
+    assert np.sum(result) == pytest.approx(9483.482148264484)
+
+
+def test_fourthpartfunc() -> None:
+    """Test for fourthpartfunc function"""
+
+    np.random.seed(0)
+    n_taus = 100
+    n_gammas = 40
+
+    result = _fourthpartfunc(
+        rho1=np.random.random(size=(1, 30)),
+        rho2=np.random.random(size=(1, 30)),
+        tauexp2=np.random.random(size=(1, n_taus)),
+        n_gammas=n_gammas,
+        deltexp=np.random.random(size=(1, n_taus)),
+    )
+    assert result.shape == (n_taus, n_gammas)
+    assert np.sum(result) == pytest.approx(75883.96658627654)
+
+
+def test_stft() -> None:
+    """Test for stft function"""
+    np.random.seed(0)
+
+    fft_size = 32
+    signal = np.random.random(size=600)
+    stft_out = stft(signal, win_size=128, fft_size=fft_size)
+
+    assert stft_out.shape == (8, fft_size)
+    assert np.sum(stft_out) == pytest.approx(0.07977179832430759)
+
+
+def test_remove_silent_frames() -> None:
+    """Test for remove_silent_frames function"""
+
+    np.random.seed(0)
+    n_frames = 600
+
+    left_ear_clean = np.random.random(n_frames)
+    right_ear_clean = np.random.random(n_frames)
+    left_ear_clean[0:300] = 0
+    left_ear_noisy = left_ear_clean + np.random.random(n_frames)
+    right_ear_noisy = right_ear_clean + np.random.random(n_frames)
+
+    (xl_sil, xr_sil, yl_sil, yr_sil) = remove_silent_frames(
+        left_ear_clean=left_ear_clean,
+        right_ear_clean=right_ear_clean,
+        left_ear_noisy=left_ear_noisy,
+        right_ear_noisy=right_ear_noisy,
+        dynamic_range=40,
+        frame_length=64,
+        hop=32,
+    )
+
+    assert np.sum(xl_sil[0:300]) == pytest.approx(0.0)
+    assert np.sum(xl_sil) == pytest.approx(128.15949932062742)
+    assert np.sum(xr_sil) == pytest.approx(272.06229083012374)
+    assert np.sum(yl_sil) == pytest.approx(413.65823132789836)
+    assert np.sum(yr_sil) == pytest.approx(551.1857227061379)
+
+
+def test_thirdoct() -> None:
+    """Test for thirdoct function"""
+    (
+        octave_band_matrix,
+        centre_frequencies,
+        frequency_band_edges_indices,
+        freq_low,
+        freq_high,
+    ) = thirdoct(frequency_sampling=8000, nfft=512, num_bands=3, min_freq=100)
+
+    assert freq_low == pytest.approx([93.75, 109.375, 140.625])
+    assert freq_high == pytest.approx([109.375, 140.625, 171.875])
+    assert centre_frequencies == pytest.approx(
+        np.array([[100.0, 125.99210499, 158.7401052]])
+    )
+    assert octave_band_matrix.shape == (3, 257)
+    assert np.sum(octave_band_matrix) == pytest.approx(5.0)
+    assert frequency_band_edges_indices == pytest.approx(
+        np.array([[7, 7], [8, 9], [10, 11]])
+    )
+
+
+@pytest.mark.parametrize(
+    "lag, n_samples, initial_value",
+    [
+        (20, 1000, 0),
+        (20, 1000, 10),
+        (10, 500, 0),
+        (10, 500, 10),
+        (10, 500, -10),
+        (0, 1000, 10),
+    ],
+)
+def test_find_delay_impulse(lag, n_samples, initial_value) -> None:
+    """Test for find_delay_impulse function with lag applied in both directions"""
+    np.random.seed(0)
+
+    # Make channel 0 lagged relative to channel 1 and check lag is recovered
+    ddf = np.random.random(size=(n_samples, 4))
+    if lag != 0:
+        ddf[:-lag, 1] = ddf[lag:, 0]
+    else:
+        ddf[:, 1] = ddf[:, 0]
+    delay = find_delay_impulse(ddf=ddf, initial_value=initial_value)
+
+    assert delay.shape == (2, 1)
+    assert delay[0, 0] - delay[1, 0] == lag
+
+    # Make channel 1 lagged relative to channel 0 and check lag is recovered
+    ddf = np.random.random(size=(n_samples, 4))
+    if lag != 0:
+        ddf[:-lag, 0] = ddf[lag:, 1]
+    else:
+        ddf[:, 0] = ddf[:, 1]
+    delay = find_delay_impulse(ddf=ddf, initial_value=initial_value)
+
+    assert delay.shape == (2, 1)
+    assert delay[1, 0] - delay[0, 0] == lag
diff --git a/tests/evaluator/msbg/test_audiogram.py b/tests/evaluator/msbg/test_audiogram.py
new file mode 100644
index 000000000..73f44cf5c
--- /dev/null
+++ b/tests/evaluator/msbg/test_audiogram.py
@@ -0,0 +1,4 @@
+"""Tests for audiogram module"""
+
+
+# Audiogram
diff --git a/tests/evaluator/msbg/test_cochlear.py b/tests/evaluator/msbg/test_cochlear.py
new file mode 100644
index 000000000..ef8e46c4f
--- /dev/null
+++ b/tests/evaluator/msbg/test_cochlear.py
@@ -0,0 +1,17 @@
+"""Tests for cochlear module"""
+
+# Filterbank
+
+# compute_recruitment_parameters
+
+# gammatone_filterbank
+
+# compute_envelope
+
+
+# recruitment
+
+
+# Cochlear
+
+# simulate
diff --git a/tests/evaluator/msbg/test_msbg.py b/tests/evaluator/msbg/test_msbg.py
new file mode 100644
index 000000000..50a7ecc23
--- /dev/null
+++ b/tests/evaluator/msbg/test_msbg.py
@@ -0,0 +1,15 @@
+"""Tests for msbg module"""
+
+
+# Ear
+
+# get_src_correction
+
+# src_to_cochlear_filter
+
+
+# make_calibration_signal
+
+# array_to_list
+
+# process
diff --git a/tests/evaluator/msbg/test_msbg_utils.py b/tests/evaluator/msbg/test_msbg_utils.py
new file mode 100644
index 000000000..8521abb87
--- /dev/null
+++ b/tests/evaluator/msbg/test_msbg_utils.py
@@ -0,0 +1,16 @@
+"""Tests for msbg_utils module"""
+
+
+# read_gtf_file
+
+
+# firwin2
+
+
+# gen_eh2008_speech_noise
+
+# measure_rms
+
+# read_signal
+
+# write_signal
diff --git a/tests/evaluator/msbg/test_smearing.py b/tests/evaluator/msbg/test_smearing.py
new file mode 100644
index 000000000..4f20380ab
--- /dev/null
+++ b/tests/evaluator/msbg/test_smearing.py
@@ -0,0 +1,47 @@
+"""Tests for smearing module"""
+import numpy as np
+import pytest
+
+from clarity.evaluator.msbg.smearing import Smearer, make_smear_mat3
+
+# audfilt
+
+# make_smear_mat3
+
+# smear3
+
+# Smearer
+
+
+def test_change_filter():
+    """Test changing filter broadening factors and sampling frequency"""
+    smearer = Smearer(0.5, 1.5, 44100)
+    f_smear_old = smearer.f_smear
+    smearer.rl = 0.8
+    smearer.ru = 1.2
+    smearer.fs = 22050
+    smearer.f_smear = make_smear_mat3(smearer.rl, smearer.ru, smearer.fs)
+    assert isinstance(smearer.f_smear, np.ndarray)
+    assert smearer.f_smear.shape == (256, 256)
+    assert not np.array_equal(smearer.f_smear, f_smear_old)
+
+
+def test_smear_valid_input():
+    """Test smear with valid input"""
+    np.random.seed(0)
+    r_lower = 0.5
+    r_upper = 1.5
+    sample_freq = 44100
+    input_signal = np.random.rand(10000)
+    output_signal = Smearer(r_lower, r_upper, sample_freq).smear(input_signal)
+    assert output_signal.shape == (10240,)
+    assert np.sum(np.abs(output_signal)) == pytest.approx(5066.986397433977)
+
+
+def test_make_smear_mat3_valid_input():
+    """Tests that make_smear_mat3 returns matrix with the correct dimensions"""
+    r_lower = 0.5
+    r_upper = 1.5
+    sample_freq = 44100
+    f_smear = make_smear_mat3(rl=r_lower, ru=r_upper, fs=sample_freq)
+    assert f_smear.shape == (256, 256)
diff --git a/tests/recipes/cad1/task1/test_evaluate.py b/tests/recipes/cad1/task1/test_evaluate.py
index 803cf506d..d0bf9b48e 100644
--- a/tests/recipes/cad1/task1/test_evaluate.py
+++ b/tests/recipes/cad1/task1/test_evaluate.py
@@ -36,7 +36,7 @@ def test_results_file(tmp_path):
             "right_vocals": 0.95,
         },
     )
-    with open(results_file, "r", encoding="utf-8") as file:
+    with open(results_file, encoding="utf-8") as file:
         contents = file.read()
         assert (
             "My favorite song,My listener,0.9,0.8,0.8,0.9,0.9,0.8,0.8,0.95,0.95"
diff --git a/tests/regression/test_CEC2_scene_builder.py b/tests/regression/test_CEC2_scene_builder.py
index fc85f956c..d515ed787 100644
--- a/tests/regression/test_CEC2_scene_builder.py
+++ b/tests/regression/test_CEC2_scene_builder.py
@@ -2,7 +2,7 @@
 # Use scene builder to build random scene
 
 import json
-import os
+from pathlib import Path
 
 from omegaconf import OmegaConf
 
@@ -10,16 +10,17 @@
 
 
 def test_CEC2_scene_builder(regtest):
+    """Regression test for CEC2 scene builder"""
     cfg = OmegaConf.load("tests/test_data/configs/test_CEC2_scene_builder.yaml")
     cfg.path.root = "tests"
     set_random_seed(cfg.random_seed)
     dataset = "train"
-    rb = RoomBuilder()
+    room_builder = RoomBuilder()
 
-    room_file = os.path.join(cfg.path.metadata_dir, f"rooms.{dataset}.json")
-    rb.load(room_file)
-    sb = SceneBuilder(
-        rb=rb,
+    room_file = Path(cfg.path.metadata_dir) / f"rooms.{dataset}.json"
+    room_builder.load(room_file)
+    scene_builder = SceneBuilder(
+        rb=room_builder,
         scene_datasets=cfg.scene_datasets[dataset],
         target=cfg.target,
         interferer=cfg.interferer,
@@ -27,9 +28,9 @@ def test_CEC2_scene_builder(regtest):
         listener=cfg.listener,
         shuffle_rooms=cfg.shuffle_rooms,
     )
-    sb.instantiate_scenes(dataset=dataset)
+    scene_builder.instantiate_scenes(dataset=dataset)
 
-    print(len(sb.scenes))
+    print(len(scene_builder.scenes))
 
     with regtest:
-        print(json.dumps(sb.scenes, indent=4))
+        print(json.dumps(scene_builder.scenes, indent=4))
diff --git a/tests/regression/test_enhancers.py b/tests/regression/test_enhancers.py
index ad0071b3e..75db2511a 100644
--- a/tests/regression/test_enhancers.py
+++ b/tests/regression/test_enhancers.py
@@ -68,7 +68,7 @@ def test_GHA_inputs(regtest):
 
 def test_GHA_config(regtest):
     enhancer = gha(**gha_params)
-    with open("tests/test_data/metadata/listeners.json", "r", encoding="utf-8") as fp:
+    with open("tests/test_data/metadata/listeners.json", encoding="utf-8") as fp:
         listeners = json.load(fp)
     listener = listeners["L0001"]
     cfs = np.array(listener["audiogram_cfs"], dtype="int")
diff --git a/tests/regression/test_full_CEC1_pipeline.py b/tests/regression/test_full_CEC1_pipeline.py
index 9703d4ae7..6635a43bf 100644
--- a/tests/regression/test_full_CEC1_pipeline.py
+++ b/tests/regression/test_full_CEC1_pipeline.py
@@ -149,7 +149,7 @@ def test_full_cec1_pipeline(regtest):
     enhanced_audio = np.tanh(enhanced_audio) * 100  # * 10000
 
     # Create discrete delta function (DDF) signal for time alignment
-    ddf_signal = np.zeros((np.shape(signal)))
+    ddf_signal = np.zeros(np.shape(signal))
 
     print(ddf_signal.shape)
     ddf_signal[:, 0] = unit_impulse(len(signal), int(MSBG_FS / 2))
diff --git a/tests/regression/test_full_CEC2_pipeline.py b/tests/regression/test_full_CEC2_pipeline.py
index a80674317..d9b9777a8 100644
--- a/tests/regression/test_full_CEC2_pipeline.py
+++ b/tests/regression/test_full_CEC2_pipeline.py
@@ -1,9 +1,6 @@
 """Regression Tests for CEC2"""
 # pylint: disable=too-many-locals invalid-name
-
-# Pass some random data through code and compare with reference output
-# scene_renderer, enhancer, compressor, haspi
-from typing import Optional
+from __future__ import annotations
 
 import numpy as np
 from cpuinfo import get_cpu_info
@@ -15,6 +12,10 @@
 from clarity.enhancer.nalr import NALR
 from clarity.evaluator.haspi import haspi_v2_be
 
+# Pass some random data through code and compare with reference output
+# scene_renderer, enhancer, compressor, haspi
+
+
 CPUINFO = get_cpu_info()
 
 np.random.seed(0)
@@ -97,7 +98,7 @@
 def test_full_cec2_pipeline(
     regtest,
     tmp_path,
-    scene: Optional[dict] = None,
+    scene: dict | None = None,
     _test_paths: OmegaConf = TEST_PATHS,
     _test_metadata: OmegaConf = TEST_METADATA,
     scene_renderer: SceneRenderer = SCENE_RENDERER,