Repository: claritychallenge/clarity (language: python)

Issue:
Switch to pre-commit.ci
Currently there is a `pre-commit` GitHub Action in place which runs `pre-commit` against Pull Requests. However this sometimes fails if code hasn't been linted correctly locally first (see [example](https://github.com/claritychallenge/clarity/actions/runs/3911430678/jobs/6684813513)).

One way of getting around this is to use [pre-commit.ci](https://pre-commit.ci) which can be configured to commit and make the changes to the Pull Request directly (e.g. if `black` is applied to code).

This shortens the feedback loop and lessens the burden on contributors.
[pre-commit.ci] pre-commit-autoupdate
<!--pre-commit.ci start-->
updates:
- [github.com/pre-commit/mirrors-mypy: v0.991 → v1.0.0](https://github.com/pre-commit/mirrors-mypy/compare/v0.991...v1.0.0)
<!--pre-commit.ci end-->


Discussion / hints:

Fix patch:
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 000000000..20a02788b
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,4 @@
+[run]
+omit =
+  # Exclude versioneer
+  clarity/_version.py
\ No newline at end of file
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 000000000..4b7add1b3
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,22 @@
+# 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
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..ba99e1103
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,1 @@
+clarity/_version.py export-subst
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
deleted file mode 100644
index 8ef23706c..000000000
--- a/.github/workflows/lint.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-# This workflow will run lint and many other pre-commit hooks
-# https://pre-commit.com/
-
-name: lint
-
-on:
-  pull_request:
-    branches: [main]
-  workflow_dispatch:
-
-jobs:
-  lint:
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        os: ["ubuntu-latest"]
-        python-version: ['3.8', '3.9', '3.10']
-    name: Lint and pre-commit checks
-    steps:
-    - uses: actions/checkout@v3
-    - name: Set up Python
-      uses: actions/setup-python@v3
-    - name: Install dependencies
-      run: |
-        pip install pre-commit isort flake8==3.9.2
-        pre-commit install
-    - name: pre-commit
-      run: |
-        echo "Running pre-commit on all files..."
-        echo ""
-        pre-commit run --all-files
-    - name: Advise on installing pre-commit
-      run: |
-        echo "pre-commit checks have failed."
-        echo ""
-        echo "Please consider installing and configuring pre-commit to run locally. To do so..."
-        echo ""
-        echo " pip install pyclarity.[dev]"
-        echo " pre-commit install"
-        echo ""
-        echo "For more information please refer to https://claritychallenge.org/clarity/CONTRIBUTING.html"
-      if: always() && (steps.pre-commit.outcome == 'failure')
diff --git a/.github/workflows/pypi.yaml b/.github/workflows/pypi.yaml
index a0da8cc63..e91d4b3f7 100644
--- a/.github/workflows/pypi.yaml
+++ b/.github/workflows/pypi.yaml
@@ -2,7 +2,8 @@ name: Publish package to PyPi
 
 on:
   push:
-    branches: [master]
+    tags:
+      - v*
 
 jobs:
   build-release:
@@ -13,19 +14,25 @@ jobs:
         with:
           fetch-depth: 0
       - name: Setup Python
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4.3.0
         with:
           python-version: 3.9
       - name: Installing the package
         run: |
+          pip3 install versioneer
           pip3 install .
           pip3 install .[pypi]
       - name: Build package
         run: |
-          python -m build
-      - name: Publish package
-        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
+          python -m build --no-isolation
+      - name: Publish package to PyPI
         uses: pypa/gh-action-pypi-publish@release/v1
         with:
           user: __token__
           password: ${{ secrets.PYPI_API_TOKEN }}
+      - name: Upload release artifacts to Release Notes
+        uses: Roang-zero1/github-upload-release-artifacts-action@v2
+        with:
+          args "dist/"
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/sphinx_docx_to_gh_pages.yaml b/.github/workflows/sphinx_docs_to_gh_pages.yaml
similarity index 82%
rename from .github/workflows/sphinx_docx_to_gh_pages.yaml
rename to .github/workflows/sphinx_docs_to_gh_pages.yaml
index a1e8a0a20..9009aecac 100644
--- a/.github/workflows/sphinx_docx_to_gh_pages.yaml
+++ b/.github/workflows/sphinx_docs_to_gh_pages.yaml
@@ -15,12 +15,15 @@ jobs:
         with:
           fetch-depth: 0
       - name: Setup Python
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4.3.0
         with:
           python-version: 3.9
       - name: Installing the Documentation requirements
         run: |
           pip3 install .[docs]
+      - name: Copying recipe README.md
+        run: |
+         rsync -av --exclude-from="docs/recipe_rsync_exclude.txt" recipes docs/.
       - name: Running Sphinx to gh-pages Action
         uses: ns-rse/action-sphinx-docs-to-gh-pages@main
         with:
diff --git a/.gitignore b/.gitignore
index 7d28241e7..f7a061526 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,4 +62,7 @@ docs/clarity.*.rst
 docs/recipes.*.rst
 
 # Emacs
-*~
\ No newline at end of file
+*~
+
+# Sound files
+*.wav
\ No newline at end of file
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cf14829aa..5f6be07a4 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.3.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]
@@ -16,32 +16,31 @@ repos:
         args: [--maxkb=1024]
       - id: debug-statements
       - id: check-byte-order-marker
-#      - id: check-yaml
+      # - id: check-yaml
 
   - repo: https://github.com/psf/black
-    rev: 22.10.0
+    rev: 23.1.0
     hooks:
       - id: black
         types: [python]
         additional_dependencies: ['click==8.0.4']
 
   - repo: https://github.com/pycqa/flake8.git
-    rev: 5.0.4
+    rev: 6.0.0
     hooks:
       - id: flake8
         additional_dependencies: [flake8-print]
         types: [python]
 
-  - repo: https://github.com/pre-commit/mirrors-isort
-    rev: v5.10.1
+  - repo: https://github.com/pycqa/isort
+    rev: 5.12.0
     hooks:
       - id: isort
+        name: isort (python)
         args: ["--profile", "black"]
-        name: isort
-        types: [python]
 
   - repo: https://github.com/nbQA-dev/nbQA
-    rev: 1.5.3
+    rev: 1.6.1
     hooks:
       - id: nbqa-black
       - id: nbqa-flake8
@@ -60,6 +59,13 @@ repos:
 
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.991
+    rev: v1.0.0
     hooks:
-      - id: mypy
\ No newline at end of file
+      - id: mypy
+
+ci:
+  autofix_prs: true
+  autofix_commit_msg: '[pre-commit.ci] Fixing issues with pre-commit'
+  autoupdate_schedule: weekly
+  autoupdate_commit_msg: '[pre-commit.ci] pre-commit-autoupdate'
+  skip: [] # Optionally list ids of hooks to skip on CI
diff --git a/.pylintrc b/.pylintrc
index 8f311e557..4bcda91cf 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -87,7 +87,8 @@ disable=R0801,
         suppressed-message,
         useless-suppression,
         deprecated-pragma,
-        use-symbolic-message-instead
+        use-symbolic-message-instead,
+        logging-fstring-interpolation
 
 # Enable the message, report, category or checker with the given id(s). You can
 # either give multiple identifier separated by comma (,) or put this option
@@ -388,10 +389,20 @@ function-naming-style=snake_case
 #function-rgx=
 
 # Good variable names which should always be accepted, separated by a comma.
-good-names=i,
+good-names=a,
+           b,
+           c,
+           i,
            j,
            k,
+           m,
+           n,
+           x,
+           fx,
+           y,
+           fy,
            ex,
+           fp,
            Run,
            _
 
diff --git a/.vscode/settings.json b/.vscode/settings.json
index de288e1ea..4abfdf516 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,7 @@
 {
-    "python.formatting.provider": "black"
+    "python.formatting.provider": "black",
+    "cSpell.words": [
+        "haspi",
+        "hasqi"
+    ]
 }
\ No newline at end of file
diff --git a/README.md b/README.md
old mode 100755
new mode 100644
index dd04c06fe..912ded0fc
--- a/README.md
+++ b/README.md
@@ -1,9 +1,29 @@
-## Machine learning challenges for hearing aid processing.
+<div align="center">
+
+# Machine learning challenges for hearing aid processing.
+
 <p align="center">
-  <img src="docs/images/earfinal_clarity_customColour.png" alt="drawing" width="250"/>
-  <img src="docs/images/cropped-cadenza_logo_square-1.png" alt="Cadenza Challenge" width="250"/>
+  <img src="docs/images/earfinal_clarity_customColour.png" alt="drawing" width="200" hspace="40"/>
+
+  <img src="docs/images/cadenza_logo.png" alt="Cadenza Challenge" width="250" hspace="40"/>
+
+<p>
+
+[![PyPI version](https://badge.fury.io/py/pyclarity.svg)](https://badge.fury.io/py/pyclarity)
+[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyclarity)](https://pypi.org/project/pyclarity/)
+[![codecov.io](https://codecov.io/github/claritychallenge/clarity/coverage.svg?branch=main)](https://app.codecov.io/gh/claritychallenge/clarity)
+[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
+[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/claritychallenge/clarity/main.svg)](https://results.pre-commit.ci/latest/github/claritychallenge/clarity/main)
+[![Downloads](https://pepy.tech/badge/pyclarity)](https://pepy.tech/project/pyclarity)
+
+[![PyPI](https://img.shields.io/static/v1?label=ICASSP%202023%20Challenge%20-%20pypi&message=v0.2.1&color=orange)](https://pypi.org/project/pyclarity/0.2.1/)
+[![PyPI](https://img.shields.io/static/v1?label=CEC2%20Challenge%20-%20pypi&message=v0.1.1&color=orange)](https://pypi.org/project/pyclarity/0.1.1/)
 </p>
 
+</div>
+
+--------------------------------------------------------------------------------
+
 We are organising a series of machine learning challenges to enhance hearing-aid signal processing and to better predict
 how people perceive speech-in-noise (Clarity) and speech-in-music (Cadenza). For further details of the Clarity Project
 visit [the Clarity project website](http://claritychallenge.org/), and for details of our latest Clarity challenges
@@ -15,10 +35,12 @@ Cadenza challenges join the [Cadenza Challenge Group](https://groups.google.com/
 In this repository, you will find code to support all Clarity and Cadenza Challenges, including baselines, toolkits, and
 systems from participants. **We encourage you to make your system/model open source and contribute to this repository.**
 
-### The 2nd Clarity Enhancement Challenge (CEC2) Online Workshop [Register Now!](https://claritychallenge.org/clarity2022-CEC2-workshop/):fire::fire::fire:
+## Current Events
 
-## Installation
+- The 2nd Clarity Enhancement Challenge (CEC2) Online Workshop - December 12th - [Register Now!](https://claritychallenge.org/clarity2022-CEC2-workshop/):fire::fire::fire:
+- The ICASSP 2023 Clarity Challenge - Open Now [visit the website](https://claritychallenge.org/ICASSP2023_announcement_page/) for details on how to take part. Closing Feb 8th.
 
+## Installation
 
 ### PyPI
 
@@ -60,49 +82,49 @@ pip install -e git+https://github.com/claritychallenge/clarity.git@main
 
 Current challenge(s)
 
-- [The 2nd Clarity Enhancement Challenge (CEC2)](./recipes/cec2)
+- [The ICASSP 2023 Enhancement Challenge](./recipes/icassp_2023)
 
 Upcoming challenges
 
 - The first Cadenza Challenge will launch Spring 2023. Join the [Cadenza Challenge
   Group](https://groups.google.com/g/cadenza-challenge) to keep up-to-date on developments.
+- The 2nd Clarity Prediction Challenge is due to launch in February 2023.
 
 Previous challenges
 
 - [The 1st Clarity Prediction Challenge (CPC1)](./recipes/cpc1)
 - [The 1st Clarity Enhancement Challenge (CEC1)](./recipes/cec1)
 
-
 ## Available tools
 
 We provide also a number of tools in this repository:
+
 - **Hearing loss simulation**
-    - [Cambridge MSBG hearing loss simulator](./clarity/evaluator/msbg): descriptions can be found in the [CEC1
+  - [Cambridge MSBG hearing loss simulator](./clarity/evaluator/msbg): descriptions can be found in the [CEC1
       description](./recipes/cec1); an usage example can be found in the [CEC1 baseline](./recipes/cec1/baseline)
       evaluation script `evaluate.py`.
 - **Objective intelligibility measurement**
-    - [Modified binaural STOI (MBSTOI)](./clarity/evaluator/mbstoi/mbstoi.py): a python implementation of MBSTOI. It is
+  - [Modified binaural STOI (MBSTOI)](./clarity/evaluator/mbstoi/mbstoi.py): a Python implementation of MBSTOI. It is
       jointly used with the MSBG hearing loss model in the [CEC1 baseline](./recipes/cec1/baseline). The official matlab
-      implementation can be found here: http://ah-andersen.net/code/
-    - [Hearing-aid speech perception index (HASPI)](./clarity/evaluator/haspi/haspi.py): a python implementation of
-      HASPI Version 2, and the better-ear HASPI for binaural speech signals. For official matlab implementation, request
-      here: https://www.colorado.edu/lab/hearlab/resources
+      implementation can be found here: <http://ah-andersen.net/code/>
+  - [Hearing-aid speech perception index (HASPI)](./clarity/evaluator/haspi/haspi.py): a Python implementation of
+      HASPI Version 2, and the better-ear HASPI for binaural speech signals. For official matlab implementation, request here: <https://www.colorado.edu/lab/hearlab/resources>
+  - [Hearing-aid speech quality index (HASQI)](./clarity/evaluator/hasqi/hasqi.py): a Python implementation of
+      HASQI Version 2, and the better-ear HASQI for binaural speech signals.
+  - [Hearing-aid audio quality index (HAAQI)](./clarity/evaluator/haaqi/haaqi.py): a Python implementation of
+      HAAQI.
 - **Hearing aid enhancement**
-    - [Cambridge hearing aid fitting (CAMFIT)](./clarity/enhancer/gha/gainrule_camfit.py): a python implementation of
-      CAMFIT, translated from the [HörTech Open Master Hearing Aid (OpenMHA)](http://www.openmha.org/about/); the CAMFIT
-      is used together with OpenMHA enhancement as the [CEC1 baseline](./recipes/cec1/baseline), see `enhance.py`.
-    - [NAL-R hearing aid fitting](./clarity/enhancer/nalr.py): a python implementation of NAL-R prescription fitting. It
-      is used as the [CEC2 baseline](./recipes/cec2/baseline), see `enhance.py`.
+  - [Cambridge hearing aid fitting (CAMFIT)](./clarity/enhancer/gha/gainrule_camfit.py): a Python implementation of CAMFIT, translated from the [HörTech Open Master Hearing Aid (OpenMHA)](http://www.openmha.org/about/); the CAMFIT is used together with OpenMHA enhancement as the [CEC1 baseline](./recipes/cec1/baseline), see `enhance.py`.
+  - [NAL-R hearing aid fitting](./clarity/enhancer/nalr.py): a Python implementation of NAL-R prescription fitting. It is used as the [CEC2 baseline](./recipes/cec2/baseline), see `enhance.py`.
 
 In addition, differentiable approximation to some tools are provided:
 
-* [x] [Differentiable MSBG hearing loss model](./clarity/predictor/torch_msbg.py). See also the BUT implementation:
-      https://github.com/BUTSpeechFIT/torch_msbg_mbstoi
-* [ ] Differentiable HASPI (coming)
-
-
+- [x] [Differentiable MSBG hearing loss model](./clarity/predictor/torch_msbg.py). See also the BUT implementation:
+      <https://github.com/BUTSpeechFIT/torch_msbg_mbstoi>
+- [ ] Differentiable HASPI (coming)
 
 ## Open-source systems
+
 - CPC1:
   - [Exploiting Hidden Representations from a DNN-based Speech Recogniser for Speech Intelligibility Prediction in
     Hearing-impaired Listeners](./recipes/cpc1/e032_sheffield)
diff --git a/clarity/__init__.py b/clarity/__init__.py
index 12015f729..4620d0c9f 100644
--- a/clarity/__init__.py
+++ b/clarity/__init__.py
@@ -1,2 +1,4 @@
 """pyClarity"""
-__version__ = "0.1.0"
+from . import _version
+
+__version__ = _version.get_versions()["version"]
diff --git a/clarity/_version.py b/clarity/_version.py
new file mode 100644
index 000000000..0bb0c56de
--- /dev/null
+++ b/clarity/_version.py
@@ -0,0 +1,693 @@
+# This file helps to compute a version number in source trees obtained from
+# git-archive tarball (such as those provided by githubs download-from-tag
+# feature). Distribution tarballs (built by setup.py sdist) and build
+# directories (produced by setup.py build) will contain a much shorter file
+# that just contains the computed version number.
+
+# This file is released into the public domain.
+# Generated by versioneer-0.26
+# https://github.com/python-versioneer/python-versioneer
+
+"""Git implementation of _version.py."""
+
+import errno
+import functools
+import os
+import re
+import subprocess
+import sys
+from typing import Callable, Dict
+
+
+def get_keywords():
+    """Get the keywords needed to look up the version information."""
+    # these strings will be replaced by git during git-archive.
+    # setup.py/versioneer.py will grep for the variable names, so they must
+    # each be defined on a line of their own. _version.py will just call
+    # get_keywords().
+    git_refnames = "$Format:%d$"
+    git_full = "$Format:%H$"
+    git_date = "$Format:%ci$"
+    keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
+    return keywords
+
+
+class VersioneerConfig:
+    """Container for Versioneer configuration parameters."""
+
+
+def get_config():
+    """Create, populate and return the VersioneerConfig() object."""
+    # these strings are filled in when 'setup.py versioneer' creates
+    # _version.py
+    cfg = VersioneerConfig()
+    cfg.VCS = "git"
+    cfg.style = "pep440"
+    cfg.tag_prefix = "v"
+    cfg.parentdir_prefix = ""
+    cfg.versionfile_source = "clarity/_version.py"
+    cfg.verbose = False
+    return cfg
+
+
+class NotThisMethod(Exception):
+    """Exception raised if a method is not valid for the current scenario."""
+
+
+LONG_VERSION_PY: Dict[str, str] = {}
+HANDLERS: Dict[str, Dict[str, Callable]] = {}
+
+
+def register_vcs_handler(vcs, method):  # decorator
+    """Create decorator to mark a method as the handler of a VCS."""
+
+    def decorate(f):
+        """Store f in HANDLERS[vcs][method]."""
+        if vcs not in HANDLERS:
+            HANDLERS[vcs] = {}
+        HANDLERS[vcs][method] = f
+        return f
+
+    return decorate
+
+
+def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None):
+    """Call the given command(s)."""
+    assert isinstance(commands, list)
+    process = None
+
+    popen_kwargs = {}
+    if sys.platform == "win32":
+        # This hides the console window if pythonw.exe is used
+        startupinfo = subprocess.STARTUPINFO()
+        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+        popen_kwargs["startupinfo"] = startupinfo
+
+    for command in commands:
+        try:
+            dispcmd = str([command] + args)
+            # remember shell=False, so use git.cmd on windows, not just git
+            process = subprocess.Popen(
+                [command] + args,
+                cwd=cwd,
+                env=env,
+                stdout=subprocess.PIPE,
+                stderr=(subprocess.PIPE if hide_stderr else None),
+                **popen_kwargs,
+            )
+            break
+        except OSError:
+            e = sys.exc_info()[1]
+            if e.errno == errno.ENOENT:
+                continue
+            if verbose:
+                print("unable to run %s" % dispcmd)
+                print(e)
+            return None, None
+    else:
+        if verbose:
+            print("unable to find command, tried %s" % (commands,))
+        return None, None
+    stdout = process.communicate()[0].strip().decode()
+    if process.returncode != 0:
+        if verbose:
+            print("unable to run %s (error)" % dispcmd)
+            print("stdout was %s" % stdout)
+        return None, process.returncode
+    return stdout, process.returncode
+
+
+def versions_from_parentdir(parentdir_prefix, root, verbose):
+    """Try to determine the version from the parent directory name.
+
+    Source tarballs conventionally unpack into a directory that includes both
+    the project name and a version string. We will also support searching up
+    two directory levels for an appropriately named parent directory
+    """
+    rootdirs = []
+
+    for _ in range(3):
+        dirname = os.path.basename(root)
+        if dirname.startswith(parentdir_prefix):
+            return {
+                "version": dirname[len(parentdir_prefix) :],
+                "full-revisionid": None,
+                "dirty": False,
+                "error": None,
+                "date": None,
+            }
+        rootdirs.append(root)
+        root = os.path.dirname(root)  # up a level
+
+    if verbose:
+        print(
+            "Tried directories %s but none started with prefix %s"
+            % (str(rootdirs), parentdir_prefix)
+        )
+    raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
+
+
+@register_vcs_handler("git", "get_keywords")
+def git_get_keywords(versionfile_abs):
+    """Extract version information from the given file."""
+    # the code embedded in _version.py can just fetch the value of these
+    # keywords. When used from setup.py, we don't want to import _version.py,
+    # so we do it with a regexp instead. This function is not used from
+    # _version.py.
+    keywords = {}
+    try:
+        with open(versionfile_abs, "r") as fobj:
+            for line in fobj:
+                if line.strip().startswith("git_refnames ="):
+                    mo = re.search(r'=\s*"(.*)"', line)
+                    if mo:
+                        keywords["refnames"] = mo.group(1)
+                if line.strip().startswith("git_full ="):
+                    mo = re.search(r'=\s*"(.*)"', line)
+                    if mo:
+                        keywords["full"] = mo.group(1)
+                if line.strip().startswith("git_date ="):
+                    mo = re.search(r'=\s*"(.*)"', line)
+                    if mo:
+                        keywords["date"] = mo.group(1)
+    except OSError:
+        pass
+    return keywords
+
+
+@register_vcs_handler("git", "keywords")
+def git_versions_from_keywords(keywords, tag_prefix, verbose):
+    """Get version information from git keywords."""
+    if "refnames" not in keywords:
+        raise NotThisMethod("Short version file found")
+    date = keywords.get("date")
+    if date is not None:
+        # Use only the last line.  Previous lines may contain GPG signature
+        # information.
+        date = date.splitlines()[-1]
+
+        # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
+        # datestamp. However we prefer "%ci" (which expands to an "ISO-8601
+        # -like" string, which we must then edit to make compliant), because
+        # it's been around since git-1.5.3, and it's too difficult to
+        # discover which version we're using, or to work around using an
+        # older one.
+        date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+    refnames = keywords["refnames"].strip()
+    if refnames.startswith("$Format"):
+        if verbose:
+            print("keywords are unexpanded, not using")
+        raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
+    refs = {r.strip() for r in refnames.strip("()").split(",")}
+    # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
+    # just "foo-1.0". If we see a "tag: " prefix, prefer those.
+    TAG = "tag: "
+    tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
+    if not tags:
+        # Either we're using git < 1.8.3, or there really are no tags. We use
+        # a heuristic: assume all version tags have a digit. The old git %d
+        # expansion behaves like git log --decorate=short and strips out the
+        # refs/heads/ and refs/tags/ prefixes that would let us distinguish
+        # between branches and tags. By ignoring refnames without digits, we
+        # filter out many common branch names like "release" and
+        # "stabilization", as well as "HEAD" and "master".
+        tags = {r for r in refs if re.search(r"\d", r)}
+        if verbose:
+            print("discarding '%s', no digits" % ",".join(refs - tags))
+    if verbose:
+        print("likely tags: %s" % ",".join(sorted(tags)))
+    for ref in sorted(tags):
+        # sorting will prefer e.g. "2.0" over "2.0rc1"
+        if ref.startswith(tag_prefix):
+            r = ref[len(tag_prefix) :]
+            # Filter out refs that exactly match prefix or that don't start
+            # with a number once the prefix is stripped (mostly a concern
+            # when prefix is '')
+            if not re.match(r"\d", r):
+                continue
+            if verbose:
+                print("picking %s" % r)
+            return {
+                "version": r,
+                "full-revisionid": keywords["full"].strip(),
+                "dirty": False,
+                "error": None,
+                "date": date,
+            }
+    # no suitable tags, so version is "0+unknown", but full hex is still there
+    if verbose:
+        print("no suitable tags, using unknown + full revision id")
+    return {
+        "version": "0+unknown",
+        "full-revisionid": keywords["full"].strip(),
+        "dirty": False,
+        "error": "no suitable tags",
+        "date": None,
+    }
+
+
+@register_vcs_handler("git", "pieces_from_vcs")
+def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
+    """Get version from 'git describe' in the root of the source tree.
+
+    This only gets called if the git-archive 'subst' keywords were *not*
+    expanded, and _version.py hasn't already been rewritten with a short
+    version string, meaning we're inside a checked out source tree.
+    """
+    GITS = ["git"]
+    if sys.platform == "win32":
+        GITS = ["git.cmd", "git.exe"]
+
+    # GIT_DIR can interfere with correct operation of Versioneer.
+    # It may be intended to be passed to the Versioneer-versioned project,
+    # but that should not change where we get our version from.
+    env = os.environ.copy()
+    env.pop("GIT_DIR", None)
+    runner = functools.partial(runner, env=env)
+
+    _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=not verbose)
+    if rc != 0:
+        if verbose:
+            print("Directory %s not under git control" % root)
+        raise NotThisMethod("'git rev-parse --git-dir' returned error")
+
+    # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
+    # if there isn't one, this yields HEX[-dirty] (no NUM)
+    describe_out, rc = runner(
+        GITS,
+        [
+            "describe",
+            "--tags",
+            "--dirty",
+            "--always",
+            "--long",
+            "--match",
+            f"{tag_prefix}[[:digit:]]*",
+        ],
+        cwd=root,
+    )
+    # --long was added in git-1.5.5
+    if describe_out is None:
+        raise NotThisMethod("'git describe' failed")
+    describe_out = describe_out.strip()
+    full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
+    if full_out is None:
+        raise NotThisMethod("'git rev-parse' failed")
+    full_out = full_out.strip()
+
+    pieces = {}
+    pieces["long"] = full_out
+    pieces["short"] = full_out[:7]  # maybe improved later
+    pieces["error"] = None
+
+    branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
+    # --abbrev-ref was added in git-1.6.3
+    if rc != 0 or branch_name is None:
+        raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
+    branch_name = branch_name.strip()
+
+    if branch_name == "HEAD":
+        # If we aren't exactly on a branch, pick a branch which represents
+        # the current commit. If all else fails, we are on a branchless
+        # commit.
+        branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
+        # --contains was added in git-1.5.4
+        if rc != 0 or branches is None:
+            raise NotThisMethod("'git branch --contains' returned error")
+        branches = branches.split("\n")
+
+        # Remove the first line if we're running detached
+        if "(" in branches[0]:
+            branches.pop(0)
+
+        # Strip off the leading "* " from the list of branches.
+        branches = [branch[2:] for branch in branches]
+        if "master" in branches:
+            branch_name = "master"
+        elif not branches:
+            branch_name = None
+        else:
+            # Pick the first branch that is returned. Good or bad.
+            branch_name = branches[0]
+
+    pieces["branch"] = branch_name
+
+    # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
+    # TAG might have hyphens.
+    git_describe = describe_out
+
+    # look for -dirty suffix
+    dirty = git_describe.endswith("-dirty")
+    pieces["dirty"] = dirty
+    if dirty:
+        git_describe = git_describe[: git_describe.rindex("-dirty")]
+
+    # now we have TAG-NUM-gHEX or HEX
+
+    if "-" in git_describe:
+        # TAG-NUM-gHEX
+        mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
+        if not mo:
+            # unparsable. Maybe git-describe is misbehaving?
+            pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
+            return pieces
+
+        # tag
+        full_tag = mo.group(1)
+        if not full_tag.startswith(tag_prefix):
+            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'" % (
+                full_tag,
+                tag_prefix,
+            )
+            return pieces
+        pieces["closest-tag"] = full_tag[len(tag_prefix) :]
+
+        # distance: number of commits since tag
+        pieces["distance"] = int(mo.group(2))
+
+        # commit: short hex revision ID
+        pieces["short"] = mo.group(3)
+
+    else:
+        # HEX: no tags
+        pieces["closest-tag"] = None
+        out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root)
+        pieces["distance"] = len(out.split())  # total number of commits
+
+    # commit date: see ISO-8601 comment in git_versions_from_keywords()
+    date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
+    # Use only the last line.  Previous lines may contain GPG signature
+    # information.
+    date = date.splitlines()[-1]
+    pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
+
+    return pieces
+
+
+def plus_or_dot(pieces):
+    """Return a + if we don't already have one, else return a ."""
+    if "+" in pieces.get("closest-tag", ""):
+        return "."
+    return "+"
+
+
+def render_pep440(pieces):
+    """Build up version string, with post-release "local version identifier".
+
+    Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
+    get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
+
+    Exceptions:
+    1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
+    """
+    if pieces["closest-tag"]:
+        rendered = pieces["closest-tag"]
+        if pieces["distance"] or pieces["dirty"]:
+            rendered += plus_or_dot(pieces)
+            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
+            if pieces["dirty"]:
+                rendered += ".dirty"
+    else:
+        # exception #1
+        rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
+        if pieces["dirty"]:
+            rendered += ".dirty"
+    return rendered
+
+
+def render_pep440_branch(pieces):
+    """TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
+
+    The ".dev0" means not master branch. Note that .dev0 sorts backwards
+    (a feature branch will appear "older" than the master branch).
+
+    Exceptions:
+    1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
+    """
+    if pieces["closest-tag"]:
+        rendered = pieces["closest-tag"]
+        if pieces["distance"] or pieces["dirty"]:
+            if pieces["branch"] != "master":
+                rendered += ".dev0"
+            rendered += plus_or_dot(pieces)
+            rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
+            if pieces["dirty"]:
+                rendered += ".dirty"
+    else:
+        # exception #1
+        rendered = "0"
+        if pieces["branch"] != "master":
+            rendered += ".dev0"
+        rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
+        if pieces["dirty"]:
+            rendered += ".dirty"
+    return rendered
+
+
+def pep440_split_post(ver):
+    """Split pep440 version string at the post-release segment.
+
+    Returns the release segments before the post-release and the
+    post-release version number (or -1 if no post-release segment is present).
+    """
+    vc = str.split(ver, ".post")
+    return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
+
+
+def render_pep440_pre(pieces):
+    """TAG[.postN.devDISTANCE] -- No -dirty.
+
+    Exceptions:
+    1: no tags. 0.post0.devDISTANCE
+    """
+    if pieces["closest-tag"]:
+        if pieces["distance"]:
+            # update the post release segment
+            tag_version, post_version = pep440_split_post(pieces["closest-tag"])
+            rendered = tag_version
+            if post_version is not None:
+                rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
+            else:
+                rendered += ".post0.dev%d" % (pieces["distance"])
+        else:
+            # no commits, use the tag as the version
+            rendered = pieces["closest-tag"]
+    else:
+        # exception #1
+        rendered = "0.post0.dev%d" % pieces["distance"]
+    return rendered
+
+
+def render_pep440_post(pieces):
+    """TAG[.postDISTANCE[.dev0]+gHEX] .
+
+    The ".dev0" means dirty. Note that .dev0 sorts backwards
+    (a dirty tree will appear "older" than the corresponding clean one),
+    but you shouldn't be releasing software with -dirty anyways.
+
+    Exceptions:
+    1: no tags. 0.postDISTANCE[.dev0]
+    """
+    if pieces["closest-tag"]:
+        rendered = pieces["closest-tag"]
+        if pieces["distance"] or pieces["dirty"]:
+            rendered += ".post%d" % pieces["distance"]
+            if pieces["dirty"]:
+                rendered += ".dev0"
+            rendered += plus_or_dot(pieces)
+            rendered += "g%s" % pieces["short"]
+    else:
+        # exception #1
+        rendered = "0.post%d" % pieces["distance"]
+        if pieces["dirty"]:
+            rendered += ".dev0"
+        rendered += "+g%s" % pieces["short"]
+    return rendered
+
+
+def render_pep440_post_branch(pieces):
+    """TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
+
+    The ".dev0" means not master branch.
+
+    Exceptions:
+    1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
+    """
+    if pieces["closest-tag"]:
+        rendered = pieces["closest-tag"]
+        if pieces["distance"] or pieces["dirty"]:
+            rendered += ".post%d" % pieces["distance"]
+            if pieces["branch"] != "master":
+                rendered += ".dev0"
+            rendered += plus_or_dot(pieces)
+            rendered += "g%s" % pieces["short"]
+            if pieces["dirty"]:
+                rendered += ".dirty"
+    else:
+        # exception #1
+        rendered = "0.post%d" % pieces["distance"]
+        if pieces["branch"] != "master":
+            rendered += ".dev0"
+        rendered += "+g%s" % pieces["short"]
+        if pieces["dirty"]:
+            rendered += ".dirty"
+    return rendered
+
+
+def render_pep440_old(pieces):
+    """TAG[.postDISTANCE[.dev0]] .
+
+    The ".dev0" means dirty.
+
+    Exceptions:
+    1: no tags. 0.postDISTANCE[.dev0]
+    """
+    if pieces["closest-tag"]:
+        rendered = pieces["closest-tag"]
+        if pieces["distance"] or pieces["dirty"]:
+            rendered += ".post%d" % pieces["distance"]
+            if pieces["dirty"]:
+                rendered += ".dev0"
+    else:
+        # exception #1
+        rendered = "0.post%d" % pieces["distance"]
+        if pieces["dirty"]:
+            rendered += ".dev0"
+    return rendered
+
+
+def render_git_describe(pieces):
+    """TAG[-DISTANCE-gHEX][-dirty].
+
+    Like 'git describe --tags --dirty --always'.
+
+    Exceptions:
+    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
+    """
+    if pieces["closest-tag"]:
+        rendered = pieces["closest-tag"]
+        if pieces["distance"]:
+            rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
+    else:
+        # exception #1
+        rendered = pieces["short"]
+    if pieces["dirty"]:
+        rendered += "-dirty"
+    return rendered
+
+
+def render_git_describe_long(pieces):
+    """TAG-DISTANCE-gHEX[-dirty].
+
+    Like 'git describe --tags --dirty --always -long'.
+    The distance/hash is unconditional.
+
+    Exceptions:
+    1: no tags. HEX[-dirty]  (note: no 'g' prefix)
+    """
+    if pieces["closest-tag"]:
+        rendered = pieces["closest-tag"]
+        rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
+    else:
+        # exception #1
+        rendered = pieces["short"]
+    if pieces["dirty"]:
+        rendered += "-dirty"
+    return rendered
+
+
+def render(pieces, style):
+    """Render the given version pieces into the requested style."""
+    if pieces["error"]:
+        return {
+            "version": "unknown",
+            "full-revisionid": pieces.get("long"),
+            "dirty": None,
+            "error": pieces["error"],
+            "date": None,
+        }
+
+    if not style or style == "default":
+        style = "pep440"  # the default
+
+    if style == "pep440":
+        rendered = render_pep440(pieces)
+    elif style == "pep440-branch":
+        rendered = render_pep440_branch(pieces)
+    elif style == "pep440-pre":
+        rendered = render_pep440_pre(pieces)
+    elif style == "pep440-post":
+        rendered = render_pep440_post(pieces)
+    elif style == "pep440-post-branch":
+        rendered = render_pep440_post_branch(pieces)
+    elif style == "pep440-old":
+        rendered = render_pep440_old(pieces)
+    elif style == "git-describe":
+        rendered = render_git_describe(pieces)
+    elif style == "git-describe-long":
+        rendered = render_git_describe_long(pieces)
+    else:
+        raise ValueError("unknown style '%s'" % style)
+
+    return {
+        "version": rendered,
+        "full-revisionid": pieces["long"],
+        "dirty": pieces["dirty"],
+        "error": None,
+        "date": pieces.get("date"),
+    }
+
+
+def get_versions():
+    """Get version information or return default if unable to do so."""
+    # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
+    # __file__, we can work backwards from there to the root. Some
+    # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
+    # case we can only use expanded keywords.
+
+    cfg = get_config()
+    verbose = cfg.verbose
+
+    try:
+        return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose)
+    except NotThisMethod:
+        pass
+
+    try:
+        root = os.path.realpath(__file__)
+        # versionfile_source is the relative path from the top of the source
+        # tree (where the .git directory might live) to this file. Invert
+        # this to find the root from __file__.
+        for _ in cfg.versionfile_source.split("/"):
+            root = os.path.dirname(root)
+    except NameError:
+        return {
+            "version": "0+unknown",
+            "full-revisionid": None,
+            "dirty": None,
+            "error": "unable to find root of source tree",
+            "date": None,
+        }
+
+    try:
+        pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
+        return render(pieces, cfg.style)
+    except NotThisMethod:
+        pass
+
+    try:
+        if cfg.parentdir_prefix:
+            return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
+    except NotThisMethod:
+        pass
+
+    return {
+        "version": "0+unknown",
+        "full-revisionid": None,
+        "dirty": None,
+        "error": "unable to compute version",
+        "date": None,
+    }
diff --git a/clarity/data/HOA_tools_cec2.py b/clarity/data/HOA_tools_cec2.py
index f95dfaa10..3e85c777c 100644
--- a/clarity/data/HOA_tools_cec2.py
+++ b/clarity/data/HOA_tools_cec2.py
@@ -12,20 +12,53 @@
 logger = logging.getLogger(__name__)
 
 
-# Code for generation ambisonic rotation matrices
+def convert_a_to_b_format(
+    front_left_up: np.ndarray,
+    front_right_down: np.ndarray,
+    back_left_down: np.ndarray,
+    back_right_up: np.ndarray,
+):
+    """Converts 1st order A format audio into 1st order B format.
+    For more information on ambisonic formats see Gerzon, Michael A.. “Ambisonics. Part two: Studio techniques.” (1975).
+
+    Args:
+        front_left_up (np.ndarray): Front-left-up audio
+        front_right_down (np.ndarray): Front-right-down audio
+        back_left_down (np.ndarray): Back-left-down audio
+        back_right_up (np.ndarray): Back-right-up audio
+
+    Raises:
+        TypeError: input must be numpy array
+        ValueError: all inputs must have same dimensions
+
+    Returns:
+        nd.array: 4xN array containing B-format audio. indexed w,x,y,z
+    """
+
+    shapes = [
+        front_left_up.shape,
+        front_right_down.shape,
+        back_left_down.shape,
+        back_right_up.shape,
+    ]
+    if not all(shape == shapes[0] for shape in shapes):
+        raise ValueError("All inputs need to have same dimensions")
 
+    w = 0.5 * sum([front_left_up, front_right_down, back_left_down, back_right_up])
+    x = 0.5 * (front_left_up - back_left_down) + (front_right_down - back_right_up)
+    y = 0.5 * (front_left_up - back_right_up) - (front_right_down - back_left_down)
+    z = 0.5 * (front_left_up - back_left_down) + (back_right_up - front_right_down)
 
+    return np.stack([w, x, y, z])
+
+
+# Code for generation ambisonic rotation matrices
 @njit
 def compute_rotation_matrix(n: int, foa_rotmat: np.ndarray) -> np.ndarray:
     """Generate a rotation matrix to rotate HOA soundfield.
-    Based on 'Rotation Matrices for Real Spherical Harmonics. Direct Determination
-    by Recursion' Joseph Ivanic and Klaus Ruedenberg J. Phys. Chem. 1996, 100, 15,
-    6342–6347 and Gorzel, M., Allen, A., Kelly, I., Kammerl, J., Gungormusler,
-    A., Yeh, H., and Boland, F., "Efficient Encoding and Decoding of Binaural Sound
-    with Resonance Audio", In proc. of the AES International Conference on Immersive
-    and Interactive Audio, March 2019
-    Operates on HOA of a given order rotates by azimuth theta
-    and elevation phi
+
+    Based on [1]_ and [2]_. Operates on HOA of a given order rotates by azimuth theta
+    and elevation phi.
 
     Args:
         order (int): order of ambisonic soundfield
@@ -33,6 +66,18 @@ def compute_rotation_matrix(n: int, foa_rotmat: np.ndarray) -> np.ndarray:
 
     Returns:
         np.ndarray: HOA rotation matrix
+
+    References:
+    .. [1] Ivanic J, Ruedenberg K (1996) Rotation Matrices for Real Spherical Harmonics.
+           Direct Determination J. Phys. Chem. 1996, 100(15):6342–6347. Available at
+           <https://pubs.acs.org/doi/10.1021/jp953350u> and
+           <https://doi.org/10.1021/JP9833350>
+
+    .. [2] Gorzel M, Allen A, Kelly I, Kammerl J, Gungormusler, A, Yeh H, Boland F
+           Efficient Encoding and Decoding of Binaural Sound with Resonance Audio
+           In Proc. of the AES International Conference on Immersive and Interactive
+           Audio, March 2019. Available at
+           <https://www.aes.org/e-lib/browse.cfm?elib=20446>
     """
     m = (n + 1) ** 2
     # construct rotamat for given order
@@ -59,122 +104,138 @@ def compute_rotation_matrix(n: int, foa_rotmat: np.ndarray) -> np.ndarray:
 
 
 @njit
-def centred_element(r: np.ndarray, i: int, j: int):
+def centred_element(reference: np.ndarray, row: int, col: int):
     """Get value from centered element indexing.
 
     Args:
-        r (matrix): input matrix
-        i (int): row index
-        j (int): column index
+        reference (matrix): reference input matrix
+        row (int): row index
+        col (int): column index
 
     Returns:
         Any: matrix element
     """
-    offset = int((r.shape[0] - 1) / 2)
-    if i > r.shape[0] or j > r.shape[1]:
+    offset = int((reference.shape[0] - 1) / 2)
+    if row > reference.shape[0] or col > reference.shape[1]:
         raise IndexError
-    output = r[i + offset, j + offset]
+    output = reference[row + offset, col + offset]
     return output
 
 
 @njit
-def P(i, a, b, el, r):
+def P(i, a, b, order, rotation_matrices):
     """P function for rotation matrix calculation.
 
     Args:
         i (int): index
         a (int): 'a' value
         b (int): 'b' value
-        el (int): order
+        order (int): order
         r (list(matrix)): rotation matrices
 
     Returns:
         float: P value
     """
     p = 0.0
-    if b == el:
-        p = (centred_element(r[1], i, 1) * centred_element(r[el - 1], a, el - 1)) - (
-            centred_element(r[1], i, -1) * centred_element(r[el - 1], a, -el + 1)
+    if b == order:
+        p = (
+            centred_element(rotation_matrices[1], i, 1)
+            * centred_element(rotation_matrices[order - 1], a, order - 1)
+        ) - (
+            centred_element(rotation_matrices[1], i, -1)
+            * centred_element(rotation_matrices[order - 1], a, -order + 1)
         )
-    elif b == -el:
-        p = (centred_element(r[1], i, 1) * centred_element(r[el - 1], a, -el + 1)) + (
-            centred_element(r[1], i, -1) * centred_element(r[el - 1], a, el - 1)
+    elif b == -order:
+        p = (
+            centred_element(rotation_matrices[1], i, 1)
+            * centred_element(rotation_matrices[order - 1], a, -order + 1)
+        ) + (
+            centred_element(rotation_matrices[1], i, -1)
+            * centred_element(rotation_matrices[order - 1], a, order - 1)
         )
     else:
-        p = centred_element(r[1], i, 0) * centred_element(r[el - 1], a, b)
+        p = centred_element(rotation_matrices[1], i, 0) * centred_element(
+            rotation_matrices[order - 1], a, b
+        )
 
     return p
 
 
 @njit
-def U(m, n, el, r):
+def U(degree, n, order, rotation_matrices):
     """U coefficient initialiser for rotation matrix calculation.
 
     Args:
-        m (int): degree
+        rotation_degree (int): Upper parameters of spherical harmonic component Y and n the lower.
         n (int): index
-        el (int): order
-        r (list(matrix)): rotation matrices
+        order (int): order
+        rotation_matrices (list(matrix)): rotation matrices
 
     Returns:
         float: U value
     """
-    return P(0, m, n, el, r)
+    return P(0, degree, n, order, rotation_matrices)
 
 
 @njit
-def V(m, n, el, r):
+def V(degree, n, order, rotation_matrices):
     """V coefficient initialiser for rotation matrix calculation.
 
     Args:
-        m (int): degree
+        degree (int): valid inputs are `int(|m|) <= order`.
         n (int): index
-        el (int): order
-        r (list(matrix)): rotation matrices
+        order (int): order
+        rotation_matrices (list(matrix)): rotation matrices
 
     Returns:
         float: V value
     """
     d = 0
-    if m == 0:
-        return P(1, 1, n, el, r) + P(-1, -1, n, el, r)
-    elif m > 0:
-        if m == 1:
-            d = 1.0
-        return P(1, m - 1, n, el, r) * np.sqrt(1 + d) - P(-1, -m + 1, n, el, r) * (
-            1 - d
+    if degree == 0:
+        return P(1, 1, n, order, rotation_matrices) + P(
+            -1, -1, n, order, rotation_matrices
         )
+    elif degree > 0:
+        if degree == 1:
+            d = 1.0
+        return P(1, degree - 1, n, order, rotation_matrices) * np.sqrt(1 + d) - P(
+            -1, -degree + 1, n, order, rotation_matrices
+        ) * (1 - d)
     else:
-        if m == -1:
+        if degree == -1:
             d = 1.0
-        return P(1, m + 1, n, el, r) * (1 - d) + P(-1, -m - 1, n, el, r) * np.sqrt(
-            1 + d
-        )
+        return P(1, degree + 1, n, order, rotation_matrices) * (1 - d) + P(
+            -1, -degree - 1, n, order, rotation_matrices
+        ) * np.sqrt(1 + d)
 
 
 @njit
-def W(m, n, el, r):
+def W(degree, n, order, rotation_matrices):
     """W coefficient initialiser for rotation matrix calculation.
 
     Args:
-        m (int): degree
+        degree (int): degree
         n (int): index
-        el (int): order
-        r (list(matrix)): rotation matrices
+        order (int): order
+        rotation_matrices (list(matrix)): rotation matrices
 
     Returns:
         float: W value
     """
-    if m == 0:
+    if degree == 0:
         return 0.0
-    elif m > 0:
-        return P(1, m + 1, n, el, r) + P(-1, -m - 1, n, el, r)
+    elif degree > 0:
+        return P(1, degree + 1, n, order, rotation_matrices) + P(
+            -1, -degree - 1, n, order, rotation_matrices
+        )
     else:
-        return P(1, m - 1, n, el, r) - P(-1, -m + 1, n, el, r)
+        return P(1, degree - 1, n, order, rotation_matrices) - P(
+            -1, -degree + 1, n, order, rotation_matrices
+        )
 
 
 @njit
-def compute_UVW_coefficients(m, n, el):
+def compute_UVW_coefficients(degree, n, order):
     """Compute U, V and W coefficients for rotation matrix calculation.
 
     Args:
@@ -185,28 +246,32 @@ def compute_UVW_coefficients(m, n, el):
     Returns:
         tuple: u, v, w
     """
-    d = 0
-    if m == 0:
-        d = 1
-    denom = float((el + n) * (el - n))
-    if np.abs(n) == el:
-        denom = float(2 * el * (2 * el - 1))
-
-    one_over_denom = 1.0 / denom
-    u = np.sqrt(float((el + m) * (el - m)) * one_over_denom)
+    d = 1 if degree == 0 else 0
+    denom = (
+        float(2 * order * (2 * order - 1))
+        if np.abs(n) == order
+        else float((order + n) * (order - n))
+    )
+
+    inverse_denom = 1.0 / denom
+    u = np.sqrt(float((order + degree) * (order - degree)) * inverse_denom)
     v = (
         0.5
         * np.sqrt(
             (1.0 + d)
-            * float(el + np.abs(m) - 1)
-            * (float(el + np.abs(m)))
-            * one_over_denom
+            * float(order + np.abs(degree) - 1)
+            * (float(order + np.abs(degree)))
+            * inverse_denom
         )
         * (1.0 - 2.0 * d)
     )
     w = (
         -0.5
-        * np.sqrt(float(el - np.abs(m) - 1) * float(el - np.abs(m)) * one_over_denom)
+        * np.sqrt(
+            float(order - np.abs(degree) - 1)
+            * float(order - np.abs(degree))
+            * inverse_denom
+        )
         * (1.0 - d)
     )
 
@@ -214,12 +279,12 @@ def compute_UVW_coefficients(m, n, el):
 
 
 @njit
-def compute_band_rotation(el, rotations, output):
+def compute_band_rotation(order, rotation_matrices, output):
     """Compute submatrix for rotation matrix.
 
     Args:
-        el (int): order of submatrix
-        rotations (list(matrix)): previous and current submatrices
+        order (int): order of submatrix
+        rotationmatrices (list(matrix)): previous and current submatrices
         output (matrix): output destination
 
     Returns:
@@ -227,28 +292,25 @@ def compute_band_rotation(el, rotations, output):
     """
     # print(f'entering band rotation with l = {el}')
 
-    for mm, m in enumerate(np.arange(-el, el + 1, 1)):
-        for nn, n in enumerate(np.arange(-el, el + 1, 1)):
-            u, v, w = compute_UVW_coefficients(m, n, el)
+    for row, m in enumerate(np.arange(-order, order + 1, 1)):
+        for col, n in enumerate(np.arange(-order, order + 1, 1)):
+            u, v, w = compute_UVW_coefficients(m, n, order)
             if np.abs(u) > 0.0:
-                uu = U(m, n, el, rotations)
-                u *= uu
+                u *= U(m, n, order, rotation_matrices)
             if np.abs(v) > 0.0:
-                vv = V(m, n, el, rotations)
-                v *= vv
+                v *= V(m, n, order, rotation_matrices)
             if np.abs(w) > 0.0:
-                ww = W(m, n, el, rotations)
-                w *= ww
-            rotations[el][mm, nn] = u + v + w
+                w *= W(m, n, order, rotation_matrices)
+            rotation_matrices[order][row, col] = u + v + w
 
-    starting_index = el * el
+    starting_index = order * order
 
     output[
-        starting_index : (starting_index + (el * 2 + 1)),
-        starting_index : (starting_index + (el * 2 + 1)),
-    ] = rotations[el]
+        starting_index : (starting_index + (order * 2 + 1)),
+        starting_index : (starting_index + (order * 2 + 1)),
+    ] = rotation_matrices[order]
 
-    return output, rotations
+    return output, rotation_matrices
 
 
 @njit
@@ -292,19 +354,19 @@ def __init__(self, order, resolution):
             foa_rotmat = np.linalg.inv(foa_rotmat)
             self.rotmat[i, :, :] = compute_rotation_matrix(order, foa_rotmat)
 
-    def rotate(self, signal, th):
+    def rotate(self, signal, rotation_vector):
         """Apply rotation to HOA signals using precomputed rotation matrices.
 
         Args:
             signal (array-like): ambisonic signals
-            th (array-like): rotation vector (in radians)
+            rotation_vector (array-like): rotation vector (in radians)
 
         Returns:
             array-like: transformed ambisonic signals
         """
         # Convert to lookup table indices
         # NOTE: Using generators below - using list comprehensions is *much* slower
-        theta_i = (th / np.pi * 180) / self.resolution  # convert to index
+        theta_i = (rotation_vector / np.pi * 180) / self.resolution  # convert to index
         theta_0 = np.floor(theta_i).astype(int)
         n_entries = self.rotmat.shape[0]  # size of table
         t_r0 = (self.rotmat[t % n_entries] for t in theta_0)
@@ -334,8 +396,8 @@ def binaural_mixdown(ambisonic_signals, hrir, hrir_metadata):
     """
     # weights = np.array(hrir_metadata["weights"])
     matrix = np.array(hrir_metadata["matrix"])
-    logger.info("decoding signal with shape %s", ambisonic_signals.shape)
-    logger.info("Decoding to %s positions", matrix.shape[0])
+    logger.info(f"Decoding signal with shape {ambisonic_signals.shape}")
+    logger.info("Decoding to {matrix.shape[0]} positions")
 
     # Decode to loudspeaker positions
 
@@ -346,31 +408,38 @@ def binaural_mixdown(ambisonic_signals, hrir, hrir_metadata):
     inv_matrix = np.linalg.pinv(matrix[:, 0:n_chans])
     y = np.dot(ambisonic_signals, inv_matrix)
 
-    z = np.zeros([y.shape[0] + hrir["M_data"].shape[0] - 1, 2])
+    stereo_audio = np.zeros([y.shape[0] + hrir["M_data"].shape[0] - 1, 2])
     hrir_data = hrir["M_data"][:, hrir_metadata["selected_channels"], :]
     for i in np.arange(y.shape[1]):
-        z[:, 0] += convolve(y[:, i], hrir_data[:, i, 0])
-        z[:, 1] += convolve(y[:, i], hrir_data[:, i, 1])
+        stereo_audio[:, 0] += convolve(y[:, i], hrir_data[:, i, 0])
+        stereo_audio[:, 1] += convolve(y[:, i], hrir_data[:, i, 1])
 
-    return z
+    return stereo_audio
 
 
-def ambisonic_convolve(signal: np.ndarray, ir: np.ndarray, order: int) -> np.ndarray:
-    """Convolve HOAIRs with signals.
+def ambisonic_convolve(
+    signal: np.ndarray, hoa_impulse_responses: np.ndarray, order: int
+) -> np.ndarray:
+    """Convolve HOA Impulse Responses with signals.
 
     Args:
         signal (ndarray[samples]): the signal to convole
-        ir (ndarray[samples, channels]): the HOA impulse responses
+        hoa_impulse_response (ndarray[samples, channels]): the HOA impulse responses
         order (int, optional): ambisonic order.
 
     Returns:
         np.ndarray[samples, channels]: the convolved signal
     """
     n = (order + 1) ** 2
-    return np.array([convolve(ir_, signal) for ir_ in ir[:, 0:n].T]).T
+    return np.array(
+        [
+            convolve(impulse_response, signal)
+            for impulse_response in hoa_impulse_responses[:, 0:n].T
+        ]
+    ).T
 
 
-def compute_rms(input_signal: np.ndarray, axis: int = 0):
+def compute_rms(input_signal: np.ndarray, axis: int = 0) -> float:
     """Compute rms values along a given axis.
     Args:
         input_signal (np.ndarray): Input signal
@@ -435,14 +504,14 @@ def smoothstep(
 
 
 def rotation_control_vector(
-    array_length: int, start_idx: int, end_idx: int, smoothness: int = 1
+    array_length: int, start_index: int, end_index: int, smoothness: int = 1
 ) -> np.ndarray:
     """Generate mapped rotation control vector for values of theta.
 
     Args:
-        array_length (int)
-        start_idx (int)
-        end_idx (int)
+        array_length (int): Length of array
+        start_index (int): Start position
+        end_index (int)
         smoothness (int, optional) Defaults to 1.
 
     Returns:
@@ -450,14 +519,13 @@ def rotation_control_vector(
     """
     # assert end_idx > start_idx
     # assert array_length > end_idx
-    if start_idx > end_idx:
-        raise ValueError(f"start_idx ({start_idx}) > end_idx ({end_idx})")
-    if end_idx > array_length:
-        raise ValueError(f"array_length ({array_length}) > end_idx {end_idx}")
+    if start_index > end_index:
+        raise ValueError(f"start_index ({start_index}) > end_index ({end_index})")
+    if end_index > array_length:
+        raise ValueError(f"array_length ({array_length}) > end_index {end_index}")
     x = np.arange(0, array_length)
-    idx = smoothstep(x, x_min=start_idx, x_max=end_idx, N=smoothness)
-    idx = np.array(np.floor(idx * (array_length - 1)), dtype=int)
-    return idx
+    idx = smoothstep(x, x_min=start_index, x_max=end_index, N=smoothness)
+    return np.array(np.floor(idx * (array_length - 1)), dtype=int)
 
 
 def rotation_vector(
diff --git a/clarity/data/demo_data.py b/clarity/data/demo_data.py
index 2e2ed63ac..7fcc54b96 100644
--- a/clarity/data/demo_data.py
+++ b/clarity/data/demo_data.py
@@ -1,3 +1,4 @@
+"""Functions for downloading demo data."""
 import os
 from pathlib import Path
 
@@ -7,7 +8,17 @@
 PACKAGE_NAME = "data.tgz"
 
 
-def get_demo_data(metadata_url, target_dir):
+def get_demo_data(metadata_url: str, target_dir: str) -> None:
+    """Download demo data.
+
+    Args:
+        metadata_url (str): URL to download data from (should be a link on Google Drive).
+        target_dir (str): Directory to save to (default 'clarity_data/demo'), will be created
+            if it doesn't exist.
+
+    Returns:
+        None
+    """
     gdown.download(metadata_url, PACKAGE_NAME, quiet=False)
     p = Path(target_dir)
     if p.exists() is False:
@@ -16,31 +27,37 @@ def get_demo_data(metadata_url, target_dir):
     os.system(f"rm {PACKAGE_NAME}")
 
 
-def get_metadata_demo(target_dir=TARGET_DIR):
+def get_metadata_demo(target_dir: str = TARGET_DIR) -> None:
+    """Download metadata."""
     url = "https://drive.google.com/uc?export=download&id=14KGm2GaRgwlrZvtmMwWTYu7itaRVQV8f"
     get_demo_data(url, target_dir)
 
 
-def get_targets_demo(target_dir=TARGET_DIR):
+def get_targets_demo(target_dir: str = TARGET_DIR) -> None:
+    """Download targets."""
     url = "https://drive.google.com/uc?export=download&id=1uu2Hes1fzqNaZSCiFNhxZM3bE_fAVKsD"
     get_demo_data(url, target_dir)
 
 
-def get_interferers_demo(target_dir=TARGET_DIR):
+def get_interferers_demo(target_dir: str = TARGET_DIR) -> None:
+    """Download interferers."""
     url = "https://drive.google.com/uc?export=download&id=1_ssD238Qv-EETzC0hJze7JhLE7bHyqwG"
     get_demo_data(url, target_dir)
 
 
-def get_rooms_demo(target_dir=TARGET_DIR):
+def get_rooms_demo(target_dir: str = TARGET_DIR) -> None:
+    """Download rooms."""
     url = "https://drive.google.com/uc?export=download&id=1FBC8DI4Ru-g3Set0fDzoKmXTqHqNXV8n"
     get_demo_data(url, target_dir)
 
 
-def get_scenes_demo(target_dir=TARGET_DIR):
+def get_scenes_demo(target_dir: str = TARGET_DIR) -> None:
+    """Download secnes."""
     url = "https://drive.google.com/uc?export=download&id=1PB0CfGXhpkYNk8HbE5lTWowm2016x6Hl"
     get_demo_data(url, target_dir)
 
 
-def get_hrirs_demo(target_dir=TARGET_DIR):
+def get_hrirs_demo(target_dir: str = TARGET_DIR) -> None:
+    """Download hiris."""
     url = "https://drive.google.com/uc?export=download&id=1USrHLFhOE_jdAQcEKqumG3M5dEZVcwSd"
     get_demo_data(url, target_dir)
diff --git a/clarity/data/scene_builder_cec2.py b/clarity/data/scene_builder_cec2.py
index d76bfed81..1759a45d2 100644
--- a/clarity/data/scene_builder_cec2.py
+++ b/clarity/data/scene_builder_cec2.py
@@ -6,6 +6,7 @@
 import random
 import re
 from enum import Enum
+from typing import Dict, List
 
 import numpy as np
 from tqdm import tqdm
@@ -38,7 +39,7 @@ def set_random_seed(random_seed):
         np.random.seed(random_seed)
 
 
-def get_vector(text, vector_name):
+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"
@@ -48,16 +49,16 @@ def get_vector(text, vector_name):
         vector_name (str): name of vector to extract (e.g. "sourceViewVectors")
 
     Returns:
-        list[float]: vector as list of floats
+        List[float]: vector as list of floats
     """
     findall_str = f".*{vector_name}.*"
-    x = re.findall(findall_str, text)
-    x = re.sub(" ", "", x[0].split("=")[-1])
-    x = [float(idx) for idx in x.split(",")]
+    x_str = re.findall(findall_str, text)
+    x_str = re.sub(" ", "", x_str[0].split("=")[-1])  # type: ignore
+    x = [float(idx) for idx in x_str.split(",")]  # type: ignore
     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:
@@ -86,16 +87,23 @@ 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):
-    """Process an rpf file and return key contents as a dictionary."""
+def read_rpf_file(rpf_filename: str) -> Dict:
+    """Process an rpf file and return key contents as a dictionary.
+
+    Args:
+        rpf_filename (str): Path to an rpf file to be read.
+
+    Returns:
+        dict: dictionary of rpf file contents {"position": sourcePositions, "view_vector": sourceViewVectors}
+    """
     with open(rpf_filename, "r", encoding="utf-8") as f:
         text = f.read()
 
     rpf_dict = {}
 
     room_dict = {}
-    room_dict["name"] = get_room_name(text)
-    room_dict["dimensions"] = get_room_dims(text)
+    room_dict["name"] = get_room_name(text)  # type: ignore
+    room_dict["dimensions"] = get_room_dims(text)  # type: ignore
 
     rpf_dict["room"] = room_dict
 
@@ -103,38 +111,33 @@ def read_rpf_file(rpf_filename):
     # rpf_dict["hrirfilename"] = get_hrir_filename(text)
 
     rpf_dict["source"] = {
-        "position": get_vector(text, "sourcePositions"),
-        "view_vector": get_vector(text, "sourceViewVectors"),
+        "position": get_vector(text, "sourcePositions"),  # type: ignore
+        "view_vector": get_vector(text, "sourceViewVectors"),  # type: ignore
     }
 
     rpf_dict["receiver"] = {
-        "position": get_vector(text, "receiverPositions"),
-        "view_vector": get_vector(text, "receiverViewVectors"),
+        "position": get_vector(text, "receiverPositions"),  # type: ignore
+        "view_vector": get_vector(text, "receiverViewVectors"),  # type: ignore
     }
 
     return rpf_dict
 
 
-def build_room(target_file, interferer_files):
+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.
 
     Args:
         target_file (str): rpf file containing the target position
-        interferer_files (list[str]): list of files containing the interferer positions
+        interferer_files (List[str]): list of files containing the interferer positions
 
     Returns:
         dict: dictionary representation of the scene following CEC2 scene.json format
     """
-    scene_dict = {}
-    scene_dict["target"] = {}
-    scene_dict["listener"] = {}
-    scene_dict["interferer"] = {}
-
     rpf_dict = read_rpf_file(target_file)
 
-    scene_dict = {}
+    scene_dict: dict = {}
     scene_dict["name"] = rpf_dict["room"]["name"]
     scene_dict["dimensions"] = rpf_dict["room"]["dimensions"]
     scene_dict["target"] = rpf_dict["source"]
@@ -153,8 +156,19 @@ def build_room(target_file, interferer_files):
     return scene_dict
 
 
-def make_rpf_filename_dict(rpf_location, scene_str, n_interferers):
-    """Construct dictionary storing all rpf files that will be processed."""
+def make_rpf_filename_dict(
+    rpf_location: str, scene_str: str, n_interferers: int
+) -> Dict:
+    """Construct dictionary storing all rpf files that will be processed.
+
+    Args:
+        rpf_location (str): Location
+        scene (str): Scene (as string)
+        n_interferers (int): Number of interferers.
+
+    Returns:
+        dict: Dictionary of rpf files to be processed.
+    """
     rpf_filename_format = "{rpf_location}/{scene_str}_{source}.rpf"
 
     rpf_filedict = {}
@@ -167,33 +181,48 @@ def make_rpf_filename_dict(rpf_location, scene_str, n_interferers):
             rpf_location=rpf_location, scene_str=scene_str, source=f"i{i}"
         )
         for i in range(1, n_interferers + 1)
-    ]
+    ]  # type: ignore
     return rpf_filedict
 
 
 # target handling
-def get_num_pre_samples(pre_samples_range):
-    """Number of samples prior to target onset."""
+def get_num_pre_samples(pre_samples_range: list) -> int:
+    """Number of samples prior to target onset.
+
+    Args:
+        pre_samples_range (list): parameters for number of samples priot to target onset.
+
+    Returns:
+    """
     return random.randint(*pre_samples_range)
 
 
-def get_num_post_samples(post_samples_range):
-    """Number of samples to continue player after target offsets."""
+def get_num_post_samples(post_samples_range: list) -> int:
+    """Number of samples to continue player after target offsets.
+
+
+    Args:
+        post_samples_range (list): parameters for number of samples to continue.
+
+    Returns:
+    """
     return random.randint(*post_samples_range)
 
 
-def add_this_target_to_scene(target, scene, pre_samples_range, post_samples_range):
+def add_this_target_to_scene(
+    target: dict, scene: dict, pre_samples_range: list, post_samples_range: list
+):
     """Add the target details to the scene dict.
-    Adds given target to given scene. Target details will be taken
-    from the target dict but the start time will be
-    according to the CEC2 target start time specification.
+
+    Adds given target to given scene. Target details will be taken from the target dict but
+    the start time will be according to the CEC2 target start time specification.
 
     Args:
-        target (dict): target dict read from target metadata file
-        scene (dict): complete scene dict
-        pre_samples_range (list): parameters for number of samples prior to target onset
+        target (dict): target dict read from target metadata file.
+        scene (dict): complete scene dictionary.
+        pre_samples_range (list): parameters for number of samples prior to target onset.
         post_samples_range (list): parameters for number of samples to continue
-            player after target offsets
+            player after target offsets.
     """
     scene_target = {}
     num_pre_samples = get_num_pre_samples(pre_samples_range)
@@ -206,8 +235,16 @@ def add_this_target_to_scene(target, scene, pre_samples_range, post_samples_rang
 
 
 # SNR handling
-def generate_snr(snr_range):
-    """Generate a random SNR."""
+def generate_snr(snr_range: List[int]) -> float:
+    """Generate a random Signal Noise Ratio.
+
+    Args:
+        snr_range (list): Range of values for a Uniform distribution from which to sample
+            Signal Noise Ratio.
+
+    Returns:
+        float: random number from uniform distribution in given range.
+    """
     return random.uniform(*snr_range)
 
 
@@ -220,8 +257,9 @@ class InterfererType(Enum):
     MUSIC = "music"
 
 
-def select_interferer_types(allowed_n_interferers):
+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.
     The type of each is chosen randomly but there is not allowed to be
     more than 1 music source.
@@ -244,7 +282,9 @@ def valid(selection):
     return selection
 
 
-def select_random_interferer(interferers, dataset, required_samples):
+def select_random_interferer(
+    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.
@@ -276,7 +316,7 @@ def select_random_interferer(interferers, dataset, required_samples):
     return interferer
 
 
-def get_random_interferer_offset(interferer, required_samples):
+def get_random_interferer_offset(interferer: dict, required_samples: int) -> int:
     """Generate a random offset sample for interferer.
     The offset sample is the point within the masker signal at which the interferer
     segment will be extracted. Randomly selected but with care for it not to start
@@ -302,7 +342,11 @@ def get_random_interferer_offset(interferer, required_samples):
 
 
 def add_interferer_to_scene_inner(
-    scene, interferers, number, start_time_range, end_early_time_range
+    scene: dict,
+    interferers: dict,
+    number: list,
+    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
@@ -322,9 +366,9 @@ def add_interferer_to_scene_inner(
     Args:
         scene (dict): the scene description
         interferers (dict): the interferer metadata
-        number: number of interferers
-        start_time_range: when to start
-        end_early_time_range: when to end
+        number (list): number of interferers
+        start_time_range (list): range of starting points as integers, a random number is selected between these.
+        end_early_time_range (list): range of end points as integers, a random number is selected between these.
     """
     dataset = scene["dataset"]
     selected_interferer_types = select_interferer_types(number)
@@ -372,28 +416,25 @@ def get_random_hrir_set(heads, channels):
 
 
 def generate_rotation(
-    scene,
-    relative_start_time_range,
-    duration_mean,
-    duration_sd,
-    angle_initial_mean,
-    angle_initial_sd,
-    angle_final_range,
-):
+    scene: dict,
+    relative_start_time_range: list,
+    duration_mean: float,
+    duration_sd: float,
+    angle_initial_mean: float,
+    angle_initial_sd: float,
+    angle_final_range: tuple,
+) -> List[dict]:
     """Generate a suitable head rotation for the given scene.
     Based on behavioural studies by Hadley et al. TODO: find ref
 
     Args:
         scene (dict): the scene description
-        head_turn_cfg (dict): head turn hyperparameters
-    head_turn_cfg has the following fields:
-        start_offset_mean - mean of the time offset for start of turn
-        start_offset_sd - standard deviation of the time offset for start of turn
-        angle_initial_mean
-        angle_initial_sd
-        angle_final_range (tuple)
-        duration_mean - mean of the duration of the turn
-        duration_sd - standard deviation of the duration of the turn
+        relative_start_time_range (list): Range from which start time is selected at random.
+        duration_mean (float): mean of the time offset for start of turn
+        duration_sd (float): standard deviation of the time offset for start of turn
+        angle_initial_mean (float):
+        angle_initial_sd (float):
+        angle_final_range (tuple):
 
     Returns:
         list(dict): list of dicts with keys "sample" and "view_vector"
@@ -411,7 +452,7 @@ def generate_rotation(
     random_sign = 1 if random.random() < 0.5 else -1
 
     # Generate the random start and end angle offsets for the head rotation
-    offset = 0
+    offset = 0.0
     while True:
         offset = random.gauss(0, angle_initial_sd)
         # All rotations kept within 2 sigma of the mean
@@ -452,8 +493,14 @@ def __init__(self):
         self.rooms = []
         self.room_dict = {}
 
-    def get_room(self, name):
-        """Get a room by name."""
+    def get_room(self, name: str):
+        """Get a room by name.
+
+        Args:
+            name (str): Name of room to extract.
+
+        Returns:
+        """
         return self.room_dict[name]
 
     def rebuild_dict(self):
@@ -485,14 +532,22 @@ def build_from_rpf(
         ]
         self.rebuild_dict()
 
-    def load(self, filename):
-        """Load the list of room from a json file."""
+    def load(self, filename) -> None:
+        """Load the list of room from a JSON file.
+
+        Args:
+            filename (str): filename to JSON file from which room data is loaded.
+        """
         with open(filename, "r", encoding="utf-8") as f:
             self.rooms = json.load(f)
         self.rebuild_dict()
 
     def save_rooms(self, filename):
-        """Save the list of rooms to a json file."""
+        """Save the list of rooms to a JSON file.
+
+        Args:
+            filename (str): filename to JSON file to which room data is saved.
+        """
         with open(filename, "w", encoding="utf-8") as f:
             json.dump(self.rooms, f, indent=2)
 
@@ -510,6 +565,17 @@ def __init__(
         listener,
         shuffle_rooms=None,
     ):
+        """Initialise SceneBuilder class.
+
+        Args:
+            rb ():
+            scene_datasets (dict): Dictionary of parametesr for initialising scenes.
+            target (dict): Dictionary of parameters for adding target to scenes.
+            interferer (dict): Dictionary of parameters for adding interferer to scenes.
+            snr_range (list): Range of values from which to sample Signal Noise Ratio.
+            listener (dict): Dictionary of parameters for adding listener to scenes.
+            shuffle_rooms=None ():
+        """
         self.rb = rb
         self.scenes = []
         self.scene_datasets = scene_datasets
@@ -519,8 +585,15 @@ def __init__(
         self.listener = listener
         self.shuffle_rooms = shuffle_rooms
 
-    def save_scenes(self, filename):
-        """Save the list of scenes to a json file."""
+    def save_scenes(self, filename: str) -> None:
+        """Save the list of scenes to a JSON file.
+
+        Args:
+            filename (str): Filename to save scenes to.
+
+        Returns:
+            None
+        """
         scenes = [s for s in self.scenes]
         # Replace the room structure with the room ID
         for scene in scenes:
@@ -528,27 +601,34 @@ def save_scenes(self, filename):
         with open(filename, "w", encoding="utf-8") as f:
             json.dump(self.scenes, f, indent=2)
 
-    def instantiate_scenes(self, dataset):
-        log.info("Initialise %s scenes", dataset)
+    def instantiate_scenes(self, dataset) -> None:
+        """Instantiate scenes with targets, interferers, signal noise ratio and listeners.
+
+        Args:
+            dataset:
+        """
+        log.info(f"Initialise {dataset} scenes")
         self.initialise_scenes(dataset, **self.scene_datasets)
-        log.info("adding targets to scenes")
+        log.info("Adding targets to scenes")
         self.add_target_to_scene(dataset, **self.target)
-        log.info("adding interferers to scenes")
+        log.info("Adding interferers to scenes")
         self.add_interferer_to_scene(**self.interferer)
-        log.info("assigning an SNR to each scene")
+        log.info("Assigning an SNR to each scene")
         self.add_SNR_to_scene(self.snr_range)
-        log.info("adding listener behaviours to scenes")
+        log.info("Adding listener behaviours to scenes")
         self.add_listener_details_to_scene(**self.listener)
 
-    def initialise_scenes(self, dataset, n_scenes, room_selection, scene_start_index):
+    def initialise_scenes(
+        self, dataset, n_scenes: int, room_selection: str, scene_start_index: int
+    ):
         """
         Initialise the scenes for a given dataset.
 
         Args:
             dataset: train, dev, or eval set
-            n_scenes: number of scenes to generate
-            room_selection: SEQUENTIAL or RANDOM
-            scene_start_index: index to start for scene IDs
+            n_scenes (int): number of scenes to generate
+            room_selection (str): SEQUENTIAL or RANDOM
+            scene_start_index (int): index to start for scene IDs
         """
         rooms = self.rb.rooms.copy()
         if self.shuffle_rooms:
@@ -576,15 +656,23 @@ def initialise_scenes(self, dataset, n_scenes, room_selection, scene_start_index
 
     def add_target_to_scene(
         self,
-        dataset,
-        target_speakers,
-        target_selection,
-        pre_samples_range,
-        post_samples_range,
+        dataset: str,
+        target_speakers: str,
+        target_selection: str,
+        pre_samples_range: list,
+        post_samples_range: list,
     ):
         """Add target info to the scenes.
         Target speaker file set via config.
 
+        Args:
+            dataset (str): dataset to be added.
+            target_speakers (str):
+            target_selection (str): Type of target to be added, valid values are 'SEQUENTIAL' and 'RANDOM'.
+            pre_samples_range (list): Parameters for number of samples prior to target onset.
+            post_samples_range (list): Parameters for number osamples to continue player after target offsets.
+
+
         Raises:
             Exception: _description_
         """
@@ -614,21 +702,39 @@ def add_target_to_scene(
         else:
             assert False, "Unknown target selection mode"
 
-    def add_SNR_to_scene(self, snr_range):
-        """Add the SNR info to the scenes."""
+    def add_SNR_to_scene(self, snr_range: list):
+        """Add the Signal Noise Ratio info to the scenes.
+
+        Args:
+            snr_range (list): Range of values from which Signal Noise Ratio will be sampled.
+
+        Returns:
+        """
         for scene in tqdm(self.scenes):
             scene["SNR"] = generate_snr(snr_range)
 
     def add_interferer_to_scene(
         self,
-        speech_interferers,
-        noise_interferers,
-        music_interferers,
-        number,
-        start_time_range,
-        end_early_time_range,
+        speech_interferers: str,
+        noise_interferers: str,
+        music_interferers: str,
+        number: list,
+        start_time_range: list,
+        end_early_time_range: list,
     ):
-        """Add interferer to the scene description file."""
+        """Add interferer to the scene description file.
+
+        Args:
+            speech_interferers (str): Path to speech interferer to load.
+            noise_interferers (str): Path to noise interferer to load.
+            music_interferers (str): Path to music interferer to load
+            number (list): Number of interefers to be added.
+            start_time_range (list): Range for randomly selecting start point.
+            end_early_time_range (list): Range for randomly selecting end point.
+
+        Returns:
+            None
+        """
         # Load and prepare speech interferer metadata
         with open(speech_interferers, "r", encoding="utf-8") as f:
             interferers_speech = json.load(f)
@@ -644,7 +750,7 @@ def add_interferer_to_scene(
             interferers_noise = json.load(f)
         for interferer in interferers_noise:
             interferer["ID"] += ".wav"
-        interferer_by_type = {}
+        interferer_by_type: dict = {}
         for interferer in interferers_noise:
             interferer_by_type.setdefault(interferer["class"], []).append(interferer)
         interferers_noise = list(interferer_by_type.values())
@@ -673,14 +779,28 @@ def add_listener_details_to_scene(
         self,
         heads,
         channels,
-        relative_start_time_range,
-        duration_mean,
-        duration_sd,
-        angle_initial_mean,
-        angle_initial_sd,
-        angle_final_range,
+        relative_start_time_range: list,
+        duration_mean: float,
+        duration_sd: float,
+        angle_initial_mean: float,
+        angle_initial_sd: float,
+        angle_final_range: tuple,
     ):
-        """Add the listener info to the scenes."""
+        """Add the listener info to the scenes.
+
+        Args:
+            heads ():
+            channels ():
+            relative_start_time_range (list): Range from which start time is selected at random.
+            duration_mean (float): mean of the time offset for start of turn
+            duration_sd (float): standard deviation of the time offset for start of turn
+            angle_initial_mean (float):
+            angle_initial_sd (float):
+            angle_final_range (tuple):
+
+        Returns:
+            None
+        """
         for scene in tqdm(self.scenes):
             listener = {}
             listener["rotation"] = generate_rotation(
diff --git a/clarity/data/scene_renderer_cec1.py b/clarity/data/scene_renderer_cec1.py
index 050a9bd7b..eae0df73b 100644
--- a/clarity/data/scene_renderer_cec1.py
+++ b/clarity/data/scene_renderer_cec1.py
@@ -1,3 +1,4 @@
+"""Scene rendering for CEC1 challenge."""
 import logging
 import math
 import os
@@ -22,19 +23,18 @@ def __init__(
         input_path,
         output_path,
         num_channels=1,
-        fs=44100,
+        sample_rate=44100,
         ramp_duration=0.5,
         tail_duration=0.2,
         pre_duration=2.0,
         post_duration=1.0,
         test_nbits=16,
     ):
-
         self.input_path = input_path
         self.output_path = output_path
-        self.fs = fs
+        self.sample_rate = sample_rate
         self.ramp_duration = ramp_duration
-        self.n_tail = int(tail_duration * fs)
+        self.n_tail = int(tail_duration * sample_rate)
         self.pre_duration = pre_duration
         self.post_duration = post_duration
         self.test_nbits = test_nbits
@@ -59,7 +59,7 @@ def read_signal(
             offset_is_samples (bool): measurement units for offset (default: False)
 
         Returns:
-            ndarray: audio signal
+            np.ndarray: audio signal
         """
         try:
             wave_file = SoundFile(filename)
@@ -72,8 +72,10 @@ def read_signal(
                 f"Wav file ({filename}) was expected to have {nchannels} channels."
             )
 
-        if wave_file.samplerate != self.fs:
-            raise Exception(f"Sampling rate is not {self.fs} for filename {filename}.")
+        if wave_file.samplerate != self.sample_rate:
+            raise Exception(
+                f"Sampling rate is not {self.sample_rate} for filename {filename}."
+            )
 
         if not offset_is_samples:  # Default behaviour
             offset = int(offset * wave_file.samplerate)
@@ -84,38 +86,63 @@ def read_signal(
         x = wave_file.read(frames=nsamples)
         return x
 
-    def write_signal(self, filename, x, fs, floating_point=True):
-        """Write a signal as fixed or floating point wav file."""
+    def write_signal(
+        self,
+        filename: str,
+        signal: np.ndarray,
+        sample_rate: int,
+        floating_point: bool = True,
+    ) -> None:
+        """Write a signal as fixed or floating point wav file.
+
+        Args:
+            filename (string): Name of file to write to.
+            signal (np.ndarray): Array to write.
+            sample_rate (int): Sample Rate
+            floating_point (bool): Whether to write as subtype of floating point
+
+        Returns:
+            None: Does not return anything, writes signal to given filename.
+        """
 
-        if fs != self.fs:
-            logging.warning("Sampling rate mismatch: %s with sr=%s.", filename, fs)
+        if sample_rate != self.sample_rate:
+            logging.warning(
+                f"Sampling rate mismatch: {filename} with sample rate={sample_rate}."
+            )
             # raise ValueError("Sampling rate mismatch")
 
         if floating_point is False:
             if self.test_nbits == 16:
                 subtype = "PCM_16"
                 # If signal is float and we want int16
-                x *= 32768
-                x = x.astype(np.dtype("int16"))
-                assert np.max(x) <= 32767 and np.min(x) >= -32768
+                signal *= 32768
+                signal = signal.astype(np.dtype("int16"))
+                assert np.max(signal) <= 32767 and np.min(signal) >= -32768
             elif self.test_nbits == 24:
                 subtype = "PCM_24"
         else:
             subtype = "FLOAT"
 
-        soundfile.write(filename, x, fs, subtype=subtype)
+        soundfile.write(filename, signal, sample_rate, subtype=subtype)
+
+    def apply_ramp(self, signal, ramp_duration):
+        """Apply half cosine ramp into and out of signal.
 
-    def apply_ramp(self, x, dur):
-        """Apply half cosine ramp into and out of signal
+        Args:
+            signal (np.ndarray): signal to be ramped.
+            ramp_duration (int): ramp duration in seconds.
 
-        dur - ramp duration in seconds
+        Returns:
+            np.ndarray: Signal ramped into and out of by cosine function.
         """
-        ramp = np.cos(np.linspace(math.pi, 2 * math.pi, int(self.fs * dur)))
+        ramp = np.cos(
+            np.linspace(math.pi, 2 * math.pi, int(self.sample_rate * ramp_duration))
+        )
         ramp = (ramp + 1) / 2
-        y = np.array(x)
-        y[0 : len(ramp)] *= ramp
-        y[-len(ramp) :] *= ramp[::-1]
-        return y
+        signal_ramped = np.array(signal)
+        signal_ramped[0 : len(ramp)] *= ramp
+        signal_ramped[-len(ramp) :] *= ramp[::-1]
+        return signal_ramped
 
     def apply_brir(self, signal, brir):
         """Convolve a signal with a BRIR.
@@ -143,37 +170,50 @@ def apply_brir(self, signal, brir):
         output = np.vstack([signal_l, signal_r]).T
         return output[0:output_len, :]
 
-    def compute_snr(self, target, noise, pre_samples=0, post_samples=-1):
-        """Return the SNR.
+    def compute_snr(
+        self, target: np.ndarray, noise: np.ndarray, pre_samples=0, post_samples=-1
+    ):
+        """Return the Signal Noise Ratio (SNR).
+
         Take the overlapping segment of the noise and get the speech-weighted
-        better ear SNR. (Note, SNR is a ratio -- not in dB.)
+        better ear Signal Noise Ratio. (Note, SNR is a ratio -- not in dB.)
+
+        Args:
+            target (np.ndarray): Target signal.
+            noise (np.ndarray): Noise (should be same length as target)
+
+        Returns:
+            float: signal_noise_ratio for better ear.
         """
 
-        pre_samples = int(self.fs * self.pre_duration)
-        post_samples = int(self.fs * self.post_duration)
+        pre_samples = int(self.sample_rate * self.pre_duration)
+        post_samples = int(self.sample_rate * self.post_duration)
 
         segment_target = target[pre_samples:-post_samples]
         segment_noise = noise[pre_samples:-post_samples]
-        assert len(segment_target) == len(segment_noise)
+        try:
+            assert len(segment_target) == len(segment_noise)
+        except AssertionError as e:
+            raise ValueError(
+                f"Target ({len(segment_target)}) differs in length from Noise ({len(segment_noise)})"
+            ) from e
 
         snr = better_ear_speechweighted_snr(segment_target, segment_noise)
-
         return snr
 
     def render(
         self,
-        target,
-        noise_type,
-        interferer,
-        room,
-        scene,
+        target: str,
+        noise_type: str,
+        interferer: str,
+        room: str,
+        scene: str,
         offset,
-        snr_dB,
+        snr_dB: int,
         dataset,
         pre_samples=88200,
         post_samples=44100,
     ):
-
         brir_stem = f"{self.input_path}/{dataset}/rooms/brir/brir_{room}"
         anechoic_brir_stem = f"{self.input_path}/{dataset}/rooms/brir/anech_brir_{room}"
         target_fn = f"{self.input_path}/{dataset}/targets/{target}.wav"
@@ -184,7 +224,7 @@ def render(
         target = self.read_signal(target_fn)
         target = np.pad(target, [(pre_samples, post_samples)])
 
-        interferer = self.read_signal(
+        interferer_signal = self.read_signal(
             interferer_fn, offset=offset, nsamples=len(target), offset_is_samples=True
         )
 
@@ -192,12 +232,14 @@ def render(
             logging.debug("Target and interferer have different lengths")
 
         # Apply 500ms half-cosine ramp
-        interferer = self.apply_ramp(interferer, dur=self.ramp_duration)
+        interferer_signal = self.apply_ramp(
+            interferer_signal, ramp_duration=self.ramp_duration
+        )
 
         prefix = f"{self.output_path}/{scene}"
         outputs = [
             (f"{prefix}_target.wav", target),
-            (f"{prefix}_interferer.wav", interferer),
+            (f"{prefix}_interferer.wav", interferer_signal),
         ]
 
         snr_ref = None
@@ -210,7 +252,7 @@ def render(
 
             # Apply the BRIRs
             target_at_ear = self.apply_brir(target, target_brir)
-            interferer_at_ear = self.apply_brir(interferer, interferer_brir)
+            interferer_at_ear = self.apply_brir(interferer_signal, interferer_brir)
 
             # Scale interferer to obtain SNR specified in scene description
             logging.info("Scaling interferer to obtain mixture SNR = %s dB.", snr_dB)
@@ -256,15 +298,17 @@ def render(
         outputs.append((f"{prefix}_target_anechoic.wav", target_anechoic))
 
         # Write all output files
-        for (filename, signal) in outputs:
-            self.write_signal(filename, signal, self.fs)
+        for filename, signal in outputs:
+            self.write_signal(filename, signal, self.sample_rate)
 
 
-def check_scene_exists(scene, output_path, num_channels):
+def check_scene_exists(scene: dict, output_path: str, num_channels: int) -> bool:
     """Checks correct dataset directory for full set of pre-existing files.
 
     Args:
         scene (dict): dictionary defining the scene to be generated.
+        output_path (str): Path files should be saved to.
+        num_channels (int): Number of channels
 
     Returns:
         status: boolean value indicating whether scene signals exist
diff --git a/clarity/data/utils.py b/clarity/data/utils.py
index 6cd3126e2..15682e29c 100644
--- a/clarity/data/utils.py
+++ b/clarity/data/utils.py
@@ -1,3 +1,4 @@
+"""Utilities for data generation."""
 import os
 import sys
 
@@ -29,6 +30,8 @@ def better_ear_speechweighted_snr(target: np.ndarray, noise: np.ndarray) -> floa
         noise (np.ndarray):
 
     Returns:
+        (float)
+    Maximum Signal Noise Ratio between left and right channel.
     """
     if np.ndim(target) == 1:
         # analysis left ear and right ear for single channel target
@@ -105,7 +108,6 @@ def pad(signal: np.ndarray, length: int) -> np.ndarray:
     Returns:
         np.array:
     """
-    # FIXME : Consider encapsulating in a 'try: ... except: ...' should the assertion not be met.
     try:
         assert length >= signal.shape[0]
     except AssertionError:
diff --git a/clarity/enhancer/gha/gha_interface.py b/clarity/enhancer/gha/gha_interface.py
index 79d96fdca..ab4e9e70f 100644
--- a/clarity/enhancer/gha/gha_interface.py
+++ b/clarity/enhancer/gha/gha_interface.py
@@ -26,7 +26,6 @@ def __init__(
         equiv0dBSPL=100,
         test_nbits=16,
     ):
-
         if audf is None:
             audf = [250, 500, 1000, 2000, 3000, 4000, 6000, 8000]
         if noisegatelevels is None:
diff --git a/clarity/enhancer/nalr.py b/clarity/enhancer/nalr.py
index 956ad73a4..3e76799be 100644
--- a/clarity/enhancer/nalr.py
+++ b/clarity/enhancer/nalr.py
@@ -153,10 +153,11 @@ def build(
             NAL-R FIR filter
             delay
         """
-        if cfs is None:
-            cfs = np.array([250, 500, 1000, 2000, 3000, 6000])
 
-        hl = self.hl_interp(np.array(hl), np.array(cfs))
+        # Apply interpolation only if cfs is not None
+        if cfs is not None:
+            hl = self.hl_interp(np.array(hl), np.array(cfs))
+
         mloss = np.max(hl)
 
         if mloss > 0:
diff --git a/clarity/evaluator/haaqi/haaqi.py b/clarity/evaluator/haaqi/haaqi.py
index 0a375b5d5..5f418576d 100644
--- a/clarity/evaluator/haaqi/haaqi.py
+++ b/clarity/evaluator/haaqi/haaqi.py
@@ -3,42 +3,52 @@
 
 from clarity.evaluator.haspi import eb
 
+# pylint: disable=too-many-arguments
+# pylint: disable=too-many-locals
+
 
 def haaqi_v1(
-    x: np.ndarray,
-    fx: int,
-    y: np.ndarray,
-    fy: int,
-    hl: np.ndarray,
-    eq: int,
+    reference: np.ndarray,
+    reference_freq: int,
+    processed: np.ndarray,
+    processed_freq: int,
+    hearing_loss: np.ndarray,
+    equalisation: int,
     level1: int = 65,
+    silence_threshold: float = 2.5,
+    add_noise: float = 0.0,
+    segment_covariance: int = 16,
 ):
     """
-    Compute the HAAQI music quality index using the
-    auditory model followed by computing the envelope cepstral
-    correlation and BM vibration average short-time coherence signals.
+    Compute the HAAQI music quality index using the auditory model followed by
+    computing the envelope cepstral correlation and Basilar Membrane vibration
+    average short-time coherence signals.
+
     The reference signal presentation level for NH listeners is assumed
     to be 65 dB SPL. The same model is used for both normal and
     impaired hearing.
 
     Arguments:
-        x (ndarray):  Clear input reference speech signal with no noise or distortion.
+    reference (ndarray):  Clear input reference speech signal with no noise or distortion.
             If a hearing loss is specified, NAL-R equalization is optional
-        fx: Sampling rate in Hz for signal x
-        y:  Output signal with noise, distortion, HA gain, and/or processing.
-        fy: Sampling rate in Hz for signal y.
-        hl: (1,6) vector of hearing loss at the 6 audiometric frequencies
+    reference_freq (int): Sampling rate in Hz for reference signal.
+    processed (np.ndarray):  Output signal with noise, distortion, HA gain, and/or processing.
+    processed_freq (int): Sampling rate in Hz for processed signal.
+    hearling_loss (np.ndarray): (1,6) vector of hearing loss at the 6 audiometric frequencies
         [250, 500, 1000, 2000, 4000, 6000] Hz.
-        eq: Flag to provide equalization for the hearing loss to signal x:
+    equalisation (int): Flag to provide equalization for the hearing loss to signal processed:
             1 = no EQ has been provided, the function will add NAL-R
             2 = NAL-R EQ has already been added to the reference signal
-        level1: Optional input specifying level in dB SPL that corresponds to a
+    level1 (int): Optional input specifying level in dB SPL that corresponds to a
            signal RMS = 1. Default is 65 dB SPL if argument not provided.
            Default: 65
+    silence_threshold (float): Silence threshold sum across bands, dB above aud threshold. Default : 2.5
+    add_noise (float): Additive noise in dB SL to condition cross-covariances. Default: 0.0
+    segment_covariance (int): Segment size for the covariance calculation. Default: 16
 
     Returns:
-        combined : Quality is the polynomial sum of the nonlin and linear terms
-        nonlin : Nonlinear quality component = .245(BMsync5) + .755(CepHigh)^3
+        combined : Quality is the polynomial sum of the nonlinear and linear terms
+        nonlinear : Nonlinear quality component = .245(BMsync5) + .755(CepHigh)^3
         linear : Linear quality component = std of spectrum and norm spectrum
         raw : Vector of raw values = [cephigh, bmsync5, dloud, dnorm]
 
@@ -50,73 +60,90 @@ def haaqi_v1(
 
     # Auditory model for quality
     # Reference is no processing or NAL-R, impaired hearing
-    xenv, xbm, yenv, ybm, xsl, ysl, fsamp = eb.EarModel(x, fx, y, fy, hl, eq, level1)
+    (
+        reference_db,
+        reference_basilar_membrane,
+        processed_db,
+        processed_basilar_membrane,
+        reference_sl,
+        processed_sl,
+        freq_sample,
+    ) = eb.ear_model(
+        reference,
+        reference_freq,
+        processed,
+        processed_freq,
+        hearing_loss,
+        equalisation,
+        level1,
+    )
 
     # ---------------------------------------
     # Envelope and long-term average spectral features
     # Smooth the envelope outputs: 250 Hz sub-sampling rate
-    segsize = 8  # Averaging segment size in msec
-    xdb = eb.env_smooth(xenv, segsize, fsamp)
-    ydb = eb.env_smooth(yenv, segsize, fsamp)
+    segment_size = 8  # Averaging segment size in msec
+    reference_smooth = eb.env_smooth(reference_db, segment_size, freq_sample)
+    processed_smooth = eb.env_smooth(processed_db, segment_size, freq_sample)
 
     # Mel cepstrum correlation after passing through modulation filterbank
-    thr = 2.5  # Silence threshold: sum across bands, dB above aud threshold
-    addnoise = 0.0  # dditive noise in dB SL to condition cross-covariances
-    _, _, cephigh, _ = eb.melcor9(
-        xdb, ydb, thr, addnoise, segsize
+    _, _, mel_cepstral_high, _ = eb.melcor9(
+        reference_smooth, processed_smooth, silence_threshold, add_noise, segment_size
     )  # 8 modulation freq bands
 
     # Linear changes in the long-term spectra
     # dloud  vector: [sum abs diff, std dev diff, max diff] spectra
     # dnorm  vector: [sum abs diff, std dev diff, max diff] norm spectra
     # dslope vector: [sum abs diff, std dev diff, max diff] slope
-    dloud_vector, dnorm_vector, _ = eb.spect_diff(xsl, ysl)
+    dloud_vector, dnorm_vector, _ = eb.spectrum_diff(reference_sl, processed_sl)
 
     # Temporal fine structure (TFS) correlation measurements
     # Compute the time-frequency segment covariances
-    segcov = 16  # Segment size for the covariance calculation
-    sigcov, sigmsx, _ = eb.bm_covary(xbm, ybm, segcov, fsamp)
+    signal_cross_covariance, reference_mean_square, _ = eb.bm_covary(
+        reference_basilar_membrane,
+        processed_basilar_membrane,
+        segment_covariance,
+        freq_sample,
+    )
 
     # Average signal segment cross-covariance
     # avecov=weighted ave of cross-covariances, using only data above threshold
     # syncov=ave cross-covariance with added IHC loss of synchronization at HF
-    thr = 2.5  # Threshold in dB SL for including time-freq tile
-    _, syncov = eb.ave_covary2(sigcov, sigmsx, thr)
-    bmsync5 = syncov[4]  # Ave segment coherence with IHC loss of sync
+    _, ihc_sync_covariance = eb.ave_covary2(
+        signal_cross_covariance, reference_mean_square, silence_threshold
+    )
+    basilar_membrane_sync5 = ihc_sync_covariance[
+        4
+    ]  # Ave segment coherence with IHC loss of sync
 
     # Extract and normalize the spectral features
     # Dloud:std
-    d = dloud_vector[1]  # Loudness difference std
-    d = d / 2.5  # Scale the value
-    d = 1.0 - d  # 1=perfect, 0=bad
-    d = min(d, 1)
-    d = max(d, 0)
-    dloud = d
+    d_loud = dloud_vector[1] / 2.5  # Loudness difference std
+    d_loud = 1.0 - d_loud  # 1=perfect, 0=bad
+    d_loud = min(d_loud, 1)
+    d_loud = max(d_loud, 0)
 
     # Dnorm:std
-    d = dnorm_vector[1]  # Slope difference std
-    d = d / 25  # Scale the value
-    d = 1.0 - d  # 1=perfect, 0=bad
-    d = min(d, 1)
-    d = max(d, 0)
-    dnorm = d
+    d_norm = dnorm_vector[1] / 25  # Slope difference std
+    d_norm = 1.0 - d_norm  # 1=perfect, 0=bad
+    d_norm = min(d_norm, 1)
+    d_norm = max(d_norm, 0)
 
     # Construct the models
-    # Nonlinear model
-    nonlin_model = 0.754 * (cephigh**3) + 0.246 * bmsync5  # Combined envelope and TFS
+    # Nonlinear model - Combined envelope and TFS
+    nonlinear_model = 0.754 * (mel_cepstral_high**3) + 0.246 * basilar_membrane_sync5
 
     # Linear model
-    lin_model = 0.329 * dloud + 0.671 * dnorm  # Linear fit
+    linear_model = 0.329 * d_loud + 0.671 * d_norm
 
     # Combined model
-    combined = (
-        0.336 * nonlin_model
-        + 0.001 * lin_model
-        + 0.501 * (nonlin_model**2)
-        + 0.161 * (lin_model**2)
+    combined_model = (
+        0.336 * nonlinear_model
+        + 0.001 * linear_model
+        + 0.501 * (nonlinear_model**2)
+        + 0.161 * (linear_model**2)
     )  # Polynomial sum
 
     # Raw data
-    raw = [cephigh, bmsync5, dloud, dnorm]
+    raw = [mel_cepstral_high, basilar_membrane_sync5, d_loud, d_norm]
 
-    return combined, nonlin_model, lin_model, raw
+    return combined_model, nonlinear_model, linear_model, raw
diff --git a/clarity/evaluator/haspi/__init__.py b/clarity/evaluator/haspi/__init__.py
index 4a9c35fd3..72bee6c90 100644
--- a/clarity/evaluator/haspi/__init__.py
+++ b/clarity/evaluator/haspi/__init__.py
@@ -1,3 +1,4 @@
+"""HASPI intelligibility index."""
 from clarity.evaluator.haspi.haspi import haspi_v2, haspi_v2_be
 
 __all__ = ["haspi_v2", "haspi_v2_be"]
diff --git a/clarity/evaluator/haspi/eb.py b/clarity/evaluator/haspi/eb.py
index c8c39f8b4..2649eb6ff 100644
--- a/clarity/evaluator/haspi/eb.py
+++ b/clarity/evaluator/haspi/eb.py
@@ -1,3 +1,4 @@
+"""Module for ???"""
 import numpy as np
 from numba import jit
 from scipy.signal import (
@@ -13,49 +14,74 @@
 
 from clarity.enhancer.nalr import NALR
 
-
-def EarModel(x, xsamp, y, ysamp, HL, itype, Level1):
+# pylint: disable=line-too-long
+# pylint: disable=too-many-arguments
+# pylint: disable=too-many-branches
+# pylint: disable=too-many-lines
+# pylint: disable=too-many-locals
+# pylint: disable=too-many-statements
+
+
+def ear_model(
+    reference,
+    reference_freq,
+    processed,
+    processed_freq,
+    hearing_loss,
+    itype,
+    level1,
+    nchan=32,
+    m_delay=1,
+    shift=0.02,
+):
     """
-    Function to implement a cochlear model that includes the middle ear,
-    auditory filter bank, OHC dynamic-range compression, and IHC attenuation.
+    Function that implements a cochlear model that includes the middle ear,
+    auditory filter bank, Outer Hair Cell (OHC) dynamic-range compression,
+    and Inner Hair Cell (IHC) attenuation.
+
     The inputs are the reference and processed signals that are to be
-    compared. The reference x is at the reference intensity (e.g. 65 dB SPL
+    compared. The reference is at the reference intensity (e.g. 65 dB SPL
     or with NAL-R amplification) and has no other processing. The processed
-    signal y is the hearing-aid output, and is assumed to have the same or
-    greater group delay compared to the reference. The function outputs are
-    the envelopes of the signals after OHC compression and IHC loss
-    attenuation.
+    signal is the hearing-aid output, and is assumed to have the same or
+    greater group delay compared to the reference.
+
+    The function outputs the envelopes of the signals after OHC compression
+    and IHC loss attenuation.
 
-    Args:
-    x        reference signal: should be adjusted to 65 dB SPL (itype=0 or 1)
+    Arguments:
+    reference (np.ndarray): reference signal: should be adjusted to 65 dB SPL (itype=0 or 1)
                or to 65 dB SPL plus NAL-R gain (itype=2)
-    xsamp    sampling rate for the reference signal, Hz
-    y        processed signal (e.g. hearing-aid output) includes HA gain
-    ysamp    sampling rate for the processed signal, Hz
-    HL       audiogram giving the hearing loss in dB at six audiometric
+    reference_freq (int): sampling rate for the reference signal, Hz
+    processed (np.ndarray): processed signal (e.g. hearing-aid output) includes HA gain
+    processed_freq (int): sampling rate for the processed signal, Hz
+    hearing_loss (np.ndarray): audiogram giving the hearing loss in dB at six audiometric
                frequencies: [250, 500, 1000, 2000, 4000, 6000] Hz
-    itype    purpose for the calculation:
+    itype (int): purpose for the calculation:
              0=intelligibility: reference is nornal hearing and must not
                include NAL-R EQ
              1=quality: reference does not include NAL-R EQ
              2=quality: reference already has NAL-R EQ applied
-    Level1   level calibration: signal RMS=1 corresponds to Level1 dB SPL
+    level1   level calibration: signal RMS=1 corresponds to Level1 dB SPL
+    nchan (int): auditory frequency bands
+    m_delay (int): Compensate for the gammatone group delay.
+    shift (float): Basal shift of the basilar membrane length
 
     Returns:
-    xdB      envelope for the reference in each band
-    xBM      BM motion for the reference in each band
-    ydB      envelope for the processed signal in each band
-    yBM      BM motion for the processed signal in each band
-    xSL      compressed RMS average reference in each band converted to dB SL
-    ySL      compressed RMS average output in each band converted to dB SL
-    fsamp    sampling rate in Hz for the model outputs
-
+    reference_db (): envelope for the reference in each band
+    reference_basilar_membrane (): BM motion for the reference in each band
+    processed_db (): envelope for the processed signal in each band
+    processed_basilar_membrane (): BM motion for the processed signal in each band
+    reference_sl (): compressed RMS average reference in each band converted to dB SL
+    processed_sl (): compressed RMS average output in each band converted to dB SL
+    freq_sample (): sampling rate in Hz for the model outputs
+
+    Updates:
     James M. Kates, 27 October 2011.
-    BM motion added 30 Dec 2011.
+    Basilar Membrane added 30 Dec 2011.
     Revised 19 June 2012.
     Remove match of reference RMS level to processed 29 August 2012.
     IHC adaptation added 1 October 2012.
-    BM envelope coverted to dB SL, 2 Oct 2012.
+    Basilar Membrane envelope converted to dB SL, 2 Oct 2012.
     Filterbank group delay corrected, 14 Dec 2012.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     Updated by Gerardo Roa Dabike, September 2022.
@@ -64,211 +90,326 @@ def EarModel(x, xsamp, y, ysamp, HL, itype, Level1):
     # Processing parameters
     # OHC and IHC parameters for the hearing loss
     # Auditory filter center frequencies span 80 to 8000 Hz.
-    nchan = 32  # Use 32 auditory frequency bands
-    mdelay = 1  # Compensate for the gammatone group delay
-    cfreq = CenterFreq(nchan)  # Center frequencies on an ERB scale
+    _center_freq = center_frequency(nchan)  # Center frequencies on an ERB scale
 
     # Cochlear model parameters for the processed signal
-    attnOHCy, BWminy, lowkneey, CRy, attnIHCy = LossParameters(HL, cfreq)
+    (
+        attn_ohc_y,
+        bandwidth_min_y,
+        low_knee_y,
+        compression_ratio_y,
+        attn_ihc_y,
+    ) = loss_parameters(hearing_loss, _center_freq)
 
     # The cochlear model parameters for the reference are the same as for the hearing
     # loss if calculating quality, but are for normal hearing if calculating intelligibility.
     if itype == 0:
-        HLx = [0] * len(HL)
+        hearing_loss_x = [0] * len(hearing_loss)
     else:
-        HLx = HL
-    [attnOHCx, BWminx, lowkneex, CRx, attnIHCx] = LossParameters(HLx, cfreq)
+        hearing_loss_x = hearing_loss
+    [
+        attn_ohc_x,
+        bandwidth_min_x,
+        low_knee_x,
+        compression_ratio_x,
+        attn_ihc_x,
+    ] = loss_parameters(hearing_loss_x, _center_freq)
 
     # Parameters for the control filter bank
-    HLmax = [100, 100, 100, 100, 100, 100]
-    shift = 0.02  # Basal shift of 0.02 of the basilar membrane length
-    cfreq1 = CenterFreq(nchan, shift)  # Center frequencies for the control
-    _, BW1, _, _, _ = LossParameters(HLmax, cfreq1)
+    hl_max = [100, 100, 100, 100, 100, 100]
+    _center_freq_control = center_frequency(
+        nchan, shift
+    )  # Center frequencies for the control
+    _, bandwidth_1, _, _, _ = loss_parameters(hl_max, _center_freq_control)
     # Maximum BW for the control
 
     # Input signal adjustments
     # Convert the signals to 24 kHz sampling rate.
     # Using 24 kHz guarantees that all of the cochlear filters have the same shape
     # independent of the incoming signal sampling rates
-    x24, _ = Resamp24kHz(x, xsamp)
-    y24, fsamp = Resamp24kHz(y, ysamp)
+    reference_24hz, _ = resample_24khz(reference, reference_freq)
+    processed_24hz, freq_sample = resample_24khz(processed, processed_freq)
 
     # Check file sizes
-    nxy = min(len(x24), len(y24))
-    x24 = x24[:nxy]
-    y24 = y24[:nxy]
+    min_signal_length = min(len(reference_24hz), len(processed_24hz))
+    reference_24hz = reference_24hz[:min_signal_length]
+    processed_24hz = processed_24hz[:min_signal_length]
 
     # Bulk broadband signal alignment
-    x24, y24 = InputAlign(x24, y24)
-    nsamp = len(x24)
+    reference_24hz, processed_24hz = input_align(reference_24hz, processed_24hz)
+    nsamp = len(reference_24hz)
 
     # For HASQI, here add NAL-R equalization if the quality reference doesn't already have it.
     if itype == 1:
         nfir = 140  # Length in samples of the FIR NAL-R EQ filter (24-kHz rate)
-        enhancer = NALR(nfir, fsamp)
+        enhancer = NALR(nfir, freq_sample)
         aud = [250, 500, 1000, 2000, 4000, 6000]
-        nalr_fir, _ = enhancer.build(HL, aud)
-        x24 = convolve(x24, nalr_fir)  # Apply the NAL-R filter
-        x24 = x24[nfir : nfir + nsamp]
+        nalr_fir, _ = enhancer.build(hearing_loss, aud)
+        reference_24hz = convolve(reference_24hz, nalr_fir)  # Apply the NAL-R filter
+        reference_24hz = reference_24hz[nfir : nfir + nsamp]
 
     # Cochlear model
     # Middle ear
-    xmid = MiddleEar(x24, fsamp)
-    ymid = MiddleEar(y24, fsamp)
+    reference_mid = middle_ear(reference_24hz, freq_sample)
+    processed_mid = middle_ear(processed_24hz, freq_sample)
 
     # Initialize storage
     # Reference and processed envelopes and BM motion
-    xdB = np.zeros((nchan, nsamp))
-    ydB = np.zeros((nchan, nsamp))
+    reference_db = np.zeros((nchan, nsamp))
+    processed_db = np.zeros((nchan, nsamp))
 
     # Reference and processed average spectral values
-    xave = np.zeros(nchan)
-    yave = np.zeros(nchan)  # Processed
-    xcave = np.zeros(nchan)  # Reference control
-    ycave = np.zeros(nchan)  # Processed control
+    reference_average = np.zeros(nchan)
+    processed_average = np.zeros(nchan)
+    reference_control_average = np.zeros(nchan)
+    processed_control_average = np.zeros(nchan)
 
     # Filter bandwidths adjusted for intensity
-    BWx = np.zeros(nchan)
-    BWy = np.zeros(nchan)
+    reference_bandwidth = np.zeros(nchan)
+    processed_bandwidth = np.zeros(nchan)
 
-    xb = np.zeros((nchan, nsamp))
-    yb = np.zeros((nchan, nsamp))
+    reference_b = np.zeros((nchan, nsamp))
+    processed_b = np.zeros((nchan, nsamp))
 
     # Loop over each filter in the auditory filter bank
     for n in range(nchan):
         # Control signal envelopes for the reference and processed signals
-        xcontrol, _, ycontrol, _ = GammatoneBM(
-            xmid, BW1[n], ymid, BW1[n], fsamp, cfreq1[n]
+        reference_control, _, processed_control, _ = gammatone_basilar_membrane(
+            reference_mid,
+            bandwidth_1[n],
+            processed_mid,
+            bandwidth_1[n],
+            freq_sample,
+            _center_freq_control[n],
         )
 
         # Adjust the auditory filter bandwidths for the average signal level
-        BWx[n] = BWadjust(xcontrol, BWminx[n], BW1[n], Level1)  # Reference
-        BWy[n] = BWadjust(ycontrol, BWminy[n], BW1[n], Level1)  # Processed
+        reference_bandwidth[n] = bandwidth_adjust(
+            reference_control, bandwidth_min_x[n], bandwidth_1[n], level1
+        )
+        processed_bandwidth[n] = bandwidth_adjust(
+            processed_control, bandwidth_min_y[n], bandwidth_1[n], level1
+        )
 
         # Envelopes and BM motion of the reference and processed signals
-        xenv, xbm, yenv, ybm = GammatoneBM(xmid, BWx[n], ymid, BWy[n], fsamp, cfreq[n])
+        xenv, xbm, yenv, ybm = gammatone_basilar_membrane(
+            reference_mid,
+            reference_bandwidth[n],
+            processed_mid,
+            processed_bandwidth[n],
+            freq_sample,
+            _center_freq[n],
+        )
 
         # RMS levels of the ref and output envelopes for linear metric
-        xave[n] = np.sqrt(np.mean(xenv**2))
-        yave[n] = np.sqrt(np.mean(yenv**2))
-        xcave[n] = np.sqrt(np.mean(xcontrol**2))
-        ycave[n] = np.sqrt(np.mean(ycontrol**2))
+        reference_average[n] = np.sqrt(np.mean(xenv**2))
+        processed_average[n] = np.sqrt(np.mean(yenv**2))
+        reference_control_average[n] = np.sqrt(np.mean(reference_control**2))
+        processed_control_average[n] = np.sqrt(np.mean(processed_control**2))
 
         # Cochlear compression for the signal envelopes and BM motion
-        xc, xb[n] = EnvCompressBM(
-            xenv, xbm, xcontrol, attnOHCx[n], lowkneex[n], CRx[n], fsamp, Level1
+        reference_cochlear_compression, reference_b[n] = env_compress_basilar_membrane(
+            xenv,
+            xbm,
+            reference_control,
+            attn_ohc_x[n],
+            low_knee_x[n],
+            compression_ratio_x[n],
+            freq_sample,
+            level1,
         )
-        yc, yb[n] = EnvCompressBM(
-            yenv, ybm, ycontrol, attnOHCy[n], lowkneey[n], CRy[n], fsamp, Level1
+        processed_cochlear_compression, processed_b[n] = env_compress_basilar_membrane(
+            yenv,
+            ybm,
+            processed_control,
+            attn_ohc_y[n],
+            low_knee_y[n],
+            compression_ratio_y[n],
+            freq_sample,
+            level1,
         )
 
         # Correct for the delay between the reference and output
-        yc = EnvAlign(xc, yc)  # Align processed envelope to reference
-        yb[n] = EnvAlign(xb[n], yb[n])  # Align processed BM motion to reference
+        processed_cochlear_compression = envelope_align(
+            reference_cochlear_compression, processed_cochlear_compression
+        )  # Align processed envelope to reference
+        processed_b[n] = envelope_align(
+            reference_b[n], processed_b[n]
+        )  # Align processed BM motion to reference
 
         # Convert the compressed envelopes and BM vibration envelopes to dB SPL
-        xc, xb[n] = EnvSL(xc, xb[n], attnIHCx[n], Level1)
-        yc, yb[n] = EnvSL(yc, yb[n], attnIHCy[n], Level1)
+        reference_cochlear_compression, reference_b[n] = envelope_sl(
+            reference_cochlear_compression, reference_b[n], attn_ihc_x[n], level1
+        )
+        processed_cochlear_compression, processed_b[n] = envelope_sl(
+            processed_cochlear_compression, processed_b[n], attn_ihc_y[n], level1
+        )
 
         # Apply the IHC rapid and short-term adaptation
         delta = 2  # Amount of overshoot
-        xdB[n], xb[n] = IHCadapt(xc, xb[n], delta, fsamp)
-        ydB[n], yb[n] = IHCadapt(yc, yb[n], delta, fsamp)
+        reference_db[n], reference_b[n] = inner_hair_cell_adaptation(
+            reference_cochlear_compression, reference_b[n], delta, freq_sample
+        )
+        processed_db[n], processed_b[n] = inner_hair_cell_adaptation(
+            processed_cochlear_compression, processed_b[n], delta, freq_sample
+        )
 
     # Additive noise level to give the auditory threshold
-    IHCthr = -10  # Additive noise level, dB re: auditory threshold
-    xBM = BMaddnoise(xb, IHCthr, Level1)
-    yBM = BMaddnoise(yb, IHCthr, Level1)
+    ihc_threshold = -10  # Additive noise level, dB re: auditory threshold
+    reference_basilar_membrane = basilar_membrane_add_noise(
+        reference_b, ihc_threshold, level1
+    )
+    processed_basilar_membrane = basilar_membrane_add_noise(
+        processed_b, ihc_threshold, level1
+    )
 
     # Correct for the gammatone filterbank interchannel group delay.
-    if mdelay > 0:
-        xdB = GroupDelayComp(xdB, BWx, cfreq, fsamp)
-        ydB = GroupDelayComp(ydB, BWx, cfreq, fsamp)
-        xBM = GroupDelayComp(xBM, BWx, cfreq, fsamp)
-        yBM = GroupDelayComp(yBM, BWx, cfreq, fsamp)
+    if m_delay > 0:
+        reference_db = group_delay_compensate(
+            reference_db, reference_bandwidth, _center_freq, freq_sample
+        )
+        processed_db = group_delay_compensate(
+            processed_db, reference_bandwidth, _center_freq, freq_sample
+        )
+        reference_basilar_membrane = group_delay_compensate(
+            reference_basilar_membrane, reference_bandwidth, _center_freq, freq_sample
+        )
+        processed_basilar_membrane = group_delay_compensate(
+            processed_basilar_membrane, reference_bandwidth, _center_freq, freq_sample
+        )
 
     # Convert average gammatone outputs to dB SPL
-    xSL = aveSL(xave, xcave, attnOHCx, lowkneex, CRx, attnIHCx, Level1)
-    ySL = aveSL(yave, ycave, attnOHCy, lowkneey, CRy, attnIHCy, Level1)
+    reference_sl = convert_rms_to_sl(
+        reference_average,
+        reference_control_average,
+        attn_ohc_x,
+        low_knee_x,
+        compression_ratio_x,
+        attn_ihc_x,
+        level1,
+    )
+    processed_sl = convert_rms_to_sl(
+        processed_average,
+        processed_control_average,
+        attn_ohc_y,
+        low_knee_y,
+        compression_ratio_y,
+        attn_ihc_y,
+        level1,
+    )
 
-    return xdB, xBM, ydB, yBM, xSL, ySL, fsamp
+    return (
+        reference_db,
+        reference_basilar_membrane,
+        processed_db,
+        processed_basilar_membrane,
+        reference_sl,
+        processed_sl,
+        freq_sample,
+    )
 
 
-def CenterFreq(nchan, shift=None):
+def center_frequency(
+    nchan,
+    shift=None,
+    low_freq=80,
+    high_freq=8000,
+    ear_q=9.26449,
+    min_bw=24.7,
+):
     """
-    Function to compute the ERB frequency spacing for the gammatone
-    filter bank. The equation comes from Malcolm Slaney (1993).
+    Compute the Equivalent Rectangular Bandwidth_[1] frequency spacing for the gammatone filter bank. The equation
+    comes from Malcolm Slaney[2]_ .
 
-    Calling variables
-    nchan		number of filters in the filter bank
-    shift     optional frequency shift of the filter bank specified as a
+    Arguments:
+    nchan (int): number of filters in the filter bank
+    low_freq (int): Low Frequency level.
+    high_freq (int): High Frequency level.
+    shift (): optional frequency shift of the filter bank specified as a
               fractional shift in distance along the BM. A positive shift
               is an increase in frequency (basal shift), and negative is
               a decrease in frequency (apical shift). The total length of
               the BM is normalized to 1. The frequency-to-distance map is
-              from D.D. Greenwood (1990), JASA 87, 2592-2605, Eq (1).
+              from D.D. Greenwood[3]_.
+    ear_q (float):
+    min_bw (float):
 
+    Returns:
+
+
+    References:
+    .. [1] Moore BCJ, Glasberg BR (1983) Suggested formulae for calculating
+           auditory-filter bandwidths and excitation patterns. J Acoustical
+           Soc America 74:750-753. Available at
+           <https://doi.org/10.1121/1.389861>
+    .. [2] Slaney M (1993) An Efficient Implemtnation of the Patterson-
+           Holdsworth Auditory Filter Bank. Available at:
+           <https://asset-pdf.scinapse.io/prod/396690109/396690109.pdf>.
+    .. [3] Greenwood DD (1990) A cochlear frequency-position function for
+           several species--29 years later. J Acoust Soc Am 87(6):2592-
+           2605. Available at
+           <https://doi.o10.1121/1.399052>
+
+    Updates:
     James M. Kates, 25 January 2007.
     Frequency shift added 22 August 2008.
     Lower and upper frequencies fixed at 80 and 8000 Hz, 19 June 2012.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
-    lowFreq = 80
-    highFreq = 8000
-
-    # Moore and Glasberg ERB values
-    EarQ = 9.26449
-    minBW = 24.7
 
     # In the Matlab code, the loop below never evaluates
     # (but the current code was trained with this bug)
     shift = None  # This is to keep consistency with MATLAB code
     if shift is not None:
         k = 1
-        A = 165.4
+        A = 165.4  # pylint: disable=invalid-name
         a = 2.1  # shift specified as a fraction of the total length
         # Locations of the low and high frequencies on the BM between 0 and 1
-        xLow = (1 / a) * np.log10(k + (lowFreq / A))
-        xHigh = (1 / a) * np.log10(k + (highFreq / A))
+        x_low = (1 / a) * np.log10(k + (low_freq / A))
+        x_high = (1 / a) * np.log10(k + (high_freq / A))
         # Shift the locations
-        xLow = xLow * (1 + shift)
-        xHigh = xHigh * (1 + shift)
+        x_low = x_low * (1 + shift)
+        x_high = x_high * (1 + shift)
         # Compute the new frequency range
-        lowFreq = A * (10 ** (a * xLow) - k)
-        highFreq = A * (10 ** (a * xHigh) - k)
+        low_freq = A * (10 ** (a * x_low) - k)
+        high_freq = A * (10 ** (a * x_high) - k)
 
     # All of the following expressions are derived in Apple TR #35,
     # "An Efficient Implementation of the Patterson-Holdsworth Cochlear
-    # Filter Bank" by Malcolm Slaney.
-    cf = -(EarQ * minBW) + np.exp(
+    # Filter Bank" by Malcolm Slaney. https://engineering.purdue.edu/~malcolm/apple/tr35/PattersonsEar.pdf
+    _center_freq = -(ear_q * min_bw) + np.exp(
         np.arange(1, nchan)
-        * (-np.log(highFreq + EarQ * minBW) + np.log(lowFreq + EarQ * minBW))
+        * (-np.log(high_freq + ear_q * min_bw) + np.log(low_freq + ear_q * min_bw))
         / (nchan - 1)
-    ) * (highFreq + EarQ * minBW)
-    cf = np.insert(cf, 0, highFreq)  # Last center frequency is set to highFreq
-    cf = np.flip(cf)
-    return cf
+    ) * (high_freq + ear_q * min_bw)
+    _center_freq = np.insert(
+        _center_freq, 0, high_freq
+    )  # Last center frequency is set to highFreq
+    _center_freq = np.flip(_center_freq)
+    return _center_freq
 
 
-def LossParameters(HL, cfreq):
+def loss_parameters(hearing_loss, center_freq, audiometric_freq=None):
     """
-    Function to apportion the hearing loss to the outer hair cells (OHC)
-    and the inner hair cells (IHC) and to increase the bandwidth of the
-    cochlear filters in proportion to the OHC fraction of the total loss.
+    Apportion the hearing loss to the outer hair cells (OHC) and the inner
+    hair cells (IHC) and to increase the bandwidth of the cochlear filters
+    in proportion to the OHC fraction of the total loss.
 
-    Calling variables:
-    HL		hearing loss at the 6 audiometric frequencies
-    cfreq		array containing the center frequencies of the gammatone filters
-                arranged from low to high
+    Arguments:
+    hearing_loss (np.ndarray): hearing loss at the 6 audiometric frequencies
+    center_freq (np.ndarray): array containing the center frequencies of the
+        gammatone filters arranged from low to high
+    audiometric_freq (list):
 
     Returns:
-    attnOHC	attenuation in dB for the OHC gammatone filters
-    BW		OHC filter bandwidth expressed in terms of normal
-    lowknee	Lower kneepoint for the low-level linear amplification
-    CR		Ranges from 1.4:1 at 150 Hz to 3.5:1 at 8 kHz for normal
-                hearing. Reduced in proportion to the OHC loss to 1:1.
-    attnIHC	attenuation in dB for the input to the IHC synapse
-
+    attenuated_ohc (): attenuation in dB for the OHC gammatone filters
+    bandwidth (): OHC filter bandwidth expressed in terms of normal
+    low_knee (): Lower kneepoint for the low-level linear amplification
+    compression_ratio (): Ranges from 1.4:1 at 150 Hz to 3.5:1 at 8 kHz for normal
+        hearing. Reduced in proportion to the OHC loss to 1:1.
+    attenuated_ihc ():	attenuation in dB for the input to the IHC synapse
+
+    Updates:
     James M. Kates, 25 January 2007.
     Version for loss in dB and match of OHC loss to CR, 9 March 2007.
     Low-frequency extent changed to 80 Hz, 27 Oct 2011.
@@ -276,133 +417,157 @@ def LossParameters(HL, cfreq):
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Audiometric frequencies in Hz
-    aud = [250, 500, 1000, 2000, 4000, 6000]
+    if audiometric_freq is None:
+        audiometric_freq = [250, 500, 1000, 2000, 4000, 6000]
 
     # Interpolation to give the loss at the gammatone center frequencies
     # Use linear interpolation in dB. The interpolation assumes that
     # cfreq[1] < aud[1] and cfreq[nfilt] > aud[6]
-    nfilt = len(cfreq)
-    fv = np.insert(aud, [0, len(aud)], [cfreq[0], cfreq[-1]])
+    nfilt = len(center_freq)
+    f_v = np.insert(
+        audiometric_freq, [0, len(audiometric_freq)], [center_freq[0], center_freq[-1]]
+    )
 
     # Interpolated gain in dB
-    loss = np.interp(cfreq, fv, np.insert(HL, [0, len(HL)], [HL[0], HL[-1]]))
+    loss = np.interp(
+        center_freq,
+        f_v,
+        np.insert(
+            hearing_loss, [0, len(hearing_loss)], [hearing_loss[0], hearing_loss[-1]]
+        ),
+    )
     loss = np.maximum(loss, 0)
     # Make sure there are no negative losses
 
     # Compression ratio changes linearly with ERB rate from 1.25:1 in the 80-Hz
     # frequency band to 3.5:1 in the 8-kHz frequency band
-    CR = 1.25 + 2.25 * np.arange(nfilt) / (nfilt - 1)
+    compression_ratio = 1.25 + 2.25 * np.arange(nfilt) / (nfilt - 1)
 
     # Maximum OHC sensitivity loss depends on the compression ratio.
     # The compression I/O curves assume linear below 30 and above 100 dB SPL in normal ears.
-    maxOHC = 70 * (1 - (1 / CR))  # HC loss that results in 1:1 compression
-    thrOHC = 1.25 * maxOHC  # Loss threshold for adjusting the OHC parameters
+    max_ohc = 70 * (
+        1 - (1 / compression_ratio)
+    )  # HC loss that results in 1:1 compression
+    theoretical_ohc = 1.25 * max_ohc  # Loss threshold for adjusting the OHC parameters
 
     # Apportion the loss in dB to the outer and inner hair cells based on the data of
     # Moore et al (1999), JASA 106, 2761-2778.
 
     # Reduce the CR towards 1:1 in proportion to the OHC loss.
-    attnOHC = 0.8 * np.copy(loss)
-    attnIHC = 0.2 * np.copy(loss)
+    attenuated_ohc = 0.8 * np.copy(loss)
+    attnenuated_ihc = 0.2 * np.copy(loss)
 
-    attnOHC[loss >= thrOHC] = 0.8 * thrOHC[loss >= thrOHC]
-    attnIHC[loss >= thrOHC] = 0.2 * thrOHC[loss >= thrOHC] + (
-        loss[loss >= thrOHC] - thrOHC[loss >= thrOHC]
+    attenuated_ohc[loss >= theoretical_ohc] = (
+        0.8 * theoretical_ohc[loss >= theoretical_ohc]
     )
+    attnenuated_ihc[loss >= theoretical_ohc] = 0.2 * theoretical_ohc[
+        loss >= theoretical_ohc
+    ] + (loss[loss >= theoretical_ohc] - theoretical_ohc[loss >= theoretical_ohc])
 
     # Adjust the OHC bandwidth in proportion to the OHC loss
-    BW = np.ones(nfilt)
-    BW = BW + (attnOHC / 50.0) + 2.0 * (attnOHC / 50.0) ** 6
+    bandwidth = np.ones(nfilt)
+    bandwidth = bandwidth + (attenuated_ohc / 50.0) + 2.0 * (attenuated_ohc / 50.0) ** 6
 
     # Compute the compression lower kneepoint and compression ratio
-    lowknee = attnOHC + 30
-    upamp = 30 + 70 / CR  # Output level for an input of 100 dB SPL
+    low_knee = attenuated_ohc + 30
+    upamp = 30 + (70 / compression_ratio)  # Output level for an input of 100 dB SPL
 
-    CR = (100 - lowknee) / (upamp + attnOHC - lowknee)  # OHC loss Compression ratio
+    compression_ratio = (100 - low_knee) / (
+        upamp + attenuated_ohc - low_knee
+    )  # OHC loss Compression ratio
 
-    return attnOHC, BW, lowknee, CR, attnIHC
+    return attenuated_ohc, bandwidth, low_knee, compression_ratio, attnenuated_ihc
 
 
-def Resamp24kHz(x, fsampx):
+def resample_24khz(reference_signal, reference_freq, freq_sample_hz=24000):
     """
-    Function to resample the input signal at 24 kHz. The input sampling rate
-    is rounded to the nearest kHz to comput the sampling rate conversion
+    Resample the input signal at 24 kHz. The input sampling rate is
+    rounded to the nearest kHz to compute the sampling rate conversion
     ratio.
 
-    Calling variables:
-    x         input signal
-    fsampx    sampling rate for the input in Hz
+    Arguments:
+    reference_signal (np.ndarray): input signal
+    reference_freq (int): sampling rate for the input in Hz
+    freq_sample_hz (int): Frequency sample in Hz
 
-    Returned argument:
-    y         signal resampled at 24 kHz
-    fsamp     output sampling rate in Kz
+    Returns:
+    reference_signal_24         signal resampled at kHz (default 24Khz)
+    freq_sample_hz     output sampling rate in Hz
 
+    Updates
     James M. Kates, 20 June 2011.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
 
     # Sampling rate information
-    fsamp = 24000
-    fy = round(fsamp / 1000)  # output rate to nearest kHz
-    fx = round(fsampx / 1000)
+    sample_rate_target_khz = round(freq_sample_hz / 1000)  # output rate to nearest kHz
+    reference_freq_khz = round(reference_freq / 1000)
 
     # Resample the signal
-    if fx == fy:
+    if reference_freq_khz == sample_rate_target_khz:
         # No resampling performed if the rates match
-        y = x
-    elif fx < fy:
+        return reference_signal, freq_sample_hz
+
+    if reference_freq_khz < sample_rate_target_khz:
         # Resample for the input rate lower than the output
-        y = resample_poly(x, fy, fx)
+        resample_signal = resample_poly(
+            reference_signal, sample_rate_target_khz, reference_freq_khz
+        )
 
         # Match the RMS level of the resampled signal to that of the input
-        xRMS = np.sqrt(np.mean(x**2))
-        yRMS = np.sqrt(np.mean(y**2))
-        y = (xRMS / yRMS) * y
+        reference_rms = np.sqrt(np.mean(reference_signal**2))
+        resample_rms = np.sqrt(np.mean(resample_signal**2))
+        resample_signal = (reference_rms / resample_rms) * resample_signal
 
-    else:
-        # Resample for the input rate higher than the output
-        y = resample_poly(x, fy, fx)
-
-        # Reduce the input signal bandwidth to 21 kHz (-10.5 to +10.5 kHz)
-        # The power equalization is designed to match the signal intensities
-        # over the frequency range spanned by the gammatone filter bank.
-        # Chebyshev Type 2 LP
-        order = 7
-        atten = 30  # sidelobe attenuation in dB
-        fcutx = 21 / fx
-        bx, ax = cheby2(order, atten, fcutx)
-        xfilt = lfilter(bx, ax, x, axis=0)
-
-        # Reduce the resampled signal bandwisth to 21 kHz (-10.5 to +10.5 kHz)
-        fcuty = 21 / fy
-        by, ay = cheby2(order, atten, fcuty)
-        yfilt = lfilter(by, ay, y, axis=0)
-
-        # Compute the input and output RMS levels within the 21 kHz bandwidth and
-        # match the output to the input
-        xRMS = np.sqrt(np.mean(xfilt**2))
-        yRMS = np.sqrt(np.mean(yfilt**2))
-        y = (xRMS / yRMS) * y
-
-    return y, fsamp
-
-
-def InputAlign(x, y):
+        return resample_signal, freq_sample_hz
+
+    # Resample for the input rate higher than the output
+    resample_signal = resample_poly(
+        reference_signal, sample_rate_target_khz, reference_freq_khz
+    )
+
+    # Reduce the input signal bandwidth to 21 kHz (-10.5 to +10.5 kHz)
+    # The power equalization is designed to match the signal intensities
+    # over the frequency range spanned by the gammatone filter bank.
+    # Chebyshev Type 2 LP
+    order = 7
+    attenuation = 30  # sidelobe attenuation in dB
+    reference_freq_cut = 21 / reference_freq_khz
+    reference_b, reference_a = cheby2(order, attenuation, reference_freq_cut)
+    reference_filter = lfilter(reference_b, reference_a, reference_signal, axis=0)
+
+    # Reduce the resampled signal bandwisth to 21 kHz (-10.5 to +10.5 kHz)
+    resample_freq_cut = 21 / sample_rate_target_khz
+    target_b, target_a = cheby2(order, attenuation, resample_freq_cut)
+    target_filter = lfilter(target_b, target_a, resample_signal, axis=0)
+
+    # Compute the input and output RMS levels within the 21 kHz bandwidth and
+    # match the output to the input
+    reference_rms = np.sqrt(np.mean(reference_filter**2))
+    resample_rms = np.sqrt(np.mean(target_filter**2))
+    resample_signal = (reference_rms / resample_rms) * resample_signal
+
+    return resample_signal, freq_sample_hz
+
+
+def input_align(reference, processed):
     """
-    Function to provide approximate temporal alignment of the reference and
-    processed output signals. Leading and trailing zeros are then pruned.
+    Approximate temporal alignment of the reference and processed output
+    signals. Leading and trailing zeros are then pruned.
+
     The function assumes that the two sequences have the same sampling rate:
     call eb_Resamp24kHz for each sequence first, then call this function to
     align the signals.
 
-    Calling variables:
-    x       input reference sequence
-    y       hearing-aid output sequence
+    Arguments:
+    reference (np.ndarray): input reference sequence
+    processed (np.ndarray): hearing-aid output sequence
 
     Returns:
-    xp   pruned and shifted reference
-    yp   pruned and shifted hearing-aid output
+    reference (np.ndarray): pruned and shifted reference
+    processed (np.ndarray): pruned and shifted hearing-aid output
 
+    Updates:
     James M. Kates, 12 July 2011.
     Match the length of the processed output to the reference for the
     purposes of computing the cross-covariance
@@ -411,16 +576,18 @@ def InputAlign(x, y):
 
     # Match the length of the processed output to the reference for the purposes
     # of computing the cross-covariance
-    nx = len(x)
-    ny = len(y)
-    nsamp = min(nx, ny)
+    reference_n = len(reference)
+    processed_n = len(processed)
+    min_sample_length = min(reference_n, processed_n)
 
     # Determine the delay of the output relative to the reference
-    xy = correlate(
-        x[:nsamp] - np.mean(x[:nsamp]), y[:nsamp] - np.mean(y[:nsamp]), "full"
+    reference_processed_correlation = correlate(
+        reference[:min_sample_length] - np.mean(reference[:min_sample_length]),
+        processed[:min_sample_length] - np.mean(processed[:min_sample_length]),
+        "full",
     )  # Matlab code uses xcov thus the subtraction of mean
-    index = np.argmax(np.abs(xy))
-    delay = nsamp - index - 1
+    index = np.argmax(np.abs(reference_processed_correlation))
+    delay = min_sample_length - index - 1
 
     # Back up 2 msec to allow for dispersion
     fsamp = 24000  # Cochlear model input sampling rate in Hz
@@ -429,67 +596,76 @@ def InputAlign(x, y):
     # Align the output with the reference allowing for the dispersion
     if delay > 0:
         # Output delayed relative to the reference
-        y = np.concatenate((y[delay:ny], np.zeros(delay)))
+        processed = np.concatenate((processed[delay:processed_n], np.zeros(delay)))
     else:
         # Output advanced relative to the reference
-        y = np.concatenate((np.zeros(-delay), y[: ny + delay]))
+        processed = np.concatenate((np.zeros(-delay), processed[: processed_n + delay]))
 
     # Find the start and end of the noiseless reference sequence
-    xabs = np.abs(x)
-    xmax = np.max(xabs)
-    xthr = 0.001 * xmax  # Zero detection threshold
+    reference_abs = np.abs(reference)
+    reference_max = np.max(reference_abs)
+    reference_threshold = 0.001 * reference_max  # Zero detection threshold
 
-    above_threshold = np.where(xabs > xthr)[0]
-    nx0 = above_threshold[0]
-    nx1 = above_threshold[-1]
+    above_threshold = np.where(reference_abs > reference_threshold)[0]
+    reference_n_above_threshold = above_threshold[0]
+    reference_n_below_threshold = above_threshold[-1]
 
     # Prune the sequences to remove the leading and trailing zeros
-    nx1 = min(nx1, ny)
-    xp = x[nx0 : nx1 + 1]
-    yp = y[nx0 : nx1 + 1]
+    reference_n_below_threshold = min(reference_n_below_threshold, processed_n)
 
-    return xp, yp
+    return (
+        reference[reference_n_above_threshold : reference_n_below_threshold + 1],
+        processed[reference_n_above_threshold : reference_n_below_threshold + 1],
+    )
 
 
-def MiddleEar(x, fsamp):
+def middle_ear(reference, freq_sample):
     """
-    Function to design the middle ear filters and process the input
-    through the cascade of filters. The middle ear model is a 2-pole HP
-    filter at 350 Hz in series with a 1-pole LP filter at 5000 Hz. The
-    result is a rough approximation to the equal-loudness contour at
-    threshold.
+    Design the middle ear filters and process the input through the
+    cascade of filters. The middle ear model is a 2-pole HP filter
+    at 350 Hz in series with a 1-pole LP filter at 5000 Hz. The
+    result is a rough approximation to the equal-loudness contour
+    at threshold.
 
-    Calling variables:
-        x		input signal
-        fsamp	sampling rate in Hz
+    Arguments:
+    reference (np.ndarray):	input signal
+    freq_sample (int): sampling rate in Hz
 
-    Function output:
-        xout	filtered output
+    Returns:
+    xout (): filtered output
 
+    Updates:
     James M. Kates, 18 January 2007.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
 
     # Design the 1-pole Butterworth LP using the bilinear transformation
-    bLP, aLP = butter(1, 5000 / (0.5 * fsamp))
+    butterworth_low_pass, low_pass = butter(1, 5000 / (0.5 * freq_sample))
 
     # LP filter the input
-    y = lfilter(bLP, aLP, x)
+    y = lfilter(butterworth_low_pass, low_pass, reference)
 
     # Design the 2-pole Butterworth HP using the bilinear transformation
-    bHP, aHP = butter(2, 350 / (0.5 * fsamp), "high")
+    butterworth_high_pass, high_pass = butter(2, 350 / (0.5 * freq_sample), "high")
 
     # HP fitler the signal
-    xout = lfilter(bHP, aHP, y)
-
-    return xout
-
-
-def GammatoneBM(x, BWx, y, BWy, fs, cf):
+    return lfilter(butterworth_high_pass, high_pass, y)
+
+
+def gammatone_basilar_membrane(
+    reference,
+    reference_bandwidth,
+    processed,
+    processed_bandwidth,
+    freq_sample,
+    center_freq,
+    ear_q=9.26449,
+    min_bandwidth=24.7,
+):
     """
     4th-order gammatone auditory filter. This implementation is based
-    on the c program published on-line by Ning Ma, U. Sheffield, UK,
-    that gives an implementation of the Martin Cooke (1991) filters:
+    on the c program published on-line by Ning Ma, U. Sheffield, UK[1]_
+    that gives an implementation of the Martin Cooke filters[2]_:
     an impulse-invariant transformation of the gammatone filter. The
     signal is demodulated down to baseband using a complex exponential,
     and then passed through a cascade of four one-pole low-pass filters.
@@ -499,19 +675,32 @@ def GammatoneBM(x, BWx, y, BWy, fs, cf):
     should match; if they don't, the signals are truncated to the shorter of
     the two lengths.
 
-    Calling variables:
-    x			first sequence to be filtered
-    BWx	    bandwidth for x relative to that of a normal ear
-    y			second sequence to be filtered
-    BWy	    bandwidth for x relative to that of a normal ear
-    fs		sampling rate in Hz
-    cf		filter center frequency in Hz
+    Arguments:
+    reference (): first sequence to be filtered
+    reference_bandwidth: bandwidth for x relative to that of a normal ear
+    processed (): second sequence to be filtered
+    processed_bandwidth (): bandwidth for x relative to that of a normal ear
+    freq_sample (): sampling rate in Hz
+    center_frequency (int): filter center frequency in Hz
+    ear_q: (float): ???
+    min_bandwidth (float): ???
 
     Returns:
-    envx      filter envelope output (modulated down to baseband) 1st signal
-    BMx       BM motion for the first signal
-    envy      filter envelope output (modulated down to baseband) 2nd signal
-    BMy       BM motion for the second signal
+    reference_envelope (): filter envelope output (modulated down to baseband) 1st signal
+    reference_basilar_membrane (): Basilar Membrane for the first signal
+    processed_envelope (): filter envelope output (modulated down to baseband) 2nd signal
+    processed_basilar_membrane (): Basilar Membrane for the second signal
+
+    References:
+    .. [1] Ma N, Green P, Barker J, Coy A (2007) Exploiting correlogram
+           structure for robust speech recognition with multiple speech
+           sources. Speech Communication, 49 (12): 874-891. Availab at
+           <https://doi.org/10.1016/j.specom.2007.05.003>
+           <https://staffwww.dcs.shef.ac.uk/people/N.Ma/resources/gammatone/>
+    .. [2] Cooke, M. (1993) Modelling auditory processing and organisation.
+           Cambridge University Press
+
+    Updates:
     James M. Kates, 8 Jan 2007.
     Vectorized version for efficient MATLAB execution, 4 February 2007.
     Cosine and sine generation, 29 June 2011.
@@ -519,144 +708,171 @@ def GammatoneBM(x, BWx, y, BWy, fs, cf):
     Cosine/sine loop speed increased, 9 August 2013.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
-    # Filter ERB from Moore and Glasberg (1983)
-    earQ = 9.26449
-    minBW = 24.7
-    ERB = minBW + (cf / earQ)
+    # Filter Equivalent Rectangular Bandwidth from Moore and Glasberg (1983) doi: 10.1121/1.389861
+    erb = min_bandwidth + (center_freq / ear_q)
 
-    # Check the lengths of the two signals
-    nx = len(x)
-    ny = len(y)
-    nsamp = min(nx, ny)
-    x = x[:nsamp]
-    y = y[:nsamp]
+    # Check the lengths of the two signals and trim to shortest
+    min_sample = min(len(reference), len(processed))
+    x = reference[:min_sample]
+    y = processed[:min_sample]
 
     # Filter the first signal
     # Initialize the filter coefficients
-    tpt = 2 * np.pi / fs
-    tptBW = BWx * tpt * ERB * 1.019
-    a = np.exp(-tptBW)
-    a1 = 4.0 * a
-    a2 = -6.0 * a * a
-    a3 = 4.0 * a * a * a
-    a4 = -a * a * a * a
-    a5 = 4.0 * a * a
-    gain = 2.0 * (1 - a1 - a2 - a3 - a4) / (1 + a1 + a5)
+    tpt = 2 * np.pi / freq_sample
+    tpt_bw = reference_bandwidth * tpt * erb * 1.019
+    a = np.exp(-tpt_bw)
+    a_1 = 4.0 * a
+    a_2 = -6.0 * a * a
+    a_3 = 4.0 * a * a * a
+    a_4 = -a * a * a * a
+    a_5 = 4.0 * a * a
+    gain = 2.0 * (1 - a_1 - a_2 - a_3 - a_4) / (1 + a_1 + a_5)
 
     # Initialize the complex demodulation
     npts = len(x)
-    sincf, coscf = GammatoneBW_demodulation(
-        npts, tpt, cf, np.zeros(npts), np.zeros(npts)
+    sincf, coscf = gammatone_bandwidth_demodulation(
+        npts, tpt, center_freq, np.zeros(npts), np.zeros(npts)
     )
 
     # Filter the real and imaginary parts of the signal
-    ureal = lfilter([1, a1, a5], [1, -a1, -a2, -a3, -a4], x * coscf)
-    uimag = lfilter([1, a1, a5], [1, -a1, -a2, -a3, -a4], x * sincf)
+    ureal = lfilter([1, a_1, a_5], [1, -a_1, -a_2, -a_3, -a_4], x * coscf)
+    uimag = lfilter([1, a_1, a_5], [1, -a_1, -a_2, -a_3, -a_4], x * sincf)
 
     # Extract the BM velocity and the envelope
-    BMx = gain * (ureal * coscf + uimag * sincf)
-    envx = gain * np.sqrt(ureal * ureal + uimag * uimag)
+    reference_basilar_membrane = gain * (ureal * coscf + uimag * sincf)
+    reference_envelope = gain * np.sqrt(ureal * ureal + uimag * uimag)
 
     # Filter the second signal using the existing cosine and sine sequences
-    tptBW = BWy * tpt * ERB * 1.019
-    a = np.exp(-tptBW)
-    a1 = 4.0 * a
-    a2 = -6.0 * a * a
-    a3 = 4.0 * a * a * a
-    a4 = -a * a * a * a
-    a5 = 4.0 * a * a
-    gain = 2.0 * (1 - a1 - a2 - a3 - a4) / (1 + a1 + a5)
+    tpt_bw = processed_bandwidth * tpt * erb * 1.019
+    a = np.exp(-tpt_bw)
+    a_1 = 4.0 * a
+    a_2 = -6.0 * a * a
+    a_3 = 4.0 * a * a * a
+    a_4 = -a * a * a * a
+    a_5 = 4.0 * a * a
+    gain = 2.0 * (1 - a_1 - a_2 - a_3 - a_4) / (1 + a_1 + a_5)
 
     # Filter the real and imaginary parts of the signal
-    ureal = lfilter([1, a1, a5], [1, -a1, -a2, -a3, -a4], y * coscf)
-    uimag = lfilter([1, a1, a5], [1, -a1, -a2, -a3, -a4], y * sincf)
+    ureal = lfilter([1, a_1, a_5], [1, -a_1, -a_2, -a_3, -a_4], y * coscf)
+    uimag = lfilter([1, a_1, a_5], [1, -a_1, -a_2, -a_3, -a_4], y * sincf)
 
     # Extract the BM velocity and the envelope
-    BMy = gain * (ureal * coscf + uimag * sincf)
-    envy = gain * np.sqrt(ureal * ureal + uimag * uimag)
-
-    return envx, BMx, envy, BMy
+    processed_basilar_membrane = gain * (ureal * coscf + uimag * sincf)
+    processed_envelope = gain * np.sqrt(ureal * ureal + uimag * uimag)
+
+    return (
+        reference_envelope,
+        reference_basilar_membrane,
+        processed_envelope,
+        processed_basilar_membrane,
+    )
 
 
 @jit(nopython=True)
-def GammatoneBW_demodulation(npts, tpt, cf, coscf, sincf):
-    cn = np.cos(tpt * cf)
-    sn = np.sin(tpt * cf)
-    cold = 1
-    sold = 0
-    coscf[0] = cold
-    sincf[0] = sold
+def gammatone_bandwidth_demodulation(
+    npts, tpt, center_freq, center_freq_cos, center_freq_sin
+):
+    """Gamma tone bandwidth demodulation
+
+    Arguments:
+    npts (): ???
+    tpt (): ???
+    center_freq (): ???
+    center_freq_cos (): ???
+    sincf (): ???
+
+    Returns:
+    sincf (): ???
+    coscf (): ???
+    """
+    cos_n = np.cos(tpt * center_freq)
+    sin_n = np.sin(tpt * center_freq)
+    cold = 1.0
+    sold = 0.0
+    center_freq_cos[0] = cold
+    center_freq_sin[0] = sold
     for n in range(1, npts):
-        arg = cold * cn + sold * sn
-        sold = sold * cn - cold * sn
+        arg = cold * cos_n + sold * sin_n
+        sold = sold * cos_n - cold * sin_n
         cold = arg
-        coscf[n] = cold
-        sincf[n] = sold
+        center_freq_cos[n] = cold
+        center_freq_sin[n] = sold
 
-    return sincf, coscf
+    return center_freq_sin, center_freq_cos
 
 
-def BWadjust(control, BWmin, BWmax, Level1):
+def bandwidth_adjust(control, bandwidth_min, bandwidth_max, level1):
     """
-    Function to compute the increase in auditory filter bandwidth in response
-    to high signal levels.
+    Compute the increase in auditory filter bandwidth in response to high
+    signal levels.
 
-    Args:
-    control     envelope output in the control filter band
-    BWmin       auditory filter bandwidth computed for the loss (or NH)
-    BWmax       auditory filter bandwidth at maximum OHC damage
-    Level1      RMS=1 corresponds to Level1 dB SPL
+    Arguments:
+    control (): envelope output in the control filter band
+    bandwidth_min (): auditory filter bandwidth computed for the loss (or NH)
+    bandwidth_max (): auditory filter bandwidth at maximum OHC damage
+    level1 ():     RMS=1 corresponds to Level1 dB SPL
 
-    Returned value:
-    BW          filter bandwidth increased for high signal levels
+    Returns:
+    bandwidth (): filter bandwidth increased for high signal levels
 
+    Updates:
     James M. Kates, 21 June 2011.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
 
     # Compute the control signal level
-    cRMS = np.sqrt(np.mean(control**2))
-    cdB = 20 * np.log10(cRMS) + Level1
+    control_rms = np.sqrt(np.mean(control**2))
+    control_db = 20 * np.log10(control_rms) + level1
 
     # Adjust the auditory filter bandwidth
-    if cdB < 50:
+    if control_db < 50:
         # No BW adjustment for a signal below 50 dB SPL
-        BW = BWmin
-    elif cdB > 100:
+        return bandwidth_min
+    if control_db > 100:
         # Maximum BW if signal is above 100 dB SPL
-        BW = BWmax
-    else:
-        # Linear interpolation between BW at 50 dB and max BW at 100 dB SPL
-        BW = BWmin + ((cdB - 50) / 50) * (BWmax - BWmin)
-
-    return BW
-
-
-def EnvCompressBM(envsig, bm, control, attnOHC, thrLow, CR, fsamp, Level1):
+        return bandwidth_max
+    return bandwidth_min + ((control_db - 50) / 50) * (bandwidth_max - bandwidth_min)
+
+
+def env_compress_basilar_membrane(
+    envsig,
+    bm,  # pylint: disable=invalid-name
+    control,
+    attn_ohc,
+    threshold_low,
+    compression_ratio,
+    fsamp,
+    level1,
+    small=1e-30,
+    threshold_high=100,
+):
     """
-    Function to compute the cochlear compression in one auditory filter
-    band. The gain is linear below the lower threshold, compressive with
-    a compression ratio of CR:1 between the lower and upper thresholds,
+    Compute the cochlear compression in one auditory filter band. The
+    gain is linear below the lower threshold, compressive with a
+    compression ratio of CR:1 between the lower and upper thresholds,
     and reverts to linear above the upper threshold. The compressor
     assumes that auditory thresold is 0 dB SPL.
 
-    Calling variables:
-    envsig	analytic signal envelope (magnitude) returned by the
+    Arguments:
+    envsig (): analytic signal envelope (magnitude) returned by the
                 gammatone filter bank
-    bm        BM motion output by the filter bank
-    control	analytic control envelope returned by the wide control
+    bm (): BM motion output by the filter bank
+    control (): analytic control envelope returned by the wide control
                 path filter bank
-    attnOHC	OHC attenuation at the input to the compressor
-    thrLow	kneepoint for the low-level linear amplification
-    CR		compression ratio
-    fsamp		sampling rate in Hz
-    Level1	dB reference level: a signal having an RMS value of 1 is
+    attn_ohc (): OHC attenuation at the input to the compressor
+    threshold_Low (): kneepoint for the low-level linear amplification
+    compression_ratio (): compression ratio
+    fsamp (): sampling rate in Hz
+    level1 (): dB reference level: a signal having an RMS value of 1 is
                 assigned to Level1 dB SPL.
+    small (): ???
+    threshold_high: kneepoint for the high-level linear amplification
 
-    Function outputs:
-    y			compressed version of the signal envelope
-    b         compressed version of the BM motion
+    Returns:
+    compressed_signal (): compressed version of the signal envelope
+    compressed_basilar_membrane (): compressed version of the BM motion
 
+    Updates:
     James M. Kates, 19 January 2007.
     LP filter added 15 Feb 2007 (Ref: Zhang et al., 2001)
     Version to compress the envelope, 20 Feb 2007.
@@ -665,17 +881,18 @@ def EnvCompressBM(envsig, bm, control, attnOHC, thrLow, CR, fsamp, Level1):
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Initialize the compression parameters
-    thrHigh = 100
+    threshold_high = 100
 
     # Convert the control envelope to dB SPL
-    small = 1e-30
     logenv = np.maximum(control, small)
-    logenv = Level1 + 20 * np.log10(logenv)
-    logenv = np.minimum(logenv, thrHigh)  # Clip signal levels above the upper threshold
-    logenv = np.maximum(logenv, thrLow)  # Clip signal at the lower threshold
+    logenv = level1 + 20 * np.log10(logenv)
+    logenv = np.minimum(
+        logenv, threshold_high
+    )  # Clip signal levels above the upper threshold
+    logenv = np.maximum(logenv, threshold_low)  # Clip signal at the lower threshold
 
     # Compute the compression gain in dB
-    gain = -attnOHC - (logenv - thrLow) * (1 - (1 / CR))
+    gain = -attn_ohc - (logenv - threshold_low) * (1 - (1 / compression_ratio))
 
     # Convert the gain to linear and apply a LP filter to give a 0.2 ms delay
     gain = 10 ** (gain / 20)
@@ -684,24 +901,26 @@ def EnvCompressBM(envsig, bm, control, attnOHC, thrLow, CR, fsamp, Level1):
     gain = lfilter(b, a, gain)
 
     # Apply the gain to the signals
-    y = gain * envsig
-    b = gain * bm
+    compressed_signal = gain * envsig
+    compressed_basilar_membrane = gain * bm
 
-    return y, b
+    return compressed_signal, compressed_basilar_membrane
 
 
-def EnvAlign(x, y):
+def envelope_align(reference, output, freq_sample=24000, corr_range=100):
     """
-    Function to align the envelope of the processed signal to that of the
-    reference signal.
-
-    Args:
-    x      envelope or BM motion of the reference signal
-    y      envelope or BM motion of the output signal
+    Align the envelope of the processed signal to that of the reference
+    signal.
 
+    Arguments:
+    reference (): envelope or BM motion of the reference signal
+    output (): envelope or BM motion of the output signal
+    freq_sample (int): Frequency sample rate in Hz
+    corr_range (int): range in msec for the correlation
     Returns:
-    y      shifted output envelope to match the input
+    y (): shifted output envelope to match the input
 
+    Updates:
     James M. Kates, 28 October 2011.
     Absolute value of the cross-correlation peak removed, 22 June 2012.
     Cross-correlation range reduced, 13 August 2013.
@@ -711,65 +930,63 @@ def EnvAlign(x, y):
     # The MATLAB code limits the range of lags to search (to 100 ms) to save
     # computation time - no such option exists in numpy, but the code below limits the delay
     # to the same range as in Matlab, for consistent results
-    fsamp = 24000
-    corr_range = 100  # Range in msec for the correlation
-    lags = round(0.001 * corr_range * fsamp)  # Range in samples
-    npts = len(x)
+    lags = round(0.001 * corr_range * freq_sample)  # Range in samples
+    npts = len(reference)
     lags = min(lags, npts)
 
-    xy = correlate(x, y, "full")
-    location = np.argmax(xy[npts - lags : npts + lags])  # Limit the range in which
+    ref_out_correlation = correlate(reference, output, "full")
+    location = np.argmax(
+        ref_out_correlation[npts - lags : npts + lags]
+    )  # Limit the range in which
     delay = lags - location - 1
 
     # Time shift the output sequence
     if delay > 0:
         # Output delayed relative to the reference
-        y = np.concatenate((y[delay:npts], np.zeros(delay)))
-    elif delay < 0:
-        # Output advanced relative to the reference
-        y = np.concatenate((np.zeros(-delay), y[: npts + delay]))
-
-    return y
+        return np.concatenate((output[delay:npts], np.zeros(delay)))
+    return np.concatenate((np.zeros(-delay), output[: npts + delay]))
 
 
-def EnvSL(env, bm, attnIHC, Level1):
+def envelope_sl(reference, basilar_membrane, attnenuated_ihc, level1, small=1e-30):
     """
-    Function to convert the compressed envelope returned by
-    cochlea_envcomp to dB SL.
+    Convert the compressed envelope returned by cochlear_envcomp to dB SL.
 
-    Args
-    env			linear envelope after compression
-    bm            linear basilar membrane vibration after compression
-    attnIHC		IHC attenuation at the input to the synapse
-    Level1		level in dB SPL corresponding to 1 RMS
+    Arguments:
+    reference (): linear envelope after compression
+    basilar_membrane (): linear Basilar Membrane vibration after compression
+    attnenuated_ihc (): IHC attenuation at the input to the synapse
+    level1 (): level in dB SPL corresponding to 1 RMS
+    small (float): ???
 
-    Return
-    y				envelope in dB SL
-    b             BM vibration with envelope converted to dB SL
+    Returns:
+    _reference (): reference envelope in dB SL
+    _basilar_membrane (): Basilar Membrane vibration with envelope converted to dB SL
 
+    Updates:
     James M. Kates, 20 Feb 07.
     IHC attenuation added 9 March 2007.
     Basilar membrane vibration conversion added 2 October 2012.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Convert the envelope to dB SL
-    small = 1e-30
-    y = Level1 - attnIHC + 20 * np.log10(env + small)
-    y = np.maximum(y, 0)
+    _reference = level1 - attnenuated_ihc + 20 * np.log10(reference + small)
+    _reference = np.maximum(_reference, 0)
 
     # Convert the linear BM motion to have a dB SL envelope
-    gain = (y + small) / (env + small)
-    b = gain * bm
+    gain = (_reference + small) / (reference + small)
+    _basilar_membrane = gain * basilar_membrane
 
-    return y, b
+    return _reference, _basilar_membrane
 
 
 @jit(nopython=True)
-def IHCadapt(xdB, xBM, delta, fsamp):
+def inner_hair_cell_adaptation(
+    reference_db, reference_basilar_membrane, delta, freq_sample
+):
     """
-    Function to provide inner hair cell (IHC) adaptation. The adaptation is
-    based on an equivalent RC circuit model, and the derivatives are mapped
-    into 1st-order backward differences. Rapid and short-term adaptation are
+    Provide inner hair cell (IHC) adaptation. The adaptation is based on an
+    equivalent RC circuit model, and the derivatives are mapped into
+    1st-order backward differences. Rapid and short-term adaptation are
     provided. The input is the signal envelope in dB SL, with IHC attenuation
     already applied to the envelope. The outputs are the envelope in dB SL
     with adaptation providing overshoot of the long-term output level, and
@@ -777,17 +994,18 @@ def IHCadapt(xdB, xBM, delta, fsamp):
     the adaptation. IHC attenuation and additive noise for the equivalent
     auditory threshold are provided by a subsequent call to eb_BMatten.
 
-    Calling variables:
-    xdB      signal envelope in one frequency band in dB SL
+    Arguments:
+    reference_db (np.ndarray): signal envelope in one frequency band in dB SL
              contains OHC compression and IHC attenuation
-    xBM      basilar membrane vibration with OHC compression but no IHC atten
-    delta    overshoot factor = delta x steady-state
-    fsamp    sampling rate in Hz
+    reference_basilar_membrane (): basilar membrane vibration with OHC compression but no IHC atten
+    delta (): overshoot factor = delta x steady-state
+    freq_sample (int): sampling rate in Hz
 
     Returns:
-    ydB      envelope in dB SL with IHC adaptation
-    yBM      BM motion multiplied by the IHC adaptation gain function
+    output_db ():     envelope in dB SL with IHC adaptation
+    output_basilar_membrane (): Basilar Membrane multiplied by the IHC adaptation gain function
 
+    Updates:
     James M. Kates, 1 October 2012.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
@@ -802,190 +1020,220 @@ def IHCadapt(xdB, xBM, delta, fsamp):
     tau2 = 0.001 * tau2
 
     # Equivalent circuit parameters
-    T = 1 / fsamp
-    R1 = 1 / delta
-    R2 = 0.5 * (1 - R1)
-    R3 = R2
-    C1 = tau1 * (R1 + R2) / (R1 * R2)
-    C2 = tau2 / ((R1 + R2) * R3)
+    freq_sample_inverse = 1 / freq_sample
+    r_1 = 1 / delta
+    r_2 = 0.5 * (1 - r_1)
+    r_3 = r_2
+    c_1 = tau1 * (r_1 + r_2) / (r_1 * r_2)
+    c_2 = tau2 / ((r_1 + r_2) * r_3)
 
     # Intermediate values used for the voltage update matrix inversion
-    a11 = R1 + R2 + R1 * R2 * (C1 / T)
-    a12 = -R1
-    a21 = -R3
-    a22 = R2 + R3 + R2 * R3 * (C2 / T)
-    denom = 1 / (a11 * a22 - a21 * a12)
+    a11 = r_1 + r_2 + r_1 * r_2 * (c_1 / freq_sample_inverse)
+    a12 = -r_1
+    a21 = -r_3
+    a22 = r_2 + r_3 + r_2 * r_3 * (c_2 / freq_sample_inverse)
+    denom = 1 / ((a11 * a22) - (a21 * a12))
 
     # Additional intermediate values
-    R1inv = 1 / R1
-    R12C1 = R1 * R2 * (C1 / T)
-    R23C2 = R2 * R3 * (C2 / T)
+    r_1_inv = 1 / r_1
+    product_r1_r2_c1 = r_1 * r_2 * (c_1 / freq_sample_inverse)
+    product_r2_r3_c2 = r_2 * r_3 * (c_2 / freq_sample_inverse)
 
     # Initalize the outputs and state of the equivalent circuit
-    nsamp = len(xdB)
-    gain = np.ones_like(xdB)  # Gain vector to apply to the BM motion, default is 1
-    ydB = np.zeros_like(xdB)
-    V1 = 0
-    V2 = 0
+    nsamp = len(reference_db)
+    gain = np.ones_like(
+        reference_db
+    )  # Gain vector to apply to the BM motion, default is 1
+    output_db = np.zeros_like(reference_db)
+    v_1 = 0
+    v_2 = 0
     small = 1e-30
 
     # Loop to process the envelope signal
     # The gain asymptote is 1 for an input envelope of 0 dB SPL
     for n in range(nsamp):
-        V0 = xdB[n]
-        b1 = V0 * R2 + R12C1 * V1
-        b2 = R23C2 * V2
-        V1 = denom * (a22 * b1 - a12 * b2)
-        V2 = denom * (-a21 * b1 + a11 * b2)
-        out = (V0 - V1) * R1inv
-        ydB[n] = out
+        v_0 = reference_db[n]
+        b_1 = v_0 * r_2 + product_r1_r2_c1 * v_1
+        b_2 = product_r2_r3_c2 * v_2
+        v_1 = denom * (a22 * b_1 - a12 * b_2)
+        v_2 = denom * (-a21 * b_1 + a11 * b_2)
+        out = (v_0 - v_1) * r_1_inv
+        output_db[n] = out
 
-    ydB = np.maximum(ydB, 0)
-    gain = (ydB + small) / (xdB + small)
+    output_db = np.maximum(output_db, 0)
+    gain = (output_db + small) / (reference_db + small)
 
-    yBM = gain * xBM
+    output_basilar_membrane = gain * reference_basilar_membrane
 
-    return ydB, yBM
+    return output_db, output_basilar_membrane
 
 
-def BMaddnoise(x, thr, Level1):
+def basilar_membrane_add_noise(reference, threshold, level1):
     """
-    Function to apply the IHC attenuation to the BM motion and to add a
+    Apply the IHC attenuation to the BM motion and to add a
     low-level Gaussian noise to give the auditory threshold.
 
-    Args:
-    x         BM motion to be attenuated
-    thr       additive noise level in dB re:auditory threshold
-    Level1    an input having RMS=1 corresponds to Leve1 dB SPL
+    Arguments:
+    reference ():         BM motion to be attenuated
+    threshold (): additive noise level in dB re:auditory threshold
+    level1 (): an input having RMS=1 corresponds to Leve1 dB SPL
 
     Returns:
-    y         attenuated signal with threhsold noise added
+    Attenuated signal with threhsold noise added
 
+    Updates:
     James M. Kates, 19 June 2012.
     Just additive noise, 2 Oct 2012.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
-    gn = 10 ** ((thr - Level1) / 20)  # Linear gain for the noise
+    gain = 10 ** ((threshold - level1) / 20)  # Linear gain for the noise
 
     # rng = np.random.default_rng()
-    noise = gn * np.random.standard_normal(x.shape)  # Gaussian RMS=1, then attenuated
-    y = x + noise
-
-    return y
-
-
-def GroupDelayComp(xenv, BW, cfreq, fsamp):
+    noise = gain * np.random.standard_normal(
+        reference.shape
+    )  # Gaussian RMS=1, then attenuated
+    return reference + noise
+
+
+def group_delay_compensate(
+    reference,
+    bandwidths,
+    center_freq,
+    freq_sample,
+    ear_q=9.26449,
+    min_bandwidth=24.7,
+):
     """
-    Function to compensate for the group delay of the gammatone filter bank.
-    The group delay is computed for each filter at its center frequency. The
-    firing rate output of the IHC model is then adjusted so that all outputs
-    have the same group delay.
+    Compensate for the group delay of the gammatone filter bank. The group
+    delay is computed for each filter at its center frequency. The firing
+    rate output of the IHC model is then adjusted so that all outputs have
+    the same group delay.
 
-    Calling variables:
-        xenv (np.ndarray): matrix of signal envelopes or BM motion
-        BW (): gammatone filter bandwidths adjusted for loss
-        cfreq (): center frequencies of the bands
-        fsamp (): sampling rate for the input signal in Hz (e.g. 24,000 Hz)
+    Arguments:
+    xenv (np.ndarray): matrix of signal envelopes or BM motion
+    bandwidths (): gammatone filter bandwidths adjusted for loss
+    center_freq (): center frequencies of the bands
+    freq_sample (): sampling rate for the input signal in Hz (e.g. 24,000 Hz)
+    ear_q (float):
+    min_bandwidth (float) :
 
     Returns:
-        yenv    envelopes or BM motion compensated for the group delay
+    processed (): envelopes or BM motion compensated for the group delay.
 
+    Updates:
     James M. Kates, 28 October 2011.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Processing parameters
-    nchan = len(BW)
+    nchan = len(bandwidths)
 
     # Filter ERB from Moore and Glasberg (1983)
-    earQ = 9.26449
-    minBW = 24.7
-    ERB = minBW + (cfreq / earQ)
+    erb = min_bandwidth + (center_freq / ear_q)
 
     # Initialize the gamatone filter coefficients
-    tpt = 2 * np.pi / fsamp
-    tptBW = tpt * 1.019 * BW * ERB
-    a = np.exp(-tptBW)
-    a1 = 4.0 * a
-    a2 = -6.0 * a * a
-    a3 = 4.0 * a * a * a
-    a4 = -a * a * a * a
-    a5 = 4.0 * a * a
+    tpt = 2 * np.pi / freq_sample
+    tpt_bandwidth = tpt * 1.019 * bandwidths * erb
+    a = np.exp(-tpt_bandwidth)
+    a_1 = 4.0 * a
+    a_2 = -6.0 * a * a
+    a_3 = 4.0 * a * a * a
+    a_4 = -a * a * a * a
+    a_5 = 4.0 * a * a
 
     # Compute the group delay in samples at fsamp for each filter
-    gd = np.zeros(nchan)
+    _group_delay = np.zeros(nchan)
     for n in range(nchan):
-        _, gd[n] = group_delay(
-            ([1, a1[n], a5[n]], [1, -a1[n], -a2[n], -a3[n], -a4[n]]), 1
+        _, _group_delay[n] = group_delay(
+            ([1, a_1[n], a_5[n]], [1, -a_1[n], -a_2[n], -a_3[n], -a_4[n]]), 1
         )
-    gd = np.round(gd).astype("int")  # convert to integer samples
+    _group_delay = np.round(_group_delay).astype("int")  # convert to integer samples
 
     # Compute the delay correlation
-    gmin = np.min(gd)
-    gd = gd - gmin  # Remove the minimum delay from all the over values
-    gmax = np.max(gd)
-    correct = gmax - gd  # Samples delay needed to add to give alignment
+    group_delay_min = np.min(_group_delay)
+    _group_delay = (
+        _group_delay - group_delay_min
+    )  # Remove the minimum delay from all the over values
+    group_delay_max = np.max(_group_delay)
+    correct = (
+        group_delay_max - _group_delay
+    )  # Samples delay needed to add to give alignment
 
     # Add delay correction to each frequency band
-    yenv = np.zeros(xenv.shape)
+    processed = np.zeros(reference.shape)
     for n in range(nchan):
-        r = xenv[n]
-        npts = len(r)
-        yenv[n] = np.concatenate((np.zeros(correct[n]), r[: npts - correct[n]]))
-
-    return yenv
-
-
-def aveSL(env, control, attnOHC, thrLow, CR, attnIHC, Level1):
+        ref = reference[n]
+        npts = len(ref)
+        processed[n] = np.concatenate((np.zeros(correct[n]), ref[: npts - correct[n]]))
+
+    return processed
+
+
+def convert_rms_to_sl(
+    reference,
+    control,
+    attnenuated_ohc,
+    threshold_low,
+    compression_ratio,
+    attnenuated_ihc,
+    level1,
+    threshold_high=100,
+    small=1e-30,
+):
     """
-    Function to covert the RMS average output of the gammatone filter bank
+    Covert the Root Mean Square average output of the gammatone filter bank
     into dB SL. The gain is linear below the lower threshold, compressive
     with a compression ratio of CR:1 between the lower and upper thresholds,
     and reverts to linear above the upper threshold. The compressor
     assumes that auditory thresold is 0 dB SPL.
 
-    Calling variables:
-    env		analytic signal envelope (magnitude) returned by the
-                gammatone filter bank, RMS average level
-    control   control signal envelope
-    attnOHC	OHC attenuation at the input to the compressor
-    thrLow	kneepoint for the low-level linear amplification
-    CR		compression ratio
-    attnIHC	IHC attenuation at the input to the synapse
-    Level1	dB reference level: a signal having an RMS value of 1 is
+    Arguments:
+    reference (): analytic signal envelope (magnitude) returned by the
+    gammatone filter bank, RMS average level
+    control (): control signal envelope
+    attenuated_ohc (): OHC attenuation at the input to the compressor
+    threshold_low (): kneepoint for the low-level linear amplification
+    compression_ratio (): compression ratio
+    attenuated_ihc (): IHC attenuation at the input to the synapse
+    level1 (): dB reference level: a signal having an RMS value of 1 is
                 assigned to Level1 dB SPL.
+    threshold_high (int):
+    small (float):
 
-    Function output:
-    xdB		compressed output in dB above the impaired threshold
+    Returns:
+    reference_db (): compressed output in dB above the impaired threshold
 
+    Updates:
     James M. Kates, 6 August 2007.
     Version for two-tone suppression, 29 August 2008.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
 
     # Initialize the compression parameters
-    thrHigh = 100  # Upper compression threshold
+    threshold_high = 100  # Upper compression threshold
 
     # Convert the control to dB SPL
     small = 1e-30
-    logenv = np.maximum(control, small)
-    logenv = Level1 + 20 * np.log10(logenv)
-    logenv = np.minimum(logenv, thrHigh)
-    logenv = np.maximum(logenv, thrLow)
+    control_db_spl = np.maximum(control, small)
+    control_db_spl = level1 + 20 * np.log10(control_db_spl)
+    control_db_spl = np.minimum(control_db_spl, threshold_high)
+    control_db_spl = np.maximum(control_db_spl, threshold_low)
 
     # Compute compression gain in dB
-    gain = -attnOHC - (logenv - thrLow) * (1 - (1 / CR))
+    gain = -attnenuated_ohc - (control_db_spl - threshold_low) * (
+        1 - (1 / compression_ratio)
+    )
 
     # Convert the signal envelope to dB SPL
-    logenv = np.maximum(env, small)
-    logenv = Level1 + 20 * np.log10(logenv)
-    logenv = np.maximum(logenv, 0)
-    xdB = logenv + gain - attnIHC
-    xdB = np.maximum(xdB, 0)
+    control_db_spl = np.maximum(reference, small)
+    control_db_spl = level1 + 20 * np.log10(control_db_spl)
+    control_db_spl = np.maximum(control_db_spl, 0)
+    reference_db = control_db_spl + gain - attnenuated_ihc
+    reference_db = np.maximum(reference_db, 0)
 
-    return xdB
+    return reference_db
 
 
-def env_smooth(env, segsize, fsamp):
+def env_smooth(envelopes: np.ndarray, segment_size, freq_sample):
     """
     Function to smooth the envelope returned by the cochlear model. The
     envelope is divided into segments having a 50% overlap. Each segment is
@@ -994,43 +1242,47 @@ def env_smooth(env, segsize, fsamp):
     2*(1000/segsize).
 
     Arguments:
-        xenv: matrix of envelopes in each of the auditory bands
-        segsize: averaging segment size in msec
-        fsamp: input envelope sampling rate in Hz
+    envelopes (np.ndarray): matrix of envelopes in each of the auditory bands
+    segment_size: averaging segment size in msec
+    freq_sample (int): input envelope sampling rate in Hz
 
     Returns:
-        smooth: matrix of subsampled windowed averages in each band
+    smooth: matrix of subsampled windowed averages in each band
 
+    Updates:
     James M. Kates, 26 January 2007.
     Final half segment added 27 August 2012.
-
     Translated from MATLAB to Python by Gerardo Roa Dabike, September 2022.
     """
 
     # Compute the window
-    nwin = int(np.around(segsize * (0.001 * fsamp)))  # Segment size in samples
-    test = nwin - 2 * np.floor(nwin / 2)  # 0=even, 1=odd
+    n_samples = int(
+        np.around(segment_size * (0.001 * freq_sample))
+    )  # Segment size in samples
+    test = n_samples - 2 * np.floor(n_samples / 2)  # 0=even, 1=odd
     if test > 0:
         # Force window length to be even
-        nwin = nwin + 1
-    window = np.hanning(nwin)  # Raised cosine von Hann window
+        n_samples = n_samples + 1
+    window = np.hanning(n_samples)  # Raised cosine von Hann window
     wsum = np.sum(window)  # Sum for normalization
 
     #  The first segment has a half window
-    nhalf = int(nwin / 2)
-    halfwindow = window[nhalf:nwin]
+    nhalf = int(n_samples / 2)
+    halfwindow = window[nhalf:n_samples]
     halfsum = np.sum(halfwindow)
 
     # Number of segments and assign the matrix storage
-    nchan = np.size(env, 0)
-    npts = np.size(env, 1)
-    nseg = int(1 + np.floor(npts / nwin) + np.floor((npts - nwin / 2) / nwin))
-    smooth = np.zeros((nchan, nseg))
+    n_channels = np.size(envelopes, 0)
+    npts = np.size(envelopes, 1)
+    nseg = int(
+        1 + np.floor(npts / n_samples) + np.floor((npts - n_samples / 2) / n_samples)
+    )
+    smooth = np.zeros((n_channels, nseg))
 
     #  Loop to compute the envelope in each frequency band
-    for k in range(nchan):
+    for k in range(n_channels):
         # Extract the envelope in the frequency band
-        r = env[k, :]
+        r = envelopes[k, :]  # pylint: disable=invalid-name
 
         # The first (half) windowed segment
         nstart = 0
@@ -1039,7 +1291,7 @@ def env_smooth(env, segsize, fsamp):
         # Loop over the remaining full segments, 50% overlap
         for n in range(1, nseg - 1):
             nstart = int(nstart + nhalf)
-            nstop = int(nstart + nwin)
+            nstop = int(nstart + n_samples)
             smooth[k, n] = sum(r[nstart:nstop] * window.conj().transpose()) / wsum
 
         # The last (half) windowed segment
@@ -1052,105 +1304,120 @@ def env_smooth(env, segsize, fsamp):
     return smooth
 
 
-def melcor(x, y, thr, addnoise):
+def mel_cepstrum_correlation(reference, distorted, threshold, addnoise):
     """
-    Function to compute the cross-correlations between the input signal
-    time-frequency envelope and the distortion time-frequency envelope. For
-    each time interval, the log spectrum is fitted with a set of half-cosine
-    basis functions. The spectrum weighted by the basis functions corresponds
-    to mel cepstral coefficients computed in the frequency domain. The
-    amplitude-normalized cross-covariance between the time-varying basis
-    functions for the input and output signals is then computed.
+    Compute the cross-correlations between the input signal time-frequency
+    envelope and the distortion time-frequency envelope.
+
+    For each time interval, the log spectrum is fitted with a set of
+    half-cosine basis functions. The spectrum weighted by the basis
+    functions corresponds to Mel Cepstral Coefficients computed in the
+    frequency domain. The amplitude-normalized cross-covariance between
+    the time-varying basis functions for the input and output signals is
+    then computed.
 
     Arguments:
-        x : subsampled input signal envelope in dB SL in each critical band
-        y : subsampled distorted output signal envelope
-        thr : threshold in dB SPL to include segment in calculation
-        addnoise : additive Gaussian noise to ensure 0 cross-corr at low levels
+    reference (): subsampled input signal envelope in dB SL in each critical band
+    distorted (): subsampled distorted output signal envelope
+    threshold (): threshold in dB SPL to include segment in calculation
+    addnoise (): additive Gaussian noise to ensure 0 cross-corr at low levels
 
     Returns:
-        m1 : average cepstral correlation 2-6, input vs output
-        xy : individual cepstral correlations, input vs output
+    average_cepstral_correlation : average cepstral correlation 2-6, input vs output
+    individual_cepstral_correlations : individual cepstral correlations, input vs output
 
+    Updates:
     James M. Kates, 24 October 2006.
     Difference signal removed for cochlear model, 31 January 2007.
     Absolute value added 13 May 2011.
     Changed to loudness criterion for silence threhsold, 28 August 2012.
-
     Translated from MATLAB to Python by Gerardo Roa Dabike, September 2022.
     """
 
     # Processing parameters
-    nbands = x.shape[0]
+    nbands = reference.shape[0]
 
     # Mel cepstrum basis functions (mel cepstrum because of auditory bands)
     nbasis = 6  # Number of cepstral coefficients to be used
     freq = np.arange(nbasis)
     k = np.arange(nbands)
-    cepm = np.zeros((nbands, nbasis))
-    for nb in range(nbasis):
-        basis = np.cos(k * float(freq[nb]) * np.pi / float((nbands - 1)))
-        cepm[:, nb] = basis / np.linalg.norm(basis)
+    mel_cepstral = np.zeros((nbands, nbasis))
+    for n in range(nbasis):
+        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
-    xLinear = 10 ** (x / 20)  # Convert envelope dB to linear (specific loudness)
-    xsum = np.sum(xLinear, 0) / nbands  # Proportional to loudness in sones
+    reference_linear = 10 ** (
+        reference / 20
+    )  # Convert envelope dB to linear (specific loudness)
+    xsum = np.sum(reference_linear, 0) / nbands  # Proportional to loudness in sones
     xsum = 20 * np.log10(xsum)  # Convert back to dB (loudness in phons)
-    index = np.where(xsum > thr)[0]  # Identify those segments above threshold
+    index = np.where(xsum > threshold)[0]  # Identify those segments above threshold
     nsamp = index.shape[0]  # Number of segments above threshold
 
     # Exit if not enough segments above zero
-    m1 = 0
-    xy = 0
+    average_cepstral_correlation = 0
+    individual_cepstral_correlations = 0
     if nsamp <= 1:
         print("Function eb.melcor: Signal below threshold, outputs set to 0.")
-        return m1, xy
+        return average_cepstral_correlation, individual_cepstral_correlations
 
     # Remove the silent intervals
-    x = x[:, index]
-    y = y[:, index]
+    ref = reference[:, index]
+    proc = distorted[:, index]
 
     # Add the low-level noise to the envelopes
-    x = x + addnoise * np.random.standard_normal(x.shape)
-    y = y + addnoise * np.random.standard_normal(y.shape)
+    ref = ref + addnoise * np.random.standard_normal(ref.shape)
+    proc = proc + addnoise * np.random.standard_normal(proc.shape)
 
     # Compute the mel cepstrum coefficients using only those segments
     # above threshold
-    xcep = np.zeros((nbasis, nsamp))  # Input
-    ycep = np.zeros((nbasis, nsamp))  # Output
+    reference_cep = np.zeros((nbasis, nsamp))  # Input
+    processed_cep = np.zeros((nbasis, nsamp))  # Output
     for n in range(nsamp):
         for k in range(nbasis):
-            xcep[k, n] = np.sum(x[:, n] * cepm[:, k])
-            ycep[k, n] = np.sum(y[:, n] * cepm[:, k])
+            reference_cep[k, n] = np.sum(ref[:, n] * mel_cepstral[:, k])
+            processed_cep[k, n] = np.sum(proc[:, n] * mel_cepstral[:, k])
 
     # Remove the average value from the cepstral coefficients. The
     # cross-correlation thus becomes a cross-covariance, and there
     # is no effect of the absolute signal level in dB.
     for k in range(nbasis):
-        xcep[k, :] = xcep[k, :] - np.mean(xcep[k, :], axis=0)
-        ycep[k, :] = ycep[k, :] - np.mean(ycep[k, :], axis=0)
+        reference_cep[k, :] = reference_cep[k, :] - np.mean(reference_cep[k, :], axis=0)
+        processed_cep[k, :] = processed_cep[k, :] - np.mean(processed_cep[k, :], axis=0)
 
     # Normalized cross-correlations between the time-varying cepstral coeff
-    xy = np.zeros(nbasis)  # Input vs output
+    individual_cepstral_correlations = np.zeros(nbasis)  # Input vs output
     small = 1.0e-30
     for k in range(nbasis):
-        xsum = np.sum(xcep[k, :] ** 2)
-        ysum = np.sum(ycep[k, :] ** 2)
+        xsum = np.sum(reference_cep[k, :] ** 2)
+        ysum = np.sum(processed_cep[k, :] ** 2)
         if (xsum < small) or (ysum < small):
-            xy[k] = 0.0
+            individual_cepstral_correlations[k] = 0.0
         else:
-            xy[k] = np.abs(np.sum(xcep[k, :] * ycep[k, :]) / np.sqrt(xsum * ysum))
+            individual_cepstral_correlations[k] = np.abs(
+                np.sum(reference_cep[k, :] * processed_cep[k, :]) / np.sqrt(xsum * ysum)
+            )
 
     #
     # % Figure of merit is the average of the cepstral correlations, ignoring
     # % the first (average spectrum level).
-    m1 = np.sum(xy[1:nbasis]) / (nbasis - 1)
-    return m1, xy
-
-
-def melcor9(x, y, thr, addnoise, segsize):
+    average_cepstral_correlation = np.sum(
+        individual_cepstral_correlations[1:nbasis]
+    ) / (nbasis - 1)
+    return average_cepstral_correlation, individual_cepstral_correlations
+
+
+def melcor9(
+    reference,
+    distorted,
+    threshold,
+    add_noise,
+    segment_size,
+    n_cepstral_coef=6.0,
+):
     """
-    Function to compute the cross-correlations between the input signal
+    Compute the cross-correlations between the input signal
     time-frequency envelope and the distortion time-frequency envelope. For
     each time interval, the log spectrum is fitted with a set of half-cosine
     basis functions. The spectrum weighted by the basis functions corresponds
@@ -1160,183 +1427,240 @@ def melcor9(x, y, thr, addnoise, segsize):
     the 8 modulation frequencies.
 
     Arguments:
-        x : subsampled input signal envelope in dB SL in each critical band
-        y : subsampled distorted output signal envelope
-        thr : threshold in dB SPL to include segment in calculation
-        addnoise : additive Gaussian noise to ensure 0 cross-corr at low levels
-        segsize : segment size in ms used for the envelope LP filter (8 msec)
+    reference (): subsampled input signal envelope in dB SL in each critical band
+    distorted (): subsampled distorted output signal envelope
+    threshold (): threshold in dB SPL to include segment in calculation
+    add_noise (): additive Gaussian noise to ensure 0 cross-corr at low levels
+    segment_size (): segment size in ms used for the envelope LP filter (8 msec)
+    n_cepstral_coef (int): Number of cepstral ceofficients
 
     Returns:
-        CMave : average of the modulation correlations across analysis frequency
-            bands and modulation frequency bands, basis functions 2 -6
-        CMlow : average over the four lower mod freq bands, 0 - 20 Hz
-        CMhigh : average over the four higher mod freq bands, 20 - 125 Hz
-        CMmod : vector of cross-correlations by modulation frequency,
+    mel_cepstral_average (): average of the modulation correlations across analysis frequency
+        bands and modulation frequency bands, basis functions 2 -6
+    mel_cepstral_low (): average over the four lower mod freq bands, 0 - 20 Hz
+    mel_cepstral_high (): average over the four higher mod freq bands, 20 - 125 Hz
+    mel_cepstral_modulation (): vector of cross-correlations by modulation frequency,
             averaged over ananlysis frequency band
 
+    Updates:
     James M. Kates, 24 October 2006.
     Difference signal removed for cochlear model, 31 January 2007.
     Absolute value added 13 May 2011.
     Changed to loudness criterion for silence threshold, 28 August 2012.
     Version using envelope modulation filters, 15 July 2014.
     Modulation frequency vector output added 27 August 2014.
-
     Translated from MATLAB to Python by Gerardo Roa Dabike, September 2022.
     """
 
     # Processing parameters
-    nbands = x.shape[0]
+    nbands = reference.shape[0]
 
     # Mel cepstrum basis functions (mel cepstrum because of auditory bands)
-    nbasis = 6  # Number of cepstral coefficients to be used
-    freq = np.arange(nbasis)
+    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, nbasis))
-    for nb in range(nbasis):
-        basis = np.cos(k * float(freq[nb]) * np.pi / float((nbands - 1)))
-        cepm[:, nb] = basis / np.linalg.norm(basis)
+    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)))
+        cepm[:, n] = basis / np.linalg.norm(basis)
 
     # Find the segments that lie sufficiently above the quiescent rate
-    xLinear = 10 ** (x / 20)  # Convert envelope dB to linear (specific loudness)
-    xsum = np.sum(xLinear, 0) / nbands  # Proportional to loudness in sones
-    xsum = 20 * np.log10(xsum)  # Convert back to dB (loudness in phons)
-    index = np.where(xsum > thr)[0]  # Identify those segments above threshold
-    nsamp = index.shape[0]  # Number of segments above threshold
+    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
+    segments_above_threshold = index.shape[0]  # Number of segments above threshold
 
     # Modulation filter bands, segment size is 8 msec
     edge = [4.0, 8.0, 12.5, 20.0, 32.0, 50.0, 80.0]  # 8 bands covering 0 to 125 Hz
-    nmod = 1 + len(edge)  # Number of modulation filter bands
+    n_modulation_filter_bands = 1 + len(edge)  # Number of modulation filter bands
 
     # Exit if not enough segments above zero
-    CMave = 0
-    CMlow = 0
-    CMhigh = 0
-    CMmod = np.zeros(nmod)
-    if nsamp <= 1:
+    mel_cepstral_average = 0
+    mel_cepstral_low = 0
+    mel_cepstral_high = 0
+    mel_cepstral_modulation = np.zeros(n_modulation_filter_bands)
+    if segments_above_threshold <= 1:
         print("Function eb.melcor9: Signal below threshold, outputs set to 0.")
-        return CMave, CMlow, CMhigh, CMmod
+        return (
+            mel_cepstral_average,
+            mel_cepstral_low,
+            mel_cepstral_high,
+            mel_cepstral_modulation,
+        )
 
     # Remove the silent intervals
-    x = x[:, index]
-    y = y[:, index]
+    _reference = reference[:, index]
+    _distorted = distorted[:, index]
 
     # Add the low-level noise to the envelopes
-    x = x + addnoise * np.random.standard_normal(x.shape)
-    y = y + addnoise * np.random.standard_normal(y.shape)
+    _reference = _reference + add_noise * np.random.standard_normal(_reference.shape)
+    _distorted = _distorted + add_noise * np.random.standard_normal(_distorted.shape)
 
     # Compute the mel cepstrum coefficients using only those segments
     # above threshold
-    xcep = np.zeros((nbasis, nsamp))  # Input
-    ycep = np.zeros((nbasis, nsamp))  # Output
-    for n in range(nsamp):
-        for k in range(nbasis):
-            xcep[k, n] = np.sum(x[:, n] * cepm[:, k])
-            ycep[k, n] = np.sum(y[:, n] * cepm[:, k])
+    reference_cep = np.zeros((n_cepstral_coef, segments_above_threshold))  # Input
+    distorted_cep = np.zeros((n_cepstral_coef, segments_above_threshold))  # Output
+    for n in range(segments_above_threshold):
+        for k in range(n_cepstral_coef):
+            reference_cep[k, n] = np.sum(_reference[:, n] * cepm[:, k])
+            distorted_cep[k, n] = np.sum(_distorted[:, n] * cepm[:, k])
 
     # Remove the average value from the cepstral coefficients. The
     # cross-correlation thus becomes a cross-covariance, and there
     # is no effect of the absolute signal level in dB.
-    for k in range(nbasis):
-        xcep[k, :] = xcep[k, :] - np.mean(xcep[k, :], axis=0)
-        ycep[k, :] = ycep[k, :] - np.mean(ycep[k, :], axis=0)
+    for k in range(n_cepstral_coef):
+        reference_cep[k, :] = reference_cep[k, :] - np.mean(reference_cep[k, :], axis=0)
+        distorted_cep[k, :] = distorted_cep[k, :] - np.mean(distorted_cep[k, :], axis=0)
 
     # Envelope sampling parameters
-    fsub = 1000.0 / (0.5 * segsize)  # Envelope sampling frequency in Hz
-    fnyq = 0.5 * fsub  # Envelope Nyquist frequency
+    sampling_freq = 1000.0 / (0.5 * segment_size)  # Envelope sampling frequency in Hz
+    nyquist_freq = 0.5 * sampling_freq  # Envelope Nyquist frequency
 
     # Design the linear-phase envelope modulation filters
-    nfir = np.around(128 * (fnyq / 125))  # Adjust filter length to sampling rate
-    nfir = int(2 * np.floor(nfir / 2))  # Force an even filter length
-    b = np.zeros((nmod, nfir + 1))
+    n_fir = np.around(
+        128 * (nyquist_freq / 125)
+    )  # Adjust filter length to sampling rate
+    n_fir = int(2 * np.floor(n_fir / 2))  # Force an even filter length
+    b = np.zeros((n_modulation_filter_bands, n_fir + 1))
 
+    # LP filter 0-4 Hz
     b[0, :] = firwin(
-        nfir + 1, edge[0] / fnyq, window="hann", pass_zero="lowpass"
-    )  # LP filter 0-4 Hz
-    b[nmod - 1, :] = firwin(
-        nfir + 1, edge[nmod - 2] / fnyq, window="hann", pass_zero="highpass"
-    )  # HP 80-125 Hz
-    for m in range(1, nmod - 1):
+        n_fir + 1, edge[0] / nyquist_freq, window="hann", pass_zero="lowpass"
+    )
+    # HP 80-125 Hz
+    b[n_modulation_filter_bands - 1, :] = firwin(
+        n_fir + 1,
+        edge[n_modulation_filter_bands - 2] / nyquist_freq,
+        window="hann",
+        pass_zero="highpass",
+    )
+    # Bandpass filter
+    for m in range(1, n_modulation_filter_bands - 1):
         b[m, :] = firwin(
-            nfir + 1,
-            [edge[m - 1] / fnyq, edge[m] / fnyq],
+            n_fir + 1,
+            [edge[m - 1] / nyquist_freq, edge[m] / nyquist_freq],
             window="hann",
             pass_zero="bandpass",
-        )  # Bandpass filter
+        )
 
-    CM = melcor9_crosscovmatrix(b, nmod, nbasis, nsamp, nfir, xcep, ycep)
+    mel_cepstral_cross_covar = melcor9_crosscovmatrix(
+        b,
+        n_modulation_filter_bands,
+        n_cepstral_coef,
+        segments_above_threshold,
+        n_fir,
+        reference_cep,
+        distorted_cep,
+    )
 
     # Average over the  modulation filters and basis functions 2 - 6
-    for m in range(nmod):
-        for j in range(1, nbasis):
-            CMave += CM[m, j]
+    for m in range(n_modulation_filter_bands):
+        for j in range(1, n_cepstral_coef):
+            mel_cepstral_average += mel_cepstral_cross_covar[m, j]
 
-    CMave = CMave / (nmod * (nbasis - 1))
+    mel_cepstral_average = mel_cepstral_average / (
+        n_modulation_filter_bands * (n_cepstral_coef - 1)
+    )
 
     # Average over the four lower modulation filters
     for m in range(4):
-        for j in range(1, nbasis):
-            CMlow += CM[m, j]
+        for j in range(1, n_cepstral_coef):
+            mel_cepstral_low += mel_cepstral_cross_covar[m, j]
 
-    CMlow = CMlow / (4 * (nbasis - 1))
+    mel_cepstral_low = mel_cepstral_low / (4 * (n_cepstral_coef - 1))
 
     #  Average over the four upper modulation filters
     for m in range(4, 8):
-        for j in range(1, nbasis):
-            CMhigh += CM[m, j]
+        for j in range(1, n_cepstral_coef):
+            mel_cepstral_high += mel_cepstral_cross_covar[m, j]
 
-    CMhigh = CMhigh / (4 * (nbasis - 1))
+    mel_cepstral_high = mel_cepstral_high / (4 * (n_cepstral_coef - 1))
 
     # Average each modulation frequency over the basis functions
-    for m in range(nmod):
+    for m in range(n_modulation_filter_bands):
         ave = 0
-        for j in range(1, nbasis):
-            ave += CM[m, j]
+        for j in range(1, n_cepstral_coef):
+            ave += mel_cepstral_cross_covar[m, j]
 
-        CMmod[m] = ave / (nbasis - 1)
+        mel_cepstral_modulation[m] = ave / (n_cepstral_coef - 1)
+
+    return (
+        mel_cepstral_average,
+        mel_cepstral_low,
+        mel_cepstral_high,
+        mel_cepstral_modulation,
+    )
 
-    return CMave, CMlow, CMhigh, CMmod
 
+def melcor9_crosscovmatrix(b, nmod, nbasis, nsamp, nfir, reference_cep, processed_cep):
+    """Compute the cross-covariance matrix.
 
-def melcor9_crosscovmatrix(b, nmod, nbasis, nsamp, nfir, xcep, ycep):
-    """Compute the cross-covariance matrix."""
+    Arguments:
+    b (): ???
+    nmod (): ???
+    nbasis (): ???
+    nsamp (): ???
+    nfir (): ???
+    xcep (): ???
+    ycep (): ???
+
+    Returns:
+    cross_covariance_matrix ():
+    """
     small = 1.0e-30
     nfir2 = nfir / 2
     # Convolve the input and output envelopes with the modulation filters
-    X = np.zeros((nmod, nbasis, nsamp))
-    Y = np.zeros((nmod, nbasis, nsamp))
+    reference = np.zeros((nmod, nbasis, nsamp))
+    processed = np.zeros((nmod, nbasis, nsamp))
     for m in range(nmod):
         for j in range(nbasis):
-            c = convolve(b[m], xcep[j, :], mode="full")
-            X[m, j, :] = c[int(nfir2) : int(nfir2 + nsamp)]  # Remove the transients
-            c = convolve(b[m], ycep[j, :], mode="full")
-            Y[m, j, :] = c[int(nfir2) : int(nfir2 + nsamp)]  # Remove the transients
+            c = convolve(b[m], reference_cep[j, :], mode="full")
+            reference[m, j, :] = c[
+                int(nfir2) : int(nfir2 + nsamp)
+            ]  # Remove the transients
+            c = convolve(b[m], processed_cep[j, :], mode="full")
+            processed[m, j, :] = c[
+                int(nfir2) : int(nfir2 + nsamp)
+            ]  # Remove the transients
 
     # Compute the cross-covariance matrix
-    CM = np.zeros((nmod, nbasis))
+    cross_covariance_matrix = np.zeros((nmod, nbasis))
     for m in range(nmod):
         for j in range(nbasis):
             #  Index j gives the input reference band
-            xj = X[m, j]  # Input freq band j, modulation freq m
-            xj = xj - np.mean(xj)
-            xsum = np.sum(xj**2)
+            x_j = reference[m, j]  # Input freq band j, modulation freq m
+            x_j = x_j - np.mean(x_j)
+            reference_sum = np.sum(x_j**2)
 
             # Processed signal band
-            yj = Y[m, j]  # Input freq band j, modulation freq m
-            yj = yj - np.mean(yj)
-            ysum = np.sum(yj**2)
+            y_j = processed[m, j]  # Input freq band j, modulation freq m
+            y_j = y_j - np.mean(y_j)
+            processed_sum = np.sum(y_j**2)
 
             # Cross-correlate the reference and processed signals
-            if (xsum < small) or (ysum < small):
-                CM[m, j] = 0
+            if (reference_sum < small) or (processed_sum < small):
+                cross_covariance_matrix[m, j] = 0
             else:
-                CM[m, j] = np.abs(np.sum(xj * yj)) / np.sqrt(xsum * ysum)
-    return CM
+                cross_covariance_matrix[m, j] = np.abs(np.sum(x_j * y_j)) / np.sqrt(
+                    reference_sum * processed_sum
+                )
+    return cross_covariance_matrix
 
 
-def spect_diff(xSL, ySL):
+def spectrum_diff(reference_sl, processed_sl):
     """
-    Function to compute changes in the long-term spectrum and spectral slope.
-    The metric is based on the spectral distortion metric of Moore and Tan
+    Compute changes in the long-term spectrum and spectral slope.
+
+    The metric is based on the spectral distortion metric of Moore and Tan[1]_
     (JAES, Vol 52, pp 900-914). The log envelopes in dB SL are converted to
     linear to approximate specific loudness. The outputs are the sum of the
     absolute differences, the standard deviation of the differences, and the
@@ -1350,80 +1674,102 @@ def spect_diff(xSL, ySL):
     Max diff: only weight the largest deviation
 
     Arguments:
-        xSL : reference signal spectrum in dB SL
-        ySL : degraded signal spectrum in dB SL
+        reference_sl (np.ndarray): reference signal spectrum in dB SL
+        processed_sl (np.ndarray): degraded signal spectrum in dB SL
 
     Returns:
         dloud (np.array) : [sum abs diff, std dev diff, max diff] spectra
         dnorm (np.array) : [sum abs diff, std dev diff, max diff] norm spectra
         dslope (np.array) : [sum abs diff, std dev diff, max diff] slope
 
-    James M. Kates, 28 June 2012.
+    References:
+    .. [1] Moore BCJ, Tan, CT (2004) Development and Validation of a Method
+           for Predicting the Perceived Naturalness of Sounds Subjected to
+           Spectral Distortion J Audio Eng Soc 52(9):900-914. Available at.
+           <http://www.aes.org/e-lib/browse.cfm?elib=13018>.
 
+    Updates:
+    James M. Kates, 28 June 2012.
     Translated from MATLAB to Python by Gerardo Roa Dabike, September 2022.
     """
 
     # Convert the dB SL to linear magnitude values. Because of the auditory
     # filter bank, the OHC compression, and auditory threshold, the linear
     # values are closely related to specific loudness.
-    nbands = xSL.shape[0]
-    x = 10 ** (xSL / 20)
-    y = 10 ** (ySL / 20)
+    nbands = reference_sl.shape[0]
+    reference_linear_magnitude = 10 ** (reference_sl / 20)
+    processed_linear_magnitude = 10 ** (processed_sl / 20)
 
     # Normalize the level of the reference and degraded signals to have the
     # same loudness. Thus overall level is ignored while differences in
     # spectral shape are measured.
-    xsum = np.sum(x)
-    x /= xsum  # Loudness sum = 1 (arbitrary amplitude, proportional to sones)
-    ysum = np.sum(y)
-    y /= ysum
+    reference_sum = np.sum(reference_linear_magnitude)
+    reference_linear_magnitude /= (
+        reference_sum  # Loudness sum = 1 (arbitrary amplitude, proportional to sones)
+    )
+    processed_sum = np.sum(processed_linear_magnitude)
+    processed_linear_magnitude /= processed_sum
 
     # Compute the spectrum difference
     dloud = np.zeros(3)
-    d = x - y  # Difference in specific loudness in each band
-    dloud[0] = np.sum(np.abs(d))
-    dloud[1] = nbands * np.std(d)  # Biased std: second moment
-    dloud[2] = np.max(np.abs(d))
+    diff_spectrum = (
+        reference_linear_magnitude - processed_linear_magnitude
+    )  # Difference in specific loudness in each band
+    dloud[0] = np.sum(np.abs(diff_spectrum))
+    dloud[1] = nbands * np.std(diff_spectrum)  # Biased std: second moment
+    dloud[2] = np.max(np.abs(diff_spectrum))
 
     # Compute the normalized spectrum difference
     dnorm = np.zeros(3)
-    d = (x - y) / (x + y)  # Relative difference in specific loudness
-    dnorm[0] = np.sum(np.abs(d))
-    dnorm[1] = nbands * np.std(d)
-    dnorm[2] = np.max(np.abs(d))
+    diff_normalised_spectrum = (
+        reference_linear_magnitude - processed_linear_magnitude
+    ) / (
+        reference_linear_magnitude + processed_linear_magnitude
+    )  # Relative difference in specific loudness
+    dnorm[0] = np.sum(np.abs(diff_normalised_spectrum))
+    dnorm[1] = nbands * np.std(diff_normalised_spectrum)
+    dnorm[2] = np.max(np.abs(diff_normalised_spectrum))
 
     # Compute the slope difference
     dslope = np.zeros(3)
-    dx = x[1:nbands] - x[0 : nbands - 1]
-    dy = y[1:nbands] - y[0 : nbands - 1]
-    d = dx - dy  # Slope difference
-    dslope[0] = np.sum(np.abs(d))
-    dslope[1] = nbands * np.std(d)
-    dslope[2] = np.max(np.abs(d))
+    reference_slope = (
+        reference_linear_magnitude[1:nbands]
+        - reference_linear_magnitude[0 : nbands - 1]
+    )
+    processed_slope = (
+        processed_linear_magnitude[1:nbands]
+        - processed_linear_magnitude[0 : nbands - 1]
+    )
+    diff_slope = reference_slope - processed_slope  # Slope difference
+    dslope[0] = np.sum(np.abs(diff_slope))
+    dslope[1] = nbands * np.std(diff_slope)
+    dslope[2] = np.max(np.abs(diff_slope))
 
     return dloud, dnorm, dslope
 
 
-def bm_covary(xBM, yBM, segsize, fsamp):
+def bm_covary(
+    reference_basilar_membrane, processed_basilar_membrane, segment_size, freq_sample
+):
     """
-    Function to compute the cross-covariance (normalized cross-correlation)
-    between the reference and processed signals in each auditory band. The
+    Compute the cross-covariance (normalized cross-correlation) between
+    the reference and processed signals in each auditory band. The
     signals are divided into segments having 50% overlap.
 
     Arguments:
-        xBM : BM movement, reference signal
-        yBM : BM movement, processed signal
-        segsize : signal segment size, msec
-        fsamp : sampling rate in Hz
+    reference_basilar_membrane (): Basilar Membrane movement, reference signal
+    processed_basilar_membrane (): Basilar Membrane movement, processed signal
+    segment_size (): signal segment size, msec
+    freq_sample (int): sampling rate in Hz
 
     Returns:
-        sigcov (np.array) : [nchan,nseg] of cross-covariance values
-        sigMSx (np.array) : [nchan,nseg] of MS input signal energy values
-        sigMSy (np.array) : [nchan,nseg] of MS processed signal energy values
+    signal_cross_covariance (np.array) : [nchan,nseg] of cross-covariance values
+    reference_mean_square (np.array) : [nchan,nseg] of MS input signal energy values
+    processed_mean_square (np.array) : [nchan,nseg] of MS processed signal energy values
 
+    Updates:
     James M. Kates, 28 August 2012.
     Output amplitude adjustment added, 30 october 2012.
-
     Translated from MATLAB to Python by Gerardo Roa Dabike, September 2022.
     """
 
@@ -1432,10 +1778,12 @@ def bm_covary(xBM, yBM, segsize, fsamp):
 
     # Lag for computing the cross-covariance
     lagsize = 1.0  # Lag (+/-) in msec
-    maxlag = np.around(lagsize * (0.001 * fsamp))  # Lag in samples
+    maxlag = np.around(lagsize * (0.001 * freq_sample))  # Lag in samples
 
     # Compute the segment window
-    nwin = int(np.around(segsize * (0.001 * fsamp)))  # Segment size in samples
+    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
@@ -1457,175 +1805,239 @@ def bm_covary(xBM, yBM, segsize, fsamp):
     halfsum2 = 1.0 / np.sum(halfwindow**2)  # MS sum normalization, first segment
 
     # Number of segments
-    nchan = xBM.shape[0]
-    npts = xBM.shape[1]
+    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))
-    sigMSx = np.zeros((nchan, nseg))
-    sigMSy = np.zeros((nchan, nseg))
-    sigcov = np.zeros((nchan, nseg))
+    reference_mean_square = np.zeros((nchan, nseg))
+    processed_mean_square = np.zeros((nchan, nseg))
+    signal_cross_covariance = np.zeros((nchan, nseg))
 
     # Loop to compute the signal mean-squared level in each band for each
     # segment and to compute the cross-corvariances.
     for k in range(nchan):
         # Extract the BM motion in the frequency band
-        x = xBM[k, :]
-        y = yBM[k, :]
+        x = reference_basilar_membrane[k, :]
+        y = processed_basilar_membrane[k, :]
 
         # The first (half) windowed segment
         nstart = 0
-        segx = x[nstart:nhalf] * halfwindow  # Window the reference
-        segy = y[nstart:nhalf] * halfwindow  # Window the processed signal
-        segx = segx - np.mean(segx)  # Make 0-mean
-        segy = segy - np.mean(segy)
-        MSx = np.sum(segx**2) * halfsum2  # Normalize signal MS value by the window
-        MSy = np.sum(segy**2) * halfsum2
-        c = correlate(segx, segy, "full")
-        c = c[int(len(segx) - 1 - maxlag) : int(maxlag + len(segx))]
-        Mxy = np.max(np.abs(c * halfcorr))  # Unbiased cross-correlation
-        if (MSx > small) and (MSy > small):
-            sigcov[k, 0] = Mxy / np.sqrt(MSx * MSy)  # Normalized cross-covariance
+        reference_seg = x[nstart:nhalf] * halfwindow  # Window the reference
+        processed_seg = y[nstart:nhalf] * halfwindow  # 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
+        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
+        if (ref_mean_square > small) and (proc_mean_squared > small):
+            signal_cross_covariance[k, 0] = unbiased_cross_correlation / np.sqrt(
+                ref_mean_square * proc_mean_squared
+            )  # Normalized cross-covariance
         else:
-            sigcov[k, 0] = 0.0
+            signal_cross_covariance[k, 0] = 0.0
 
-        sigMSx[k, 0] = MSx  # Save the reference MS level
-        sigMSy[k, 0] = MSy
+        # Save the reference MS level
+        reference_mean_square[k, 0] = ref_mean_square
+        processed_mean_square[k, 0] = proc_mean_squared
 
         # Loop over the remaining full segments, 50% overlap
         for n in range(1, nseg - 1):
             nstart = nstart + nhalf
             nstop = nstart + nwin
-            segx = x[nstart:nstop] * window  # Window the reference
-            segy = y[nstart:nstop] * window  # Window the processed signal
-            segx = segx - np.mean(segx)  # Make 0-mean
-            segy = segy - np.mean(segy)
-            MSx = np.sum(segx**2) * winsum2  # Normalize signal MS value by the window
-            MSy = np.sum(segy**2) * winsum2
-            c = correlate(segx, segy, "full")
-            c = c[int(len(segx) - 1 - maxlag) : int(maxlag + len(segx))]
-            Mxy = np.max(np.abs(c * wincorr))  # Unbiased cross-corr
-            if (MSx > small) and (MSy > small):
-                sigcov[k, n] = Mxy / np.sqrt(MSx * MSy)  # Normalized cross-covariance
+            reference_seg = x[nstart:nstop] * window  # Window the reference
+            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
+            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
+            if (ref_mean_square > small) and (proc_mean_squared > small):
+                signal_cross_covariance[k, n] = unbiased_cross_correlation / np.sqrt(
+                    ref_mean_square * proc_mean_squared
+                )  # Normalized cross-covariance
             else:
-                sigcov[k, n] = 0.0
+                signal_cross_covariance[k, n] = 0.0
 
-            sigMSx[k, n] = MSx  # Save the reference MS level
-            sigMSy[k, n] = MSy  # Save the reference MS level
+            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
 
         # The last (half) windowed segment
         nstart = nstart + nhalf
         nstop = nstart + nhalf
-        segx = x[nstart:nstop] * window[0:nhalf]  # Window the reference
-        segy = y[nstart:nstop] * window[0:nhalf]  # Window the processed signal
-        segx = segx - np.mean(segx)  # Make 0-mean
-        segy = segy - np.mean(segy)
-        MSx = np.sum(segx**2) * halfsum2  # Normalize signal MS value by the window
-        MSy = np.sum(segy**2) * halfsum2
-
-        c = np.correlate(segx, segy, "full")
-        c = c[int(len(segx) - 1 - maxlag) : int(maxlag + len(segx))]
-
-        Mxy = np.max(np.abs(c * halfcorr))  # Unbiased cross-correlation
-        if (MSx > small) and (MSy > small):
-            sigcov[k, nseg - 1] = Mxy / np.sqrt(
-                MSx * MSy
+        reference_seg = x[nstart:nstop] * window[0:nhalf]  # Window the reference
+        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
+        proc_mean_squared = np.sum(processed_seg**2) * halfsum2
+
+        correlation = np.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
+        if (ref_mean_square > small) and (proc_mean_squared > small):
+            signal_cross_covariance[k, nseg - 1] = unbiased_cross_correlation / np.sqrt(
+                ref_mean_square * proc_mean_squared
             )  # Normalized cross-covariance
         else:
-            sigcov[k, nseg - 1] = 0.0
+            signal_cross_covariance[k, nseg - 1] = 0.0
 
-        sigMSx[k, nseg - 1] = MSx  # Save the reference MS level
-        sigMSy[k, nseg - 1] = MSy  # Save the reference MS level
+        # Save the referenceand processed MS level
+        reference_mean_square[k, nseg - 1] = ref_mean_square
+        processed_mean_square[k, nseg - 1] = proc_mean_squared
 
     # Limit the cross-covariance to lie between 0 and 1
-    sigcov = np.clip(sigcov, 0, 1)
+    signal_cross_covariance = np.clip(signal_cross_covariance, 0, 1)
 
     # Adjust the BM magnitude to correspond to the envelope in dB SL
-    sigMSx = 2.0 * sigMSx
-    sigMSy = 2.0 * sigMSy
+    reference_mean_square *= 2.0
+    processed_mean_square *= 2.0
 
-    return sigcov, sigMSx, sigMSy
+    return signal_cross_covariance, reference_mean_square, processed_mean_square
 
 
-def ave_covary2(sigcov, sigMSx, thr):
+def ave_covary2(
+    signal_cross_covariance: np.ndarray,
+    reference_signal_mean_square: np.ndarray,
+    threshold_db,
+    lp_filter_order=None,
+    freq_cutoff=None,
+):
     """
-    Function to compute the average cross-covariance between the reference
-    and processed signals in each auditory band. The silent time-frequency
-    tiles are removed from consideration. The cross-covariance is computed
-    for each segment in each frequency band. The values are weighted by 1
-    for inclusion or 0 if the tile is below threshold. The sum of the
-    covariance values across time and frequency are then divided by the total
-    number of tiles above thresold. The calculation is a modification of
-    Tan et al. (JAES 2004). The cross-covariance is also output with a
-    frequency weighting that reflects the loss of IHC synchronization at high
-    frequencies (Johnson, JASA 1980).
+    Compute the average cross-covariance between the reference and processed
+    signals in each auditory band.
+
+    The silent time-frequency tiles are removed from consideration. The
+    cross-covariance is computed for each segment in each frequency band. The
+    values are weighted by 1 for inclusion or 0 if the tile is below
+    threshold. The sum of the covariance values across time and frequency are
+    then divided by the total number of tiles above thresold. The calculation
+    is a modification of Tan et al.[1]_ . The cross-covariance is also output
+    with a frequency weighting that reflects the loss of IHC synchronization at high
+    frequencies Johnson[2]_.
 
     Arguments:
-        sigcov (np.array) : [nchan,nseg] of cross-covariance values
-        sigMSx (np.array) : [nchan,nseg] of reference signal MS values
-        thr : threshold in dB SL to include segment ave over freq in average
+    signal_cross_covariance (np.array): [nchan,nseg] of cross-covariance values
+    reference_signal_mean_square (np.array): [nchan,nseg] of reference signal MS
+        values
+    threshold_db (): threshold in dB SL to include segment ave over freq in
+        average
+    lp_filter (list): LP filter order
+    freq_cutoff (list): Cutoff frequencies in Hz
 
     Returns:
-        avecov : cross-covariance in segments averaged over time and frequency
-        syncov : cross-coraviance array, 6 different weightings for loss of
+    average_covariance (): cross-covariance in segments averaged over time and
+        frequency
+    ihc_sync_covariance (): cross-coraviance array, 6 different weightings for loss of
               IHC synchronization at high frequencies:
               LP Filter Order     Cutoff Freq, kHz
                 1              1.5
                 3              2.0
                 5              2.5, 3.0, 3.5, 4.0
 
+    References:
+
+    .. [1] Tan CT, Moore, BCJ, Zacharov N, Mattila VV (2004) Predicting the Perceived
+           Quality of Nonlinearly Distorted Music and Speech Signals. J Audio Eng Soc
+           52(9):900-914. Available at.
+           <http://www.aes.org/e-lib/browse.cfm?elib=13013>.
+
+    .. [2] Johnson DH (1980) The relationship between spike rate and synchrony in
+           responses of auditory‐nerve fibers to single tones J Acoustocal Soc of Am
+           68:1115 Available at.
+           <https://doi.org/10.1121/1.384982>
+    Updates:
     James M. Kates, 28 August 2012.
     Adjusted for BM vibration in dB SL, 30 October 2012.
     Threshold for including time-freq tile modified, 30 January 2013.
     Version for different sync loss, 15 February 2013.
-
     Translated from MATLAB to Python by Gerardo Roa Dabike, September 2022.
     """
 
     # Array dimensions
-    nchan = sigcov.shape[0]
+    n_channels = signal_cross_covariance.shape[0]
 
     # Initialize the LP filter for loss of IHC synchronization
-    cfreq = CenterFreq(nchan)  # Center frequencies in Hz on an ERB scale
-    p = np.array([1, 3, 5, 5, 5, 5])  # LP filter order
-    fcut = 1000 * np.array([1.5, 2.0, 2.5, 3.0, 3.5, 4.0])  # Cutoff frequencies in Hz
-    fsync = np.zeros((6, nchan))  # Array of filter freq resp vs band center freq
+    # Center frequencies in Hz on an ERB scale
+    _center_freq = center_frequency(n_channels)
+    # Default LP filter order
+    if lp_filter_order is None:
+        lp_filter_order = np.array([1, 3, 5, 5, 5, 5])
+    # Default cutoff frequencies in Hz
+    if freq_cutoff is None:
+        freq_cutoff = 1000 * np.array([1.5, 2.0, 2.5, 3.0, 3.5, 4.0])
+    fsync = np.zeros((6, n_channels))  # Array of filter freq resp vs band center freq
     for n in range(6):
-        fc2p = fcut[n] ** (2 * p[n])
-        freq2p = cfreq ** (2 * p[n])
+        fc2p = freq_cutoff[n] ** (2 * lp_filter_order[n])
+        freq2p = _center_freq ** (2 * lp_filter_order[n])
         fsync[n, :] = np.sqrt(fc2p / (fc2p + freq2p))
 
     # Find the segments that lie sufficiently above the threshold.
-    sigRMS = np.sqrt(sigMSx)  # Convert squared amplitude to dB envelope
-    sigLinear = 10 ** (sigRMS / 20)  # Linear amplitude (specific loudness)
-    xsum = np.sum(sigLinear, 0) / nchan  # Intensity averaged over frequency bands
-    xsum = 20 * np.log10(xsum)  # Convert back to dB (loudness in phons)
-    index = np.argwhere(
-        xsum > thr
-    ).T.squeeze()  # Identify those segments above threshold
+    # Convert squared amplitude to dB envelope
+    signal_rms = np.sqrt(reference_signal_mean_square)
+    # Linear amplitude (specific loudness)
+    signal_linear_amplitude = 10 ** (signal_rms / 20)
+    # Intensity averaged over frequency bands
+    reference_mean = np.sum(signal_linear_amplitude, 0) / n_channels
+    # Convert back to dB (loudness in phons)
+    reference_mean = 20 * np.log10(reference_mean)
+    # Identify those segments above threshold
+    index = np.argwhere(reference_mean > threshold_db).T
+    if index.size != 1:
+        index = index.squeeze()
     nseg = index.shape[0]  # Number of segments above threshold
 
     # Exit if not enough segments above zero
     if nseg <= 1:
         print("Function eb.AveCovary: Ave signal below threshold, outputs set to 0.")
-        avecov = 0
-        syncov = 0
-        return avecov, syncov
+        average_covariance = 0
+        # syncov = 0
+        ihc_sync_covariance = [0] * 6
+        return average_covariance, ihc_sync_covariance
 
     # Remove the silent segments
-    sigcov = sigcov[:, index]
-    sigRMS = sigRMS[:, index]
+    signal_cross_covariance = signal_cross_covariance[:, index]
+    signal_rms = signal_rms[:, index]
 
     # 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((nchan, nseg))  # No IHC synchronization roll-off
-    wsync1 = np.zeros((nchan, nseg))  # Loss of IHC synchronization at high frequencies
-    wsync2 = np.zeros((nchan, nseg))
-    wsync3 = np.zeros((nchan, nseg))
-    wsync4 = np.zeros((nchan, nseg))
-    wsync5 = np.zeros((nchan, nseg))
-    wsync6 = np.zeros((nchan, nseg))
-    for k in range(nchan):
+    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 sigRMS[k, n] > thr:  # Thresh in dB SL for including time-freq tile
+            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]
@@ -1635,29 +2047,33 @@ def ave_covary2(sigcov, sigMSx, thr):
                 wsync6[k, n] = fsync[5, k]
 
     # Sum the weighted covariance values
-    csum = np.sum(np.sum(weight * sigcov))  # Sum of weighted time-freq tiles
+    csum = np.sum(
+        np.sum(weight * signal_cross_covariance)
+    )  # Sum of weighted time-freq tiles
     wsum = np.sum(np.sum(weight))  # Total number of tiles above thresold
-    fsum = np.zeros(6)
-    ssum = np.zeros(6)
-    fsum[0] = np.sum(np.sum(wsync1 * sigcov))  # Sum of weighted time-freq tiles
-    ssum[0] = np.sum(np.sum(wsync1))  # Total number of tiles above thresold
-    fsum[1] = np.sum(np.sum(wsync2 * sigcov))  # Sum of weighted time-freq tiles
-    ssum[1] = np.sum(np.sum(wsync2))  # Total number of tiles above thresold
-    fsum[2] = np.sum(np.sum(wsync3 * sigcov))  # Sum of weighted time-freq tiles
-    ssum[2] = np.sum(np.sum(wsync3))  # Total number of tiles above thresold
-    fsum[3] = np.sum(np.sum(wsync4 * sigcov))  # Sum of weighted time-freq tiles
-    ssum[3] = np.sum(np.sum(wsync4))  # Total number of tiles above thresold
-    fsum[4] = np.sum(np.sum(wsync5 * sigcov))  # Sum of weighted time-freq tiles
-    ssum[4] = np.sum(np.sum(wsync5))  # Total number of tiles above thresold
-    fsum[5] = np.sum(np.sum(wsync6 * sigcov))  # Sum of weighted time-freq tiles
-    ssum[5] = np.sum(np.sum(wsync6))  # 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))
 
     # Exit if not enough segments above zero
     if wsum < 1:
-        avecov = 0
+        average_covariance = 0
         print("Function eb.AveCovary: Signal tiles below threshold, outputs set to 0.")
     else:
-        avecov = csum / wsum
-    syncov = fsum / ssum
+        average_covariance = csum / wsum
+    ihc_sync_covariance = sum_weighted_time_freq / tiles_above_threshold
 
-    return avecov, syncov
+    return average_covariance, ihc_sync_covariance
diff --git a/clarity/evaluator/haspi/ebm.py b/clarity/evaluator/haspi/ebm.py
index 9a81015ea..ebe912c59 100644
--- a/clarity/evaluator/haspi/ebm.py
+++ b/clarity/evaluator/haspi/ebm.py
@@ -1,49 +1,55 @@
+"""HASPI EBM module"""
 from math import floor
 
 import numpy as np
 from scipy.signal import convolve, convolve2d
 
 
-def EnvFilt(xdB, ydB, fcut, fsub, fsamp):
+def env_filter(reference_db, processed_db, filter_cutoff, freq_sub_sample, freq_samp):
     """
-    Function to lowpass filter and subsample the envelope in dB SL produced
-    by the model of the auditory periphery. The LP filter uses a von Hann
-    raised cosine window to ensure that there are no negative envelope values
-    produced by the filtering operation.
+    Lowpass filter and subsample the envelope in dB SL produced by the model
+    of the auditory periphery. The LP filter uses a von Hann raised cosine
+    window to ensure that there are no negative envelope values produced by
+    the filtering operation.
 
     Args:
-        xdB (np.ndarray): env in dB SL for the ref signal in each auditory band
-        ydB (np.ndarray): env in dB SL for the degraded signal in each auditory band
-        fcut ():  LP filter cutoff frequency for the filtered envelope, Hz
-        fsub ():  subsampling frequency in Hz for the LP filtered envelopes
-        fsamp ():  sampling rate in Hz for the signals xdB and ydB
+    reference_db (np.ndarray): env in dB SL for the ref signal in each auditory band
+    processed_db (np.ndarray): env in dB SL for the degraded signal in each auditory band
+    filter_cutoff ():  LP filter cutoff frequency for the filtered envelope, Hz
+    freq_sub_samp ():  subsampling frequency in Hz for the LP filtered envelopes
+    freq_samp ():  sampling rate in Hz for the signals xdB and ydB
 
     Returns:
-        tuple: xLP - LP filtered and subsampled reference signal envelope
-           Each frequency band is a separate column. yLP - LP filtered and subsampled
-           degraded signal envelope
+    tuple: reference_env - LP filtered and subsampled reference signal envelope
+           Each frequency band is a separate column.
+           processed_env - LP filtered and subsampled degraded signal envelope
 
 
+    Updates:
     James M. Kates, 12 September 2019.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Check the filter design parameters
-    assert fsub <= fsamp, "Error in ebm.EnvFilt: Subsampling rate too high."
-    assert fcut <= 0.5 * fsub, "Error in ebm.EnvFilt: LP cutoff frequency too high."
+    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."
 
     # Check the data matrix orientation
     # Require each frequency band to be a separate column
-    nrow = xdB.shape[0]  # number of rows
-    ncol = xdB.shape[1]  # number of columnts
+    nrow = reference_db.shape[0]  # number of rows
+    ncol = reference_db.shape[1]  # number of columnts
     if ncol > nrow:
-        xdB = xdB.T
-        ydB = ydB.T
-    nsamp = xdB.shape[0]
+        reference_db = reference_db.T
+        processed_db = processed_db.T
+    nsamp = reference_db.shape[0]
 
     # Compute the lowpass filter length in samples to give -3 dB at fcut Hz
-    tfilt = 1000 * (1 / fcut)  # filter length in ms
+    tfilt = 1000 * (1 / filter_cutoff)  # filter length in ms
     tfilt = 0.7 * tfilt  # Empirical adjustment to the filter length
-    nfilt = round(0.001 * tfilt * fsamp)  # Filter length in samples
+    nfilt = round(0.001 * tfilt * freq_samp)  # Filter length in samples
     nhalf = floor(nfilt / 2)
     nfilt = int(2 * nhalf)  # Force an even filter length
 
@@ -51,293 +57,335 @@ def EnvFilt(xdB, ydB, fcut, fsub, fsamp):
     # envelope values. The MATLAB code uses the hanning() function, which returns the Hann
     # window without the first and last zero-weighted window samples,
     # unlike np.hann and scipy.signal.windows.hann; the code below replicates this behaviour
-    w = 0.5 * (1 - np.cos(2 * np.pi * np.arange(1, nfilt / 2 + 1) / (nfilt + 1)))
-    benv = np.concatenate((w, np.flip(w)))
+    window = 0.5 * (1 - np.cos(2 * np.pi * np.arange(1, nfilt / 2 + 1) / (nfilt + 1)))
+    benv = np.concatenate((window, np.flip(window)))
     benv = benv / np.sum(benv)
 
     # LP filter for the envelopes at fsamp
-    xenv = convolve2d(xdB, np.expand_dims(benv, 1), "full")  # 2D convolution
-    xenv = xenv[nhalf : nhalf + nsamp, :]
-    yenv = convolve2d(ydB, np.expand_dims(benv, 1), "full")
-    yenv = yenv[nhalf : nhalf + nsamp, :]
+    reference_env = convolve2d(reference_db, np.expand_dims(benv, 1), "full")
+    reference_env = reference_env[nhalf : nhalf + nsamp, :]
+    processed_env = convolve2d(processed_db, np.expand_dims(benv, 1), "full")
+    processed_env = processed_env[nhalf : nhalf + nsamp, :]
 
     # Subsample the LP filtered envelopes
-    space = floor(fsamp / fsub)
+    space = floor(freq_samp / freq_sub_sample)
     index = np.arange(0, nsamp, space)
-    xLP = xenv[index, :]
-    yLP = yenv[index, :]
 
-    return xLP, yLP
+    return reference_env[index, :], processed_env[index, :]
 
 
-def CepCoef(xdB, ydB, thrCep, thrNerve, nbasis):
+def cepstral_correlation_coef(
+    reference_db, processed_db, thresh_cep, thresh_nerve, nbasis
+):
     """
-    Function to compute the cepstral correlation coefficients between the
-    reference signal and the distorted signal log envelopes. The silence
-    portions of the signals are removed prior to the calculation based on the
-    envelope of the reference signal. For each time sample, the log spectrum
-    in dB SL is fitted with a set of half-cosine basis functions. The
-    cepstral coefficients then form the input to the cepstral correlation
+    Compute the cepstral correlation coefficients between the reference signal
+    and the distorted signal log envelopes. The silence portions of the
+    signals are removed prior to the calculation based on the envelope of the
+    reference signal. For each time sample, the log spectrum in dB SL is
+    fitted with a set of half-cosine basis functions. The cepstral coefficients
+    then form the input to the cepstral correlation
     calculation.
 
     Args:
-        xdB ():      subsampled reference signal envelope in dB SL in each band
-        ydB ():	    subsampled distorted output signal envelope
-        thrCep ():   threshold in dB SPL to include sample in calculation
-        thrNerve (): additive noise RMS for IHC firing (in dB)
-        nbasis    number of cepstral basis functions to use
+    reference_db ():      subsampled reference signal envelope in dB SL in each band
+    processed_db ():	    subsampled distorted output signal envelope
+    thresh_cep ():   threshold in dB SPL to include sample in calculation
+    thresh_nerve (): additive noise RMS for IHC firing (in dB)
+    nbasis    number of cepstral basis functions to use
 
     Returns:
-        tuple: xcep cepstral coefficient matrix for the ref signal (nsamp,nbasis) and ycep  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.
     Gammawarp version to fit the basis functions, 11 February 2019.
     Additive noise for IHC firing rates, 24 April 2019.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Processing parameters
-    nbands = xdB.shape[1]
+    nbands = reference_db.shape[1]
 
     # Mel cepstrum basis functions
     freq = np.arange(0, nbasis)
     k = np.arange(0, nbands)
     cepm = np.zeros((nbands, nbasis))
 
-    for nb in range(nbasis):
-        basis = np.cos(freq[nb] * np.pi * k / (nbands - 1))
-        cepm[:, nb] = basis / np.sqrt(np.sum(basis**2))
+    for n in range(nbasis):
+        basis = np.cos(freq[n] * np.pi * k / (nbands - 1))
+        cepm[:, n] = basis / np.sqrt(np.sum(basis**2))
 
     # Find the reference segments that lie sufficiently above the quiescent rate
-    xLinear = 10 ** (xdB / 20)  # Convert envelope dB to linear (specific loudness)
-    xsum = np.sum(xLinear, 1) / nbands  # Proportional to loudness in sones
+    x_linear = 10 ** (
+        reference_db / 20
+    )  # Convert envelope dB to linear (specific loudness)
+    xsum = np.sum(x_linear, 1) / nbands  # Proportional to loudness in sones
     xsum = 20 * np.log10(xsum)  # Convert back to dB (loudness in phons)
-    index = np.where(xsum > thrCep)[0]  # Identify those segments above threshold
+    index = np.where(xsum > thresh_cep)[0]  # Identify those segments above threshold
     nsamp = len(index)  # Number of segments above threshold
 
     # Exit if not enough segments above zero
     assert nsamp > 1, "Function ebm_CepCoef: Signal below threshold"
 
     # Remove the silent samples
-    xdB = xdB[index, :]
-    ydB = ydB[index, :]
+    reference_db = reference_db[index, :]
+    processed_db = processed_db[index, :]
 
     # Add low-level noise to provide IHC firing jitter
-    xdB = AddNoise(xdB, thrNerve)
-    ydB = AddNoise(ydB, thrNerve)
+    reference_db = add_noise(reference_db, thresh_nerve)
+    processed_db = add_noise(processed_db, thresh_nerve)
 
     # Compute the mel cepstrum coefficients using only those samples above threshold
-    xcep = xdB @ cepm
-    ycep = ydB @ cepm
+    reference_cep = reference_db @ cepm
+    processed_cep = processed_db @ cepm
 
     # Remove the average value from the cepstral coefficients. The cepstral cross-correlation
     # will thus be a cross-covariance, and there is no effect of the absolute signal level in dB.
     for n in range(nbasis):
-        x = xcep[:, n]
+        x = reference_cep[:, n]
         x = x - np.mean(x)
-        xcep[:, n] = x
-        y = ycep[:, n]
+        reference_cep[:, n] = x
+        y = processed_cep[:, n]
         y = y - np.mean(y)
-        ycep[:, n] = y
+        processed_cep[:, n] = y
 
-    return xcep, ycep
+    return reference_cep, processed_cep
 
 
-def AddNoise(ydB, thrdB):
+def add_noise(reference_db, thresh_db):
     """
-    Function to add independent random Gaussian noise to the subsampled
-    signal envelope in each auditory frequency band.
+    Add independent random Gaussian noise to the subsampled signal envelope
+    in each auditory frequency band.
 
     Args:
-    ydB      subsampled envelope in dB re:auditory threshold
-    thrdB    additive noise RMS level (in dB)
-    Level1   an input having RMS=1 corresponds to Level1 dB SPL
+    reference_db (): subsampled envelope in dB re:auditory threshold
+    thresh_db (): additive noise RMS level (in dB)
 
     Returns:
-    zdB      envelope with threshold noise added, in dB re:auditory threshold
+      () envelope with threshold noise added, in dB re:auditory threshold
 
+    Updates
     James M. Kates, 23 April 2019.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Additive noise sequence
-    # rng = np.random.default_rng()
-    noise = thrdB * np.random.standard_normal(
-        ydB.shape
-    )  # Gaussian noise with RMS=1, then scaled
+    # Gaussian noise with RMS=1, then scaled
+    noise = thresh_db * np.random.standard_normal(reference_db.shape)
 
     # Add the noise to the signal envelope
-    zdB = ydB + noise
-    return zdB
+    return reference_db + noise
 
 
-def ModFilt(Xenv, Yenv, fsub):
+def fir_modulation_filter(
+    reference_envelope,
+    processed_envelope,
+    freq_sub_sampling,
+    center_frequencies=None,
+):
     """
-    Function to apply an FIR modulation filterbank to the reference envelope
-    signals contained in matrix Xenv and the processed signal envelope
-    signals in matrix Yenv. Each column in Xenv and Yenv is a separate filter
-    band or cepstral coefficient basis function. The modulation filters use a
-    lowpass filter for the lowest modulation rate, and complex demodulation
-    followed by a lowpass filter for the remaining bands. The onset and
-    offset transients are removed from the FIR convolutions to temporally
-    align the modulation filter outputs.
+    Apply a FIR modulation filterbank to the reference envelope signals
+    contained in matrix reference_envelope and the processed signal envelope
+    signals in matrix processed_envelope. Each column in reference_envelope
+    and processed_envelope is a separate filter band or cepstral coefficient
+    basis function. The modulation filters use a lowpass filter for the
+    lowest modulation rate, and complex demodulation followed by a lowpass
+    filter for the remaining bands. The onset and offset transients are
+    removed from the FIR convolutions to temporally align the modulation
+    filter outputs.
 
     Args:
-        Xenv (np.ndarray) : matrix containing the subsampled reference envelope values. Each
+    reference_envelope (np.ndarray) : matrix containing the subsampled reference envelope values. Each
              column is a different frequency band or cepstral basis function
              arranged from low to high.
-        Yenv (np.ndarray): matrix containing the subsampled processed envelope values
-        fsub (): envelope sub-sampling rate in Hz
+    processed_envelope (np.ndarray): matrix containing the subsampled processed envelope values
+    freq_sub_sampling (): envelope sub-sampling rate in Hz
+    center_frequencies (np.ndarray): Center Frequencies
 
     Returns:
-        tuple: Xmod a cell array containing the reference signal output of the
-             modulation filterbank. Xmod is of size {nchan,nmodfilt} where
-             nchan is the number of frequency channels or cepstral basis
-             functions in Xenv, and nmodfilt is the number of modulation
-             filters used in the analysis. Each cell contains a column vector
-             of length nsamp, where nsamp is the number of samples in each
-             envelope sequence contained in the columns of Xenv.
-             Ymod cell array containing the processed signal output of the
-             modulation filterbank.
-             cf vector of the modulation rate filter center frequencies
-
+    tuple:  reference_modulation ():  a cell array containing the
+                reference signal output of the modulation filterbank.
+                reference_modulation is of size {nchan,nmodfilt} where
+                nchan is the number of frequency channels or cepstral
+                basis functions in reference_envelope, and nmodfilt is
+                the number of modulation filters used in the analysis. Each cell contains a column vector
+                of length nsamp, where nsamp is the number of samples in each
+                envelope sequence contained in the columns of reference_envelope.
+            processed_modulation (): cell array containing the processed signal output of the
+                modulation filterbank.
+            center_frequencies (): vector of the modulation rate filter center frequencies
+
+    Updates:
     James M. Kates, 14 February 2019.
     Two matrix version of gwarp_ModFiltWindow, 19 February 2019.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
 
     # Input signal properties
-    nsamp = Xenv.shape[0]
-    nchan = Xenv.shape[1]
+    nsamp = reference_envelope.shape[0]
+    nchan = reference_envelope.shape[1]
 
     # Modulation filter band cf and edges, 10 bands
     # Band spacing uses the factor of 1.6 used by Dau
-    cf = np.array([2, 6, 10, 16, 25, 40, 64, 100, 160, 256])  # Band center frequencies
-    nmod = len(cf)
+    if center_frequencies is None:
+        center_frequencies = np.array(
+            [2, 6, 10, 16, 25, 40, 64, 100, 160, 256]
+        )  # Band center frequencies
+    nmod = len(center_frequencies)
     edge = np.zeros(nmod + 1)
     edge[0:3] = [0, 4, 8]
     for k in range(3, nmod + 1):
-        edge[k] = (cf[k - 1] ** 2) / edge[k - 1]
+        edge[k] = (center_frequencies[k - 1] ** 2) / edge[k - 1]
 
     # Allowable filters based on envelope subsampling rate
-    fNyq = 0.5 * fsub
-    index = edge < fNyq
+    fn_yq = 0.5 * freq_sub_sampling
+    index = edge < fn_yq
     edge = edge[index]  # Filter upper bands edges less than Nyquist rate
     nmod = len(edge) - 1
-    cf = cf[:nmod]
+    center_frequencies = center_frequencies[:nmod]
 
     # Assign FIR filter lengths. Setting t0=0.2 gives a filter Q of about 1.25
     # to match Ewert et al. (2002), and t0=0.33 corresponds to Q=2 (Dau et al 1997a).
     # Moritz et al. (2015) used t0=0.29. General relation Q=6.25*t0, compromise with
     # t0=0.24 which gives Q=1.5
 
-    t0 = 0.24  # Filter length in seconds for the lowest modulation frequency band
-    t = np.zeros(nmod)
-    t[0] = t0
-    t[1] = t0
-    t[2:nmod] = t0 * cf[2] / cf[2:nmod]  # Constant-Q filters above 10 Hz
-    nfir = 2 * np.floor(t * fsub / 2)  # Force even filter lengths in samples
+    t_0 = 0.24  # Filter length in seconds for the lowest modulation frequency band
+    t = np.zeros(nmod)  # pylint: disable=invalid-name
+    t[0] = t_0
+    t[1] = t_0
+    t[2:nmod] = (
+        t_0 * center_frequencies[2] / center_frequencies[2:nmod]
+    )  # Constant-Q filters above 10 Hz
+    nfir = 2 * np.floor(
+        t * freq_sub_sampling / 2
+    )  # Force even filter lengths in samples
     nhalf = nfir / 2
 
     # Design the family of lowpass windows
-    b = []  # Filter coefficients, one filter impulse response per list element
+    filter_coefficients = []
     for k in range(nmod):
-        bk = np.hanning(nfir[k] + 1)
-        bk = bk / np.sum(bk)
-        b.append(bk)
+        coefficient = np.hanning(nfir[k] + 1)
+        coefficient /= np.sum(coefficient)
+        filter_coefficients.append(coefficient)
 
     # Pre-compute the cosine and sine arrays
-    co = []  # cosine array, one frequency per list element
-    si = []  # sine array, one frequency per list element
+    cosine = []  # cosine array, one frequency per list element
+    sine = []  # sine array, one frequency per list element
     n = np.arange(1, nsamp + 1)
     for k in range(nmod):
         if k == 0:
-            co.append(1)
-            si.append(0)
+            cosine.append(1)
+            sine.append(0)
         else:
-            co.append(np.sqrt(2) * np.cos(np.pi * n * cf[k] / fNyq))
-            si.append(np.sqrt(2) * np.sin(np.pi * n * cf[k] / fNyq))
+            cosine.append(
+                np.sqrt(2) * np.cos(np.pi * n * center_frequencies[k] / fn_yq)
+            )
+            sine.append(np.sqrt(2) * np.sin(np.pi * n * center_frequencies[k] / fn_yq))
 
     # Convolve the input and output envelopes with the modulation filters
-    Xmod = np.zeros((nchan, nmod, nsamp))
-    Ymod = np.zeros((nchan, nmod, nsamp))
+    reference_modulation = np.zeros((nchan, nmod, nsamp))
+    processed_modulation = np.zeros((nchan, nmod, nsamp))
     for k in range(nmod):  # Loop over the modulation filters
-        bk = b[k]  # Extract the lowpass filter impulse response
-        nh = int(nhalf[k])  # Transient duration for the filter
-        c = co[k]  # Cosine and sine for complex modulation
-        s = si[k]
+        coefficient = filter_coefficients[k]
+        transient_duration = int(nhalf[k])  # Transient duration for the filter
+        _cosine = cosine[k]  # Cosine and sine for complex modulation
+        _sine = sine[k]
         for m in range(nchan):  # Loop over the input signal vectors
             # Reference signal
-            x = Xenv[:, m]  # Extract the frequency or cepstral coefficient band
-            u = convolve(
-                (x * c - 1j * x * s), bk
-            )  # Complex demodulation, then LP filter
-            u = u[nh : nh + nsamp]  # Truncate the filter transients
+            # Extract the frequency or cepstral coefficient band
+            reference_cepstral_coef = reference_envelope[:, m]
+            # Complex demodulation, then LP filter
+            reference_complex_demodulation = convolve(
+                (
+                    reference_cepstral_coef * _cosine
+                    - 1j * reference_cepstral_coef * _sine
+                ),
+                coefficient,
+            )
+            # Truncate the filter transients
+            reference_complex_demodulation = reference_complex_demodulation[
+                transient_duration : transient_duration + nsamp
+            ]
+            # Modulate back up to the carrier freq
             xfilt = (
-                np.real(u) * c - np.imag(u) * s
-            )  # Modulate back up to the carrier freq
-            Xmod[m, k, :] = xfilt  # Save the filtered signal
+                np.real(reference_complex_demodulation) * _cosine
+                - np.imag(reference_complex_demodulation) * _sine
+            )
+            # Save the filtered signal
+            reference_modulation[m, k, :] = xfilt
 
             # Processed signal
-            y = Yenv[:, m]  # Extract the frequency or cepstral coefficient band
-            v = convolve(
-                (y * c - 1j * y * s), bk
-            )  # Complex demodulation, then LP filter
-            v = v[nh : nh + nsamp]  # Truncate the filter transients
+            # Extract the frequency or cepstral coefficient band
+            processed_cepstral_coef = processed_envelope[:, m]
+            # Complex demodulation, then LP filter
+            processed_complex_demodulation = convolve(
+                (
+                    processed_cepstral_coef * _cosine
+                    - 1j * processed_cepstral_coef * _sine
+                ),
+                coefficient,
+            )
+            # Truncate the filter transients
+            processed_complex_demodulation = processed_complex_demodulation[
+                transient_duration : transient_duration + nsamp
+            ]
+            # Modulate back up to the carrier freq
             yfilt = (
-                np.real(v) * c - np.imag(v) * s
-            )  # Modulate back up to the carrier freq
-            Ymod[m, k, :] = yfilt  # Save the filtered signal
+                np.real(processed_complex_demodulation) * _cosine
+                - np.imag(processed_complex_demodulation) * _sine
+            )
+            processed_modulation[m, k, :] = yfilt  # Save the filtered signal
 
-    return Xmod, Ymod, cf
+    return reference_modulation, processed_modulation, center_frequencies
 
 
-def ModCorr(Xmod, Ymod):
+def modulation_cross_correlation(reference_modulation, processed_modulation):
     """
-    Function to compute the cross-correlations between the input signal
-    time-frequency envelope and the distortion time-frequency envelope. The
-    cepstral coefficients or envelopes in each frequency band have been
-    passed through the modulation filterbank using function ebm_ModFilt.
-
-    Args:
-        Xmod (np.array): cell array containing the reference signal output of the
-             modulation filterbank. Xmod is of size {nchan,nmodfilt} where
-             nchan is the number of frequency channels or cepstral basis
-             functions in Xenv, and nmodfilt is the number of modulation
-             filters used in the analysis. Each cell contains a column vector
-             of length nsamp, where nsamp is the number of samples in each
-             envelope sequence contained in the columns of Xenv.
-        Ymod subsampled distorted output signal envelope
-
-    Output:
-        float: aveCM modulation correlations averaged over basis functions 2-6
-             vector of size nmodfilt
-
-    James M. Kates, 21 February 2019.
-    Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
+     Compute the cross-correlations between the input signal time-frequency
+     envelope and the distortion time-frequency envelope. The cepstral
+     coefficients or envelopes in each frequency band have been passed
+     through the modulation filterbank using function ebm_ModFilt.
+
+     Args:
+     reference_modulation (np.array): cell array containing the reference signal output of the
+              modulation filterbank. Xmod is of size {nchan,nmodfilt} where
+              nchan is the number of frequency channels or cepstral basis
+              functions in Xenv, and nmodfilt is the number of modulation
+              filters used in the analysis. Each cell contains a column vector
+              of length nsamp, where nsamp is the number of samples in each
+              envelope sequence contained in the columns of Xenv.
+    processed_modulation (np.ndarray): subsampled distorted output signal envelope
+
+     Output:
+         float: aveCM modulation correlations averaged over basis functions 2-6
+              vector of size nmodfilt
+
+     Updates:
+     James M. Kates, 21 February 2019.
+     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Processing parameters
-    nchan = Xmod.shape[0]  # Number of basis functions
-    nmod = Xmod.shape[1]  # Number of modulation filters
+    nchan = reference_modulation.shape[0]  # Number of basis functions
+    nmod = reference_modulation.shape[1]  # Number of modulation filters
     small = 1e-30  # Zero threshold
 
     # Compute the cross-covariance matrix
-    CM = np.zeros((nchan, nmod))
+    covariance_matrix = np.zeros((nchan, nmod))
     for m in range(nmod):
         for j in range(nchan):
             # Index j gives the input reference band
-            xj = Xmod[j, m]  # Input freq band j, modulation freq m
-            xj = xj - np.mean(xj)
-            xsum = np.sum(xj**2)
+            x_j = reference_modulation[j, m]  # Input freq band j, modulation freq m
+            x_j -= np.mean(x_j)
+            xsum = np.sum(x_j**2)
             # Processed signal band
-            yj = Ymod[j, m]  # Processed freq band j, modulation freq m
-            yj = yj - np.mean(yj)
-            ysum = np.sum(yj**2)
+            y_j = processed_modulation[j, m]  # Processed freq band j, modulation freq m
+            y_j -= np.mean(y_j)
+            ysum = np.sum(y_j**2)
             # Cross-correlate the reference and processed signals
             if (xsum < small) or (ysum < small):
-                CM[j, m] = 0
+                covariance_matrix[j, m] = 0
             else:
-                CM[j, m] = np.abs(np.sum(xj * yj)) / np.sqrt(xsum * ysum)
-
-    aveCM = np.mean(CM[1:6], 0)
+                covariance_matrix[j, m] = np.abs(np.sum(x_j * y_j)) / np.sqrt(
+                    xsum * ysum
+                )
 
-    return aveCM
+    return np.mean(covariance_matrix[1:6], 0)
diff --git a/clarity/evaluator/haspi/haspi.py b/clarity/evaluator/haspi/haspi.py
index 3cf2c2bb0..b4356fc47 100644
--- a/clarity/evaluator/haspi/haspi.py
+++ b/clarity/evaluator/haspi/haspi.py
@@ -1,7 +1,26 @@
-from clarity.evaluator.haspi import eb, ebm, ip
-
-
-def haspi_v2(x, fx, y, fy, HL, Level1=65):
+"""HASPI intelligibility Index"""
+import numpy as np
+
+from clarity.evaluator.haspi.eb import ear_model
+from clarity.evaluator.haspi.ebm import (
+    cepstral_correlation_coef,
+    env_filter,
+    fir_modulation_filter,
+    modulation_cross_correlation,
+)
+from clarity.evaluator.haspi.ip import get_neural_net, nn_feed_forward_ensemble
+
+
+def haspi_v2(
+    reference: np.ndarray,
+    reference_freq: int,
+    processed: np.ndarray,
+    processed_freq: int,
+    hearing_loss,
+    level1: int = 65,
+    f_lp: int = 320,
+    itype: int = 0,
+):
     """
     Compute the HASPI intelligibility index using the
     auditory model followed by computing the envelope cepstral
@@ -12,15 +31,17 @@ def haspi_v2(x, fx, y, fy, HL, Level1=65):
     neural networks to compute the estimated intelligibility.
 
     Args:
-        x (): Clear input reference speech signal with no noise or distortion.
+    reference (np.ndarray): Clear input reference speech signal with no noise or distortion.
               If a hearing loss is specified, no amplification should be provided.
-        fx (): Sampling rate in Hz for signal x
-        y (): Output signal with noise, distortion, HA gain, and/or processing.
-        fy (): Sampling rate in Hz for signal y.
-        HL (): (1,6) vector of hearing loss at the 6 audiometric frequencies
+    reference_freq (int): Sampling rate in Hz for signal x
+    processed (np.ndarray): Output signal with noise, distortion, HA gain, and/or processing.
+    processed_freq (int): Sampling rate in Hz for signal y.
+    hearing_loss (np.ndarray): (1,6) vector of hearing loss at the 6 audiometric frequencies
                     [250, 500, 1000, 2000, 4000, 6000] Hz.
-        Level1 (): Optional input specifying level in dB SPL that corresponds to a
+    level1 (int): Optional input specifying level in dB SPL that corresponds to a
               signal RMS = 1. Default is 65 dB SPL if argument not provided.
+    f_lp (int):
+    itype (int): Intelligibility model
 
     Returns:
     Intel     Intelligibility estimated by passing the cepstral coefficients
@@ -29,74 +50,101 @@ def haspi_v2(x, fx, y, fy, HL, Level1=65):
     raw       vector of 10 cep corr modulation filterbank outputs, averaged
               over basis funct 2-6.
 
+    Updates:
     James M. Kates, 5 August 2013.
-
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
 
     # Auditory model for intelligibility
     # Reference is no processing, normal hearing
-    itype = 0  # Intelligibility model
-    xenv, _, yenv, _, _, _, fsamp = eb.EarModel(x, fx, y, fy, HL, itype, Level1)
+    reference_env, _, processed_env, _, _, _, fsamp = ear_model(
+        reference,
+        reference_freq,
+        processed,
+        processed_freq,
+        hearing_loss,
+        itype,
+        level1,
+    )
 
     # Envelope modulation features
 
     # LP filter and subsample the envelope
-    fLP = 320
-    fsub = 8 * fLP  # subsample to span 2 octaves above the cutoff frequency
-    xLP, yLP = ebm.EnvFilt(xenv, yenv, fLP, fsub, fsamp)
+    fsub = 8 * f_lp  # subsample to span 2 octaves above the cutoff frequency
+    reference_lp, processed_lp = env_filter(
+        reference_env, processed_env, f_lp, fsub, fsamp
+    )
 
     # Compute the ceptstral coefficients as a function of subsampled time
     nbasis = 6  # Use 6 basis functions
     thr = 2.5  # Silence threshold in dB SL
     dither = 0.1  # Dither in dB RMS to add to envelope signals
-    xcep, ycep = ebm.CepCoef(xLP, yLP, thr, dither, nbasis)
+    reference_cep, processed_cep = cepstral_correlation_coef(
+        reference_lp, processed_lp, thr, dither, nbasis
+    )
 
     # Cepstral coeffifiencts filtered at each modulation rate
     # Band center frequencies [2, 6, 10, 16, 25, 40, 64, 100, 160, 256] Hz
     # Band edges [0, 4, 8, 12.5, 20.5, 30.5, 52.4, 78.1, 128, 200, 328] Hz
-    xmod, ymod, _ = ebm.ModFilt(xcep, ycep, fsub)
+    reference_mod, processed_mod, _ = fir_modulation_filter(
+        reference_cep, processed_cep, fsub
+    )
 
     # Cross-correlation between the cepstral coefficients for the degraded and
     # ref signals at each modulation rate, averaged over basis functions 2-6
-    aveCM = ebm.ModCorr(xmod, ymod)
+    average_correlation_matrix = modulation_cross_correlation(
+        reference_mod, processed_mod
+    )
 
     # Intelligibility prediction
     # Get the neural network parameters and the weights for an ensemble of 10 networks
-    NNparam, Whid, Wout, b = ip.GetNeuralNet()
+    (
+        neural_net_params,
+        weights_hidden,
+        weights_out,
+        normalization_factor,
+    ) = get_neural_net()
 
     # Average the neural network outputs for the modulation filterbank values
-    model = ip.NNfeedfwdEns(aveCM, NNparam, Whid, Wout)
-    model = model[0] / b
+    model = nn_feed_forward_ensemble(
+        average_correlation_matrix, neural_net_params, weights_hidden, weights_out
+    )
+    model = model / normalization_factor
 
     # Return the intelligibility estimate and raw modulation filter outputs
-    Intel = model
-    raw = aveCM
-
-    return Intel, raw
+    return model, average_correlation_matrix
 
 
 def haspi_v2_be(
-    xl, xr, yl, yr, fs_signal, audiogram_l, audiogram_r, audiogram_cfs, Level=100
+    reference_left,
+    reference_right,
+    processed_left,
+    processed_right,
+    fs_signal,
+    audiogram_left,
+    audiogram_right,
+    audiogram_cfs,
+    level=100,
 ) -> float:
     """Better ear HASPI.
 
     Calculates HASPI for left and right ear and selects the better result.
 
     Args:
-        xl: left channel of reference signal
-        xr: right channel of reference signal
-        yl: left channel of processed signal
-        yr: right channel of processed signal
-        fs_signal: sampling rate for both signal
-        audiogram_l: left ear audiogram
-        audiogram_r: right ear audiogram
-        audiogram_cfs: audiogram frequencies
-        Level: level in dB SPL corresponding to RMS=1
+    ref_left (np.ndarray): left channel of reference signal
+    ref_right (np.ndarray): right channel of reference signal
+    proc_left (np.ndarray): left channel of processed signal
+    proc_right (np.ndarray): right channel of processed signal
+    fs_signal (int): sampling rate for both signal
+    audiogram_left (): left ear audiogram
+    audiogram_right (): right ear audiogram
+    audiogram_cfs: audiogram frequencies
+    Level: level in dB SPL corresponding to RMS=1
 
     Returns:
-        float: beHASPI score
+    float: beHASPI score
 
+    Updates:
     Zuzanna Podwinska, March 2022
     """
 
@@ -104,14 +152,23 @@ def haspi_v2_be(
     aud = [250, 500, 1000, 2000, 4000, 6000]
 
     # Adjust listener.audiogram_levels_l and _r to match the frequencies above
-    HL_l = [
-        audiogram_l[i] for i in range(len(audiogram_cfs)) if audiogram_cfs[i] in aud
+    hearing_loss_left = [
+        audiogram_left[i] for i in range(len(audiogram_cfs)) if audiogram_cfs[i] in aud
     ]
-    HL_r = [
-        audiogram_r[i] for i in range(len(audiogram_cfs)) if audiogram_cfs[i] in aud
+    hearing_loss_right = [
+        audiogram_right[i] for i in range(len(audiogram_cfs)) if audiogram_cfs[i] in aud
     ]
 
-    score_l, _ = haspi_v2(xl, fs_signal, yl, fs_signal, HL_l, Level)
-    score_r, _ = haspi_v2(xr, fs_signal, yr, fs_signal, HL_r, Level)
-
-    return max(score_l, score_r)
+    score_left, _ = haspi_v2(
+        reference_left, fs_signal, processed_left, fs_signal, hearing_loss_left, level
+    )
+    score_right, _ = haspi_v2(
+        reference_right,
+        fs_signal,
+        processed_right,
+        fs_signal,
+        hearing_loss_right,
+        level,
+    )
+
+    return max(score_left, score_right)
diff --git a/clarity/evaluator/haspi/ip.py b/clarity/evaluator/haspi/ip.py
index 6e7605b87..0fc9d6e2a 100644
--- a/clarity/evaluator/haspi/ip.py
+++ b/clarity/evaluator/haspi/ip.py
@@ -1,38 +1,42 @@
+"""Functions for ???"""
+from typing import Dict
+
 import numpy as np
 
 
-def GetNeuralNet():
+def get_neural_net():
     """
-    Function to provide the weights derived for the ensemble of ten neural
+    Provide the weights derived for the ensemble of ten neural
     networks used for the HASPI_v2 intelligibility model. The neural networks
     have ten inputs, 4 neurons in the hidden layer, and one output neuron.
     The logsig activation function is used.
 
-    Calling arguments: None
+    Arguments: None
 
     Returned values:
-    NNparam  vector of parameters defining the neural network
-    Whid     cell array 10 x 1 for the weights linking the input to the hidden
+    neural_net_params (dict): parameters defining the neural network
+    weights_hidden (): cell array 10 x 1 for the weights linking the input to the hidden
              layer. Each cell is a 11 x 4 matrix of weights
-    Wout     call array 5 x 1 for the weights linking the hidden to the output
+    weights_out (): call array 5 x 1 for the weights linking the hidden to the output
              layer. Each cell is a 5 x 1 vector of weights.
-    b        normalization so that the maximum neural net output is exactly 1.
+    normalization_factor (): normalization so that the maximum neural net output is exactly 1.
 
+    Updates:
     James M. Kates, 8 October 2019.
     Version for new neural network using actual TFS scores, 24 October 2019.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Set up the neural network parameters
-    NNparam = np.zeros(6)
-    NNparam[0] = 10  # Number of neurons in the input layer
-    NNparam[1] = 4  # Number of neurons in the hidden layer
-    NNparam[2] = 1  # Number of neurons in the output layer
-    NNparam[3] = 1  # Activation function is logsig
-    NNparam[4] = 0  # No offset for the activation function
-    NNparam[5] = 1  # Maximum activation function value
+    neural_net_params = {}
+    neural_net_params["input_layer"] = 10  # Number of neurons in the input layer
+    neural_net_params["hidden_layer"] = 4  # Number of neurons in the hidden layer
+    neural_net_params["output_layer"] = 1  # Number of neurons in the output layer
+    neural_net_params["activation_function"] = 1  # Activation function is logsig
+    neural_net_params["offset_activation"] = 0  # No offset for the activation function
+    neural_net_params["maximum_activation"] = 1  # Maximum activation function value
 
     # Input to hidden layer weights
-    Whid = [
+    weights_hidden = [
         np.array(  # 1
             [
                 [4.9980, -13.0590, 9.5478, -11.6760],
@@ -186,7 +190,7 @@ def GetNeuralNet():
     ]
 
     # Hidden to output layer weights
-    Wout = [
+    weights_out = [
         np.array([[-0.1316, -2.5182, 1.6401, -3.2093, 1.7924]]).T,  # 1
         np.array([[-0.1653, 1.7375, 1.5526, -3.2349, -2.2877]]).T,  # 2
         np.array([[0.1847, -3.1987, -2.4941, 2.7106, -1.8048]]).T,  # 3
@@ -199,26 +203,28 @@ def GetNeuralNet():
         np.array([[0.1024, -0.9517, 2.2123, -2.4008, -3.1655]]).T,  # 10
     ]
 
-    # Normalization factor
-    b = 0.9508
+    normalization_factor = 0.9508
 
-    return NNparam, Whid, Wout, b
+    return neural_net_params, weights_hidden, weights_out, normalization_factor
 
 
-def NNfeedfwdEns(data, NNparam, Whid, Wout):
+def nn_feed_forward_ensemble(
+    data: np.ndarray, neural_net_params: Dict, weights_hidden, weights_out
+) -> float:
     """
     Function to compute the neural network ensemble response to a set of
     inputs. The neural network is defined in NNfeedforwardZ.
 
-    Calling arguments:
-    data      array of features input to the neural network
-    NNparam   vector of neural network paramters
-    Whid      cell array of hidden layer weights for each network
-    Wout      cell array of output layer weights for each network
+    Args:
+    data (np.ndarray): array of features input to the neural network
+    neural_net_params (dict): vector of neural network paramters
+    weights_hidden (list): cell array of hidden layer weights for each network
+    weights_out (list): cell array of output layer weights for each network
 
-    Returned value:
+    Returns:
     model     neural network output vector averaged over the ensemble
 
+    Updates:
     James M. Kates, 20 September 2011.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
@@ -227,14 +233,15 @@ def NNfeedfwdEns(data, NNparam, Whid, Wout):
         data = np.expand_dims(data, 0)
 
     ncond = data.shape[0]  # Number of conditions in the input data
-    K = len(Whid)  # Number of networks in the ensemble
+    n_networks = len(weights_hidden)  # Number of networks in the ensemble
 
     # Ensemble average of the predictions over the set of neural networks used for training
-    predict = np.zeros((ncond, K))
-    for k in range(K):
+    predict = np.zeros((ncond, n_networks))
+    for k in range(n_networks):
         for n in range(ncond):
-            d = data[n, :]
-            _, output = NNfeedforward(d, NNparam, Whid[k], Wout[k])
+            _, output = nn_feed_forward(
+                data[n, :], neural_net_params, weights_hidden[k], weights_out[k]
+            )
             predict[n, k] = output[1]
 
     model = np.mean(predict, 1)
@@ -242,9 +249,11 @@ def NNfeedfwdEns(data, NNparam, Whid, Wout):
     return model
 
 
-def NNfeedforward(data, NNparam, Whid, Wout):
+def nn_feed_forward(
+    data: np.ndarray, neural_net_params: Dict, weights_hidden, weights_out
+):
     """
-    Function to compute the outputs at each layer of a neural network given
+    Compute the outputs at each layer of a neural network given
     the input to the network and the weights. The activiation 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
@@ -253,35 +262,28 @@ def NNfeedforward(data, NNparam, Whid, Wout):
     by the function, and the remaining values correspond to the outputs at
     each neuron in the layer.
 
-    Calling arguments:
-    data       feature vector input to the neural network
-    NNparam    network parameters from NNinit
-    Whid       matrix of weights for the hidden layer
-    Wout       matrix of weights for the output layer
+    Args:
+    data (np.ndarray): feature vector input to the neural network.
+    neural_net_params (dict): network parameters from get_neural_net().
+    weights_hidden (list): matrix of weights for the hidden layer.
+    weights_out (list): matrix of weights for the output layer.
 
-    Returned values:
-    hidden     vector of outputs from the hidden layer
-    output     vector of outputs from the output layer
+    Returns:
+    hidden (): vector of outputs from the hidden layer.
+    output (): vector of outputs from the output layer.
 
+    Updates:
     James M. Kates, 26 October 2010.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Correct Wout shape
-    if len(Wout.shape) == 1:
-        Wout = np.expand_dims(Wout, 1)
-
-    # Extract parameters from the parameter array
-    nx = int(NNparam[0])
-    nhid = int(NNparam[1])
-    nout = int(NNparam[2])
-    beta = float(NNparam[3])
-    offset = float(NNparam[4])
-    again = float(NNparam[5])
+    if len(weights_out.shape) == 1:
+        weights_out = np.expand_dims(weights_out, 1)
 
     # Initialize the array storage
-    x = np.zeros(nx + 1)
-    hidden = np.zeros(nhid + 1)
-    output = np.zeros(nout + 1)
+    x = np.zeros(neural_net_params["input_layer"] + 1)
+    hidden = np.zeros(neural_net_params["hidden_layer"] + 1)
+    output = np.zeros(neural_net_params["output_layer"] + 1)
 
     # Initialize the nodes used for constants
     x[0] = 1
@@ -289,17 +291,23 @@ def NNfeedforward(data, NNparam, Whid, Wout):
     output[0] = 1
 
     # Input layer
-    for i in range(1, nx + 1):
+    for i in range(1, neural_net_params["input_layer"] + 1):
         x[i] = data[i - 1]
 
     # Response of the hidden layer
-    for j in range(1, nhid + 1):
-        sumhid = np.sum(Whid[:, j - 1] * x)
-        hidden[j] = (again / (1 + np.exp(-beta * sumhid))) - offset
+    for j in range(1, neural_net_params["hidden_layer"] + 1):
+        sumhid = np.sum(weights_hidden[:, j - 1] * x)
+        hidden[j] = (
+            neural_net_params["maximum_activation"]
+            / (1 + np.exp(-neural_net_params["activation_function"] * sumhid))
+        ) - neural_net_params["offset_activation"]
 
     # Response of the output layer
-    for k in range(1, nout + 1):
-        sumout = np.sum(Wout[:, k - 1] * hidden)
-        output[k] = (again / (1 + np.exp(-beta * sumout))) - offset
+    for k in range(1, neural_net_params["output_layer"] + 1):
+        sumout = np.sum(weights_out[:, k - 1] * hidden)
+        output[k] = (
+            neural_net_params["maximum_activation"]
+            / (1 + np.exp(-neural_net_params["activation_function"] * sumout))
+        ) - neural_net_params["offset_activation"]
 
     return hidden, output
diff --git a/clarity/evaluator/hasqi/__init__.py b/clarity/evaluator/hasqi/__init__.py
index c90f2bc15..ac5734168 100644
--- a/clarity/evaluator/hasqi/__init__.py
+++ b/clarity/evaluator/hasqi/__init__.py
@@ -1,3 +1,3 @@
-from clarity.evaluator.hasqi.hasqi import hasqi_v2
+from clarity.evaluator.hasqi.hasqi import hasqi_v2, hasqi_v2_better_ear  # noqa F401
 
-__all__ = ["hasqi_v2"]
+__all__ = ["hasqi_v2", "hasqi_v2_be"]
diff --git a/clarity/evaluator/hasqi/hasqi.py b/clarity/evaluator/hasqi/hasqi.py
index 83e12ccab..f09ad7af1 100644
--- a/clarity/evaluator/hasqi/hasqi.py
+++ b/clarity/evaluator/hasqi/hasqi.py
@@ -1,7 +1,18 @@
 from clarity.evaluator.haspi import eb
 
 
-def hasqi_v2(x, fx, y, fy, HL, eq, level1=65):
+def hasqi_v2(
+    reference,
+    reference_freq,
+    processed,
+    processed_freq,
+    hearing_loss,
+    equalisation=1,
+    level1=65,
+    silence_threshold=2.5,
+    add_noise=0.0,
+    segment_covariance=16,
+):
     """
     Function to compute the HASQI version 2 quality index using the
     auditory model followed by computing the envelope cepstral
@@ -11,18 +22,22 @@ def hasqi_v2(x, fx, y, fy, HL, eq, level1=65):
     impaired hearing.
 
     Arguments:
-    x			Clear input reference speech signal with no noise or distortion.
+    reference (np.ndarray): Clear input reference speech signal with no noise or distortion.
               If a hearing loss is specified, NAL-R equalization is optional
-    fx        Sampling rate in Hz for signal x
-    y			Output signal with noise, distortion, HA gain, and/or processing.
-    fy        Sampling rate in Hz for signal y.
-    HL		(1,6) vector of hearing loss at the 6 audiometric frequencies
+    reference_freq (int): Sampling rate in Hz for reference signal.
+    processed (np.ndarray): Output signal with noise, distortion, HA gain, and/or processing.
+    processed_freq (int): Sampling rate in Hz for processed signal.
+    hearing_loss (np.ndarray): vector of hearing loss at the 6 audiometric frequencies
                   [250, 500, 1000, 2000, 4000, 6000] Hz.
-    eq        Flag to provide equalization for the hearing loss to signal x:
+    equalisation (int): Flag to provide equalization for the hearing loss to reference signal:
                 1 = no EQ has been provided, the function will add NAL-R
                 2 = NAL-R EQ has already been added to the reference signal
-    Level1    Optional input specifying level in dB SPL that corresponds to a
+    level1    Optional input specifying level in dB SPL that corresponds to a
               signal RMS = 1. Default is 65 dB SPL if argument not provided.
+    silence_threshold (float): Silence threshold sum across bands, dB above audio threshold. Default: 2.5
+    add_noise (float): Additive noise in dB SL to conditiona cross-covariance. Default: 0.0
+    segment_covariance (int): Segment size for the covariance calculation. Default: 16
+
     Returns:
     Combined  Quality estimate is the product of the nonlinear and linear terms
     Nonlin    Nonlinear quality component = (cepstral corr)^2 x seg BM coherence
@@ -35,85 +50,118 @@ def hasqi_v2(x, fx, y, fy, HL, eq, level1=65):
 
     # Auditory model for quality
     # Reference is no processing or NAL-R, impaired hearing
-    xenv, xBM, yenv, yBM, xSL, ySL, fsamp = eb.EarModel(x, fx, y, fy, HL, eq, level1)
+    (
+        reference_db,
+        reference_basilar_membrane,
+        processed_db,
+        processed_basilar_membrane,
+        reference_sl,
+        processed_sl,
+        freq_sample,
+    ) = eb.ear_model(
+        reference,
+        reference_freq,
+        processed,
+        processed_freq,
+        hearing_loss,
+        equalisation,
+        level1,
+    )
 
     # Envelope and long-term average spectral features
     # Smooth the envelope outputs: 125 Hz sub-sampling rate
-    segsize = 16  # Averaging segment size in msec
-    xdB = eb.env_smooth(xenv, segsize, fsamp)
-    ydB = eb.env_smooth(yenv, segsize, fsamp)
+    reference_smooth = eb.env_smooth(reference_db, segment_covariance, freq_sample)
+    processed_smooth = eb.env_smooth(processed_db, segment_covariance, freq_sample)
 
     # Mel cepstrum correlation using smoothed envelopes
-    # m1=ave of coefficients 2-6
-    # xy=vector of coefficients 1-6
-    thr = 2.5  # Silence threshold: sum across bands, dB above aud threshold
-    addnoise = 0.0  # Additive noise in dB SL to condition cross-covariances
-    cep_corr, xy = eb.melcor(xdB, ydB, thr, addnoise)
+    (
+        average_cepstral_correlation,
+        individual_cepstral_correlations,
+    ) = eb.mel_cepstrum_correlation(
+        reference_smooth, processed_smooth, silence_threshold, add_noise
+    )
 
     # Linear changes in the log-term spectra
     # dloud  vector: [sum abs diff, std dev diff, max diff] spectra
     # dnorm  vector: [sum abs diff, std dev diff, max diff] norm spectra
     # dslope vector: [sum abs diff, std dev diff, max diff] slope
-    dloud, dnorm, dslope = eb.spect_diff(xSL, ySL)
+    d_loud, d_norm, d_slope = eb.spectrum_diff(reference_sl, processed_sl)
 
     # Temporal fine structure correlation measurements
     # Compute the time-frequency segment covariances
-    segcov = 16  # Segment size for the covariance calculation
-    sigcov, sigMSx, sigMSy = eb.bm_covary(xBM, yBM, segcov, fsamp)
+    (
+        signal_cross_covariance,
+        reference_mean_square,
+        processed_mean_square,
+    ) = eb.bm_covary(
+        reference_basilar_membrane,
+        processed_basilar_membrane,
+        segment_covariance,
+        freq_sample,
+    )
 
     # Average signal segment cross-covariance
     # avecov=weighted ave of cross-covariances, using only data above threshold
     # syncov=ave cross-covariance with added IHC loss of synchronization at HF
-    thr = 2.5  # Threshold in dB SL for including time-freq tile
-    avecov, syncov = eb.ave_covary2(sigcov, sigMSx, thr)
-    bm_sync5 = syncov[4]  # Ave segment coherence with IHC loss of sync
+    silence_threshold = 2.5  # Threshold in dB SL for including time-freq tile
+    _, ihc_sync_covariance = eb.ave_covary2(
+        signal_cross_covariance, reference_mean_square, silence_threshold
+    )
+    basilar_membrane_sync5 = ihc_sync_covariance[
+        4
+    ]  # Ave segment coherence with IHC loss of sync
 
     # Extract and normalize the spectral features
     # Dloud:std
-    d = dloud[1]  # Loudness difference std
-    d = d / 2.5  # Scale the value
-    d = 1.0 - d  # 1=perfect, 0=bad
-    d = min(d, 1)
-    d = max(d, 0)
-    d_loud = d
+    d_loud = d_loud[1] / 2.5  # Loudness difference std
+    d_loud = 1.0 - d_loud  # 1=perfect, 0=bad
+    d_loud = min(d_loud, 1)
+    d_loud = max(d_loud, 0)
 
     # Dslope:std
-    d = dslope[1]  # Slope difference std
-    d = 1.0 - d
-    d = min(d, 1)
-    d = max(d, 0)
-    d_slope = d
+    d_slope = d_slope[1]  # Slope difference std
+    d_slope = 1.0 - d_slope
+    d_slope = min(d_slope, 1)
+    d_slope = max(d_slope, 0)
 
     # Construct the models
     # Nonlinear model
-    non_lin = (
-        cep_corr**2
-    ) * bm_sync5  # Combined envelope and temporal fine structure
-
+    non_linear = (
+        average_cepstral_correlation**2
+    ) * basilar_membrane_sync5  # Combined envelope and temporal fine structure
     # Linear model
     linear = 0.579 * d_loud + 0.421 * d_slope  # Linear fit
 
     # Combined model
-    combined = non_lin * linear  # Product of nonlinear x linear
+    combined = non_linear * linear  # Product of nonlinear x linear
 
     # Raw data
-    raw = [cep_corr, bm_sync5, d_loud, d_slope]
-    return combined, non_lin, linear, raw
-
-
-def hasqi_v2_be(
-    xl, xr, yl, yr, fs_signal, audiogram_l, audiogram_r, audiogram_cfs, level=100
+    raw = [average_cepstral_correlation, basilar_membrane_sync5, d_loud, d_slope]
+    return combined, non_linear, linear, raw
+
+
+def hasqi_v2_better_ear(
+    reference_left,
+    reference_right,
+    processed_left,
+    processed_right,
+    sample_freq,
+    audiogram_left,
+    audiogram_right,
+    audiogram_frequencies,
+    level=100,
+    audiogram_freq=[250, 500, 1000, 2000, 4000, 6000],
 ) -> float:
     """Better ear HASQI.
 
     Calculates HASQI for left and right ear and selects the better result.
 
     Args:
-        xl: left channel of reference signal
-        xr: right channel of reference signal
-        yl: left channel of processed signal
-        yr: right channel of processed signal
-        fs_signal: sampling rate for both signal
+        reference_left (np.ndarray): left channel of reference signal
+        reference_right (np.ndarray): right channel of reference signal
+        reference_left (np.ndarray): left channel of processed signal
+        reference_right (np.ndarray): right channel of processed signal
+        sample_freq: sampling rate for both signal
         audiogram_l: left ear audiogram
         audiogram_r: right ear audiogram
         audiogram_cfs: audiogram frequencies
@@ -124,19 +172,35 @@ def hasqi_v2_be(
 
     Gerardo Roa Dabike, November 2022
     """
-
-    # HASQI assumes the following audiogram frequencies:
-    aud = [250, 500, 1000, 2000, 4000, 6000]
-
-    # Adjust listener.audiogram_levels_l and _r to match the frequencies above
-    hl_l = [
-        audiogram_l[i] for i in range(len(audiogram_cfs)) if audiogram_cfs[i] in aud
+    # Adjust to match the frequencies
+    adjusted_left = [
+        audiogram_left[i]
+        for i in range(len(audiogram_frequencies))
+        if audiogram_frequencies[i] in audiogram_freq
     ]
-    hl_r = [
-        audiogram_r[i] for i in range(len(audiogram_cfs)) if audiogram_cfs[i] in aud
+    adjusted_right = [
+        audiogram_right[i]
+        for i in range(len(audiogram_frequencies))
+        if audiogram_frequencies[i] in audiogram_freq
     ]
 
-    score_l, _, _, _ = hasqi_v2(xl, fs_signal, yl, fs_signal, hl_l, level)
-    score_r, _, _, _ = hasqi_v2(xr, fs_signal, yr, fs_signal, hl_r, level)
-
-    return max(score_l, score_r)
+    score_left, _, _, _ = hasqi_v2(
+        reference_left,
+        sample_freq,
+        processed_left,
+        sample_freq,
+        adjusted_left,
+        equalisation=1,
+        level1=level,
+    )
+    score_right, _, _, _ = hasqi_v2(
+        reference_right,
+        sample_freq,
+        processed_right,
+        sample_freq,
+        adjusted_right,
+        equalisation=1,
+        level1=level,
+    )
+
+    return max(score_left, score_right)
diff --git a/clarity/evaluator/mbstoi/__init__.py b/clarity/evaluator/mbstoi/__init__.py
index 7684aec32..dc70efad1 100644
--- a/clarity/evaluator/mbstoi/__init__.py
+++ b/clarity/evaluator/mbstoi/__init__.py
@@ -1,3 +1,4 @@
+"""Modified Binaural Short-Time Objective Intelligibility Evaluator"""
 from clarity.evaluator.mbstoi.mbstoi import mbstoi
 
 __all__ = ["mbstoi"]
diff --git a/clarity/evaluator/mbstoi/mbstoi.py b/clarity/evaluator/mbstoi/mbstoi.py
index a86903885..bc5d4fe98 100644
--- a/clarity/evaluator/mbstoi/mbstoi.py
+++ b/clarity/evaluator/mbstoi/mbstoi.py
@@ -55,6 +55,22 @@ def mbstoi(
         right_ear_noisy (np.ndarray) : Noisy/processed speech signal from right ear.
         fs_signal (int) : Frequency sample rate of signal.
         gridcoarseness (int) : Grid coarseness as denominator of ntaus and ngammas (default: 1).
+        sample_rate (int) :  Sample Rate.
+        n_frame (int) :  Number of Frames.
+        fft_size_in_samples (int) :  ??? size in samples.
+        n_third_octave_bands (int) : Number of third octave bands.
+        centre_freq_first_third_octave_hz (int) :  150,
+        n_frames (int) :  Number of Frames.
+        dyn_range (int) : Dynamic Range.
+        tau_min (float) : Min Tau the ???
+        tau_max (float) : Max Tau the ???
+        gamma_min (int) : Minimum gamma the ???
+        gamma_max (int) : Maximum gamma the ???
+        sigma_delta_0 (float) : ???
+        sigma_epsilon_0 (float) : ???
+        alpha_0_db (int) : ???
+        tau_0 (float) : ???
+        level_shift_deviation (float) : ???
 
     Returns:
         float : mbtsoi index d.
@@ -81,7 +97,6 @@ def mbstoi(
 
     # Resample signals to 10 kHz
     if fs_signal != sample_rate:
-
         logging.debug(
             "Resampling signals with sr=%s for MBSTOI calculation.", sample_rate
         )
diff --git a/clarity/evaluator/mbstoi/mbstoi_utils.py b/clarity/evaluator/mbstoi/mbstoi_utils.py
index 7eaa5d49b..4f6e3856e 100644
--- a/clarity/evaluator/mbstoi/mbstoi_utils.py
+++ b/clarity/evaluator/mbstoi/mbstoi_utils.py
@@ -81,32 +81,32 @@ def equalisation_cancellation(
             )
         )
         # per frame
-        for jj in range(
+        for j in range(
             np.shape(intermediate_intelligibility_measure_grid)[1]
         ):  # pylint: disable=invalid-name
             seg_xl = left_ear_clean_hat[
                 int(frequency_band_edges_indices[i, 0] - 1) : int(
                     frequency_band_edges_indices[i, 1]
                 ),
-                jj : (jj + n_frames),
+                j : (j + n_frames),
             ]
             seg_xr = right_ear_clean_hat[
                 int(frequency_band_edges_indices[i, 0] - 1) : int(
                     frequency_band_edges_indices[i, 1]
                 ),
-                jj : (jj + n_frames),
+                j : (j + n_frames),
             ]
             seg_yl = left_ear_noisy_hat[
                 int(frequency_band_edges_indices[i, 0] - 1) : int(
                     frequency_band_edges_indices[i, 1]
                 ),
-                jj : (jj + n_frames),
+                j : (j + n_frames),
             ]
             seg_yr = right_ear_noisy_hat[
                 int(frequency_band_edges_indices[i, 0] - 1) : int(
                     frequency_band_edges_indices[i, 1]
                 ),
-                jj : (jj + n_frames),
+                j : (j + n_frames),
             ]
 
             # All normalised by subtracting mean
@@ -193,7 +193,7 @@ def equalisation_cancellation(
             # Ensure that intermediate correlation will be sensible and compute it
             # If all minimum values are less than 1e-40, set d[i,jj] to -1
             if np.min(abs(exx * eyy), axis=0).all() < 1e-40:
-                intermediate_intelligibility_measure_grid[i, jj] = -1
+                intermediate_intelligibility_measure_grid[i, j] = -1
                 continue
 
             proportion = np.divide(exx, eyy)
@@ -201,9 +201,9 @@ def equalisation_cancellation(
             idx1 = proportion.argmax(axis=0)
 
             # Return overall maximum and index
-            p_ec_max[i, jj] = tmp.max()
+            p_ec_max[i, j] = tmp.max()
             idx2 = tmp.argmax()
-            intermediate_intelligibility_measure_grid[i, jj] = np.divide(
+            intermediate_intelligibility_measure_grid[i, j] = np.divide(
                 exy[idx1[idx2], idx2],
                 np.sqrt(exx[idx1[idx2], idx2] * eyy[idx1[idx2], idx2]),
             )
diff --git a/clarity/evaluator/msbg/msbg.py b/clarity/evaluator/msbg/msbg.py
index 3eea17a98..fb119b56d 100644
--- a/clarity/evaluator/msbg/msbg.py
+++ b/clarity/evaluator/msbg/msbg.py
@@ -28,16 +28,20 @@
 class Ear:
     """Representation of a pairs of ears."""
 
-    def __init__(self, src_pos="ff", fs=44100, equiv0dBSPL=100, ahr=20):
+    def __init__(
+        self, src_pos="ff", sample_frequency: float = 44100.0, equiv_0db_spl=100, ahr=20
+    ) -> None:
         """
         Constructor for the Ear class.
         Args:
-            src_pos (str): Position of the source
+            src_pos (str): Position of the source.
+            sample_frequency (float): sample frequency.
+            equiv_0db_spl (): ???
+            ahr (): ???
         """
-        self.fs = fs
-        self.calibration_signal = None
+        self.sample_frequency = sample_frequency
         self.src_correction = self.get_src_correction(src_pos)
-        self.equiv0dBSPL = equiv0dBSPL
+        self.equiv_0db_spl = equiv_0db_spl
         self.ahr = ahr
         self.cochlea = None
 
@@ -51,7 +55,7 @@ def set_audiogram(self, audiogram):
         self.cochlea = Cochlea(audiogram=audiogram)
 
     @staticmethod
-    def get_src_correction(src_pos):
+    def get_src_correction(src_pos: str) -> np.array:
         """Select relevant external field to eardrum correction.
 
         Args:
@@ -63,17 +67,19 @@ def get_src_correction(src_pos):
         elif src_pos == "df":
             src_correction = DF_ED
         elif src_pos == "ITU":  # transfer to same grid
-            f = interpolate.interp1d(ITU_HZ, ITU_ERP_DRP, kind="linear")
-            src_correction = f(HZ)
+            field = interpolate.interp1d(ITU_HZ, ITU_ERP_DRP, kind="linear")
+            src_correction = field(HZ)
         else:
             logging.error(
-                "Invalid src position (%s). Must be one of ff, df or ITU", src_pos
+                f"Invalid src position ({src_pos}). Must be one of ff, df or ITU"
             )
             raise ValueError("Invalid src position")
         return src_correction
 
     @staticmethod
-    def src_to_cochlea_filt(ip_sig, src_correction, fs, backward=False):
+    def src_to_cochlea_filt(
+        ip_sig: np.ndarray, src_correction: str, sample_frequency: int, backward=False
+    ) -> np.ndarray:
         """Simulate middle and outer ear transfer functions.
 
         Made more general, Mar2012, to include diffuse field as well as ITU reference points,
@@ -86,17 +92,17 @@ def src_to_cochlea_filt(ip_sig, src_correction, fs, backward=False):
         Args:
             ip_sig (ndarray): signal to process
             src_correction (str): correction to make for src position
-            fs (int): sampling frequency
+            sample_frequency (int): sampling frequency
             backward (bool, optional): if true then cochlea to src (default: False)
 
         Returns:
-            ndarray: the processed signal
+            np.ndarray: the processed signal
 
         """
         logging.info("performing outer/middle ear corrections")
 
-        # make sure that response goes only up to fs/2
-        nyquist = int(fs / 2)
+        # make sure that response goes only up to sample_frequency/2
+        nyquist = int(sample_frequency / 2)
         ixf_useful = np.nonzero(HZ < nyquist)
 
         hz_used = HZ[ixf_useful]
@@ -104,8 +110,8 @@ def src_to_cochlea_filt(ip_sig, src_correction, fs, backward=False):
 
         # sig from free field to cochlea: 0 dB at 1kHz
         correction = src_correction - MIDEAR
-        f = interpolate.interp1d(HZ, correction, kind="linear")
-        last_correction = f(nyquist)  # generate synthetic response at Nyquist
+        field = interpolate.interp1d(HZ, correction, kind="linear")
+        last_correction = field(nyquist)  # generate synthetic response at Nyquist
 
         correction_used = np.append(correction[ixf_useful], last_correction)
         if backward:  # ie. coch->src rather than src->coch
@@ -115,7 +121,7 @@ def src_to_cochlea_filt(ip_sig, src_correction, fs, backward=False):
         correction_used = correction_used.flatten()
         # Create filter with 23 msec window to do reasonable job down to about 100 Hz
         # Scales with fs, falls over with longer windows in fir2 in original MATLAB version
-        n_wdw = 2 * math.floor((fs / 16e3) * 368 / 2)
+        n_wdw = 2 * math.floor((sample_frequency / 16e3) * 368 / 2)
         hz_used = hz_used / nyquist
 
         b = firwin2(n_wdw + 1, hz_used.flatten(), correction_used, window=("kaiser", 4))
@@ -123,11 +129,11 @@ def src_to_cochlea_filt(ip_sig, src_correction, fs, backward=False):
 
         return op_sig
 
-    def make_calibration_signal(self, REF_RMS_DB):
+    def make_calibration_signal(self, ref_rms_db):
         """Add the calibration signal to the start of the signal.
 
         Args:
-            signal (ndarray): input signal
+            ref_rms_db (ndarray): input signal
 
         Returns:
             ndarray: the processed signal
@@ -137,98 +143,126 @@ def make_calibration_signal(self, REF_RMS_DB):
         # Tone at nearest channel centre frequency to 500 Hz
         # For testing, ref_rms_dB must be equal to -31.2
 
-        noise_burst = gen_eh2008_speech_noise(duration=2, fs=self.fs, level=REF_RMS_DB)
-        tone_burst = gen_tone(freq=520, duration=0.5, fs=self.fs, level=REF_RMS_DB)
-        silence = np.zeros(int(0.05 * self.fs))  # 50 ms duration
+        noise_burst = gen_eh2008_speech_noise(
+            duration=2, sample_frequency=self.sample_frequency, level=ref_rms_db
+        )
+        tone_burst = gen_tone(
+            freq=520,
+            duration=0.5,
+            sample_frequency=self.sample_frequency,
+            level=ref_rms_db,
+        )
+        silence = np.zeros(int(0.05 * self.sample_frequency))  # 50 ms duration
         return (
             np.concatenate((silence, tone_burst, silence, noise_burst, silence)),
             silence,
         )
 
     @staticmethod
-    def array_to_list(chans):
-        """Convert ndarray into a list of 1-D arrays."""
+    def array_to_list(chans: np.ndarray) -> np.ndarray:
+        """Convert signal into a list of 1-D arrays.
+
+        Args:
+            signal (np.ndarray) Signal to be converted, can be 1-D already.
+
+        Returns:
+            np.ndarray: A list of 1-D arrays.
+        """
         if len(chans.shape) == 1:
             chans = chans[..., np.newaxis]
         return [chans[:, i] for i in range(chans.shape[1])]
 
-    def process(self, chans, add_calibration=False):
+    def process(self, signal: np.ndarray, add_calibration: bool = False) -> np.ndarray:
         """Run the hearing loss simulation.
 
         Args:
-            chans (ndarray): signal to process, shape either N, Nx1, Nx2
+            signal (ndarray): signal to process, shape either N, Nx1, Nx2
             add_calibration (bool): prepend calibration tone and speech-shaped noise
                 (default: False)
 
         Returns:
-            ndarray: the processed signal
+            np.ndarray: the processed signal
 
         """
 
-        fs = 44100  # This is the only sampling frequency that can be used
-        if fs != self.fs:
+        sample_frequency = 44100  # This is the only sampling frequency that can be used
+        if sample_frequency != self.sample_frequency:
             logging.error(
                 "Warning: only a sampling frequency of 44.1kHz can be used by MSBG."
             )
 
-        logging.info("Processing %s samples", len(chans))
+        logging.info("Processing {len(chans)} samples")
 
         # Get single channel array and convert to list
-        chans = Ear.array_to_list(chans)
+        signal = Ear.array_to_list(signal)
 
         # Need to know file RMS, and then call that a certain level in SPL:
         # needs some form of pre-measuring.
-        levelreFS = 10 * np.log10(np.mean(np.array(chans) ** 2))
+        level_resample_frequency = 10 * np.log10(np.mean(np.array(signal) ** 2))
 
-        equiv_0dB_SPL = self.equiv0dBSPL + self.ahr
+        equiv_0db_spl = self.equiv_0db_spl + self.ahr
 
-        leveldBSPL = equiv_0dB_SPL + levelreFS
-        CALIB_DB_SPL = leveldBSPL
-        TARGET_SPL = leveldBSPL
-        REF_RMS_DB = CALIB_DB_SPL - equiv_0dB_SPL
+        level_db_spl = equiv_0db_spl + level_resample_frequency
+        calib_db_spl = level_db_spl
+        target_spl = level_db_spl
+        ref_rms_db = calib_db_spl - equiv_0db_spl
 
         # Measure RMS where 3rd arg is dB_rel_rms (how far below)
-        calculated_rms, idx, _rel_dB_thresh, _active = measure_rms(chans[0], fs, -12)
+        calculated_rms, idx, _rel_db_thresh, _active = measure_rms(
+            signal[0], sample_frequency, -12
+        )
 
         # Rescale input data and check level after rescaling
         # This is to ensure that the following processing steps are applied correctly
-        change_dB = TARGET_SPL - (equiv_0dB_SPL + 20 * np.log10(calculated_rms))
-        chans = [x * np.power(10, 0.05 * change_dB) for x in chans]
-        new_rms_db = equiv_0dB_SPL + 10 * np.log10(
-            np.mean(np.power(chans[0][idx], 2.0))
+        change_db = target_spl - (equiv_0db_spl + 20 * np.log10(calculated_rms))
+        signal = [x * np.power(10, 0.05 * change_db) for x in signal]
+        new_rms_db = equiv_0db_spl + 10 * np.log10(
+            np.mean(np.power(signal[0][idx], 2.0))
         )
         logging.info(
-            "Rescaling: leveldBSPL was %3.1f dB SPL, now %3.1f dB SPL. Target SPL is %3.1f dB SPL.",
-            leveldBSPL,
-            new_rms_db,
-            TARGET_SPL,
+            "Rescaling: "
+            f"leveldBSPL was {level_db_spl:3.1f} dB SPL, now {new_rms_db:3.1f} dB SPL. "
+            f" Target SPL is {target_spl:3.1f} dB SPL."
         )
 
         # Add calibration signal at target SPL dB
         if add_calibration is True:
-            if self.calibration_signal is None:
-                self.calibration_signal = self.make_calibration_signal(REF_RMS_DB)
-            chans = [
-                np.concatenate(
-                    (self.calibration_signal[0], x, self.calibration_signal[1])
-                )
-                for x in chans
+            # if self.calibration_signal is None:
+            #     self.calibration_signal = self.make_calibration_signal(ref_rms_db)
+            # signal = [
+            #     np.concatenate(
+            #         (self.calibration_signal[0], x, self.calibration_signal[1])
+            #     )
+            #     for x in signal
+            # ]
+            calibration_signal = self.make_calibration_signal(ref_rms_db)
+            signal = [
+                np.concatenate((calibration_signal[0], x, calibration_signal[1]))
+                for x in signal
             ]
 
         # Transform from src pos to cochlea, simulate cochlea, transform back to src pos
-        chans = [Ear.src_to_cochlea_filt(x, self.src_correction, fs) for x in chans]
-        chans = [self.cochlea.simulate(x, equiv_0dB_SPL) for x in chans]
-        chans = [
-            Ear.src_to_cochlea_filt(x, self.src_correction, fs, backward=True)
-            for x in chans
+        signal = [
+            Ear.src_to_cochlea_filt(x, self.src_correction, sample_frequency)
+            for x in signal
+        ]
+        if self.cochlea is not None:
+            signal = [self.cochlea.simulate(x, equiv_0db_spl) for x in signal]
+        signal = [
+            Ear.src_to_cochlea_filt(
+                x, self.src_correction, sample_frequency, backward=True
+            )
+            for x in signal
         ]
 
         # Implement low-pass filter at top end of audio range: flat to Cutoff freq, tails
         # below -80 dB. Suitable lpf for signals later converted to MP3, flat to 15 kHz.
         # Small window to design low-pass FIR, to cut off high freq processing noise
         # low-pass to something sensible, prevents exaggeration of > 15 kHz
-        winlen = 2 * math.floor(0.0015 * fs) + 1
-        lpf44d1 = firwin(winlen, UPPER_CUTOFF_HZ / int(fs / 2), window=("kaiser", 8))
-        chans = [lfilter(lpf44d1, 1, x) for x in chans]
+        winlen = 2 * math.floor(0.0015 * sample_frequency) + 1
+        lpf44d1 = firwin(
+            winlen, UPPER_CUTOFF_HZ / int(sample_frequency / 2), window=("kaiser", 8)
+        )
+        signal = [lfilter(lpf44d1, 1, x) for x in signal]
 
-        return chans
+        return signal
diff --git a/clarity/evaluator/msbg/msbg_hparams/__init__.py b/clarity/evaluator/msbg/msbg_hparams/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/clarity/evaluator/msbg/msbg_utils.py b/clarity/evaluator/msbg/msbg_utils.py
index 3752afb8b..b93b2027f 100644
--- a/clarity/evaluator/msbg/msbg_utils.py
+++ b/clarity/evaluator/msbg/msbg_utils.py
@@ -3,7 +3,7 @@
 import logging
 import math
 import os
-from typing import Optional, Tuple, Union
+from typing import Dict, Optional, Tuple, Union
 
 import numpy as np
 import scipy
@@ -95,7 +95,7 @@
 ) * (7.5 / 9)
 
 
-def read_gtf_file(gtf_file):
+def read_gtf_file(gtf_file: str) -> Dict:
     """Read a gammatone filterbank file.
 
     List data is converted into numpy arrays.
@@ -113,17 +113,23 @@ def read_gtf_file(gtf_file):
     return data
 
 
-def firwin2(n, f, a, window=None, antisymmetric=None):  # pylint: disable=W0613
+def firwin2(
+    n_taps: int,
+    frequencies: np.ndarray,
+    filter_gains: np.ndarray,
+    window: Union[None, str, tuple] = None,
+    antisymmetric: Optional[bool] = None,
+):  # pylint: disable=W0613
     """FIR filter design using the window method.
 
     Partial implementation of scipy firwin2 but using our own MATLAB-derived fir2.
 
     Args:
-        n (int): The number of taps in the FIR filter.
-        f (ndarray): The frequency sampling points. 0.0 to 1.0 with 1.0 being Nyquist.
-        a (ndarray): The filter gains at the frequency sampling points.
+        n_taps (int): The number of taps in the FIR filter.
+        frequencies (ndarray): The frequency sampling points. 0.0 to 1.0 with 1.0 being Nyquist.
+        filter_gains (ndarray): The filter gains at the frequency sampling points.
         window (string or (string, float), optional): See scipy.firwin2 (default: (None))
-        _antisymmetric (bool, optional): Unused but present to main compatability
+        antisymmetric (bool, optional): Unused but present to main compatability
             with scipy firwin2.
 
     Returns:
@@ -131,141 +137,153 @@ def firwin2(n, f, a, window=None, antisymmetric=None):  # pylint: disable=W0613
 
     """
     window_shape = None
+    window_type = None
     if isinstance(window, tuple):
         window_type, window_param = window if window is not None else (None, 0)
     else:
         window_type = window
 
-    order = n - 1
+    order = n_taps - 1
 
     if window_type == "kaiser":
-        window_shape = scipy.signal.kaiser(n, window_param)
+        window_shape = scipy.signal.kaiser(n_taps, window_param)
 
     if window_shape is None:
-        b, _ = fir2(order, f, a)
+        filter_coef, _ = fir2(order, frequencies, filter_gains)
     else:
-        b, _ = fir2(order, f, a, window_shape)
+        filter_coef, _ = fir2(order, frequencies, filter_gains, window_shape)
 
-    return b
+    return filter_coef
 
 
 def fir2(
-    nn: int, ff: np.ndarray, aa: np.ndarray, npt: Optional[int] = None
+    filter_length: int,
+    frequencies: np.ndarray,
+    filter_gains: np.ndarray,
+    n_interpolate: Optional[int] = None,
 ) -> Tuple[np.ndarray, int]:
     """FIR arbitrary shape filter design using the frequency sampling method.
 
     Translation of MATLAB fir2.
 
     Args:
-        nn (int): Order
-        ff (ndarray): Frequency breakpoints (0 < F < 1) where 1 is Nyquist rate.
-                        First and last elements must be 0 and 1 respectively
-        aa (ndarray): Magnitude breakpoints
-        npt (int, optional): Number of points for freq response interpolation
-            (default: max (smallest power of 2 greater than nn, 512))
+        filter_length (int): Order
+        frequencies (ndarray): The frequency sampling points (0 < frequencies < 1) where 1 is Nyquist rate.
+                        First and last elements must be 0 and 1 respectively.
+        filter_gains (ndarray): The filter gains at the frequency sampling points.
+        n_interpolate (int, optional): Number of points for freq response interpolation
+            (default: max(smallest power of 2 greater than nn, 512))
 
     Returns:
-        ndarray: nn + 1 filter coefficients, 1
+        np.ndarray: nn + 1 filter coefficients, 1
 
     """
     # Work with filter length instead of filter order
-    nn += 1
+    filter_length += 1
 
-    if npt is None:
-        new_npt: int = 2 ** np.ceil(math.log(nn) / math.log(2)) if nn >= 1024 else 512
-        wind = scipy.signal.hamming(nn)
+    if n_interpolate is None:
+        wind = scipy.signal.hamming(filter_length)
     else:
-        wind = npt
-        new_npt = 2 ** np.ceil(math.log(nn) / math.log(2)) if nn >= 1024 else 512
-    npt = new_npt
+        wind = n_interpolate
+    n_interpolate = (
+        2 ** np.ceil(math.log(filter_length) / math.log(2))
+        if filter_length >= 1024
+        else 512
+    )
 
-    lap = np.fix(npt / 25).astype(int)
+    lap = np.fix(n_interpolate / 25).astype(int)
 
-    nbrk = max(len(ff), len(aa))
+    nbrk = max(len(frequencies), len(filter_gains))
 
-    ff[0] = 0
-    ff[nbrk - 1] = 1
+    frequencies[0] = 0
+    frequencies[nbrk - 1] = 1
 
-    H = np.zeros(npt + 1)
+    H = np.zeros(n_interpolate + 1)
     nint = nbrk - 1
-    df = np.diff(ff, n=1)
+    df = np.diff(frequencies, n=1)
 
-    npt += 1
+    n_interpolate += 1
     nb = 0
-    H[0] = aa[0]
+    H[0] = filter_gains[0]
 
     for i in np.arange(nint):
         if df[i] == 0:
             nb = int(np.ceil(nb - lap / 2))
             ne: int = nb + lap - 1
         else:
-            ne = int(np.fix(ff[i + 1] * npt)) - 1
+            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)
-        H[nb : (ne + 1)] = inc * aa[i + 1] + (1 - inc) * aa[i]
+        H[nb : (ne + 1)] = inc * filter_gains[i + 1] + (1 - inc) * filter_gains[i]
         nb = ne + 1
 
-    dt = 0.5 * (nn - 1)
-    rad = -dt * 1j * math.pi * np.arange(0, npt) / (npt - 1)
+    dt = 0.5 * (filter_length - 1)
+    rad = -dt * 1j * math.pi * np.arange(0, n_interpolate) / (n_interpolate - 1)
     H = H * np.exp(rad)
 
-    H = np.concatenate((H, H[npt - 2 : 0 : -1].conj()))
+    H = np.concatenate((H, H[n_interpolate - 2 : 0 : -1].conj()))
     ht = np.real(np.fft.ifft(H))
 
-    b = ht[0:nn] * wind
+    b = ht[0:filter_length] * wind
 
     return b, 1
 
 
 def gen_tone(
-    freq: float, duration: float, fs: float = 44100.0, level: float = 0.0
+    freq: float, duration: float, sample_frequency: float = 44100.0, level: float = 0.0
 ) -> np.ndarray:
     """Generate a pure tone.
 
     Args:
         freq (float): Frequency of tone in Hz.
         duration (float): Duration of tone in seconds.
-        fs (float, optional): Sample rate of generated tone in Hz. (default: 44100)
+        sample_frequency (float, optional): Sample rate of generated tone in Hz. (default: 44100)
         level (float, optional): Level of tone in dB SPL. (default: 0)
+
+    Returns:
+        np.ndarray
     """
     return (
         1.4142
         * np.power(10, (0.05 * level))
-        * np.sin(2 * np.pi * freq * np.arange(1, duration * fs + 1) / fs)
+        * np.sin(
+            2
+            * np.pi
+            * freq
+            * np.arange(1, duration * sample_frequency + 1)
+            / sample_frequency
+        )
     )
 
 
 def gen_eh2008_speech_noise(
     duration: float,
-    fs: float = 44100.0,
+    sample_frequency: float = 44100.0,
     level: Optional[float] = None,
     supplied_b: Optional[np.ndarray] = None,
 ) -> np.ndarray:
     """Generate speech shaped noise.
 
-    Start with white noise and re-shape to ideal SII, ie flat to 500 Hz, and
-    sloping -9db/oct beyond that.
+    Start with white noise and re-shape to ideal SII, ie flat to 500 Hz, and sloping -9db/oct beyond that.
 
-    Slightly different shape from SII stylised same as
-    EarHEar 2008 paper, Moore et al.
+    Slightly different shape from SII stylised same as EarHEar 2008 paper, Moore et al.
 
     Args:
         duration (float): Duration of signal in seconds
-        fs (float): Sampling rate
+        sample_frequency (float): Sampling rate
         level (float, optional): Normalise to level dB if present
-        supplied_b (ndarray, optional): High-pass filter
-            (default: uses built-in pre-emphasis filter)
+        supplied_b (ndarray, optional): High-pass filter (default: uses built-in pre-emphasis filter)
 
     Returns:
         ndarray: Noise signal
 
     """
-    fs = int(fs)
-    n_samples = int(duration * fs)
+    sample_frequency = int(sample_frequency)
+    n_samples = int(duration * sample_frequency)
 
     # this rescales so that we get -7.5 dB/oct up to 8kHz, and -13 dB/oct above that
-    norm_freq = GEN_NOISE_HZ / (fs / 2)
+    norm_freq = GEN_NOISE_HZ / (sample_frequency / 2)
     last_f_idx = np.max(np.where(norm_freq < 1))
     norm_freq = np.append(norm_freq[0 : last_f_idx + 1], 1)
 
@@ -275,7 +293,7 @@ def gen_eh2008_speech_noise(
     m = np.exp(np.log(10) * norm_emph / 20)
 
     # Create type II filter with 10 msec window and even number of taps
-    n_taps = int(2 * np.ceil(10 * (fs / 2000))) + 1
+    n_taps = int(2 * np.ceil(10 * (sample_frequency / 2000))) + 1
     b = (
         supplied_b
         if supplied_b is not None
@@ -283,22 +301,26 @@ def gen_eh2008_speech_noise(
     )
 
     # white noise, 0 DC
-    nburst = np.random.random((1, n_samples + len(b))) - 0.5
+    n_burst = np.random.random((1, n_samples + len(b))) - 0.5
 
     # remove low-freq noise that may bias RMS estimate, -33dB at 50 Hz
-    eh2008_nse = scipy.signal.lfilter(b, 1, nburst)
+    eh2008_nse = scipy.signal.lfilter(b, 1, n_burst)
 
     # high-pass filter to remove low freqs (will be 2-pass with filtfilt)
-    hpf = scipy.signal.ellip(3, 0.1, 50, 100 / (fs / 2), "high")
-    padlen = 3 * (max(len(hpf[1]), len(hpf[0])) - 1)
-    eh2008_nse = scipy.signal.filtfilt(*hpf, eh2008_nse, padlen=padlen).flatten()
+    high_pass_filter = scipy.signal.ellip(
+        3, 0.1, 50, 100 / (sample_frequency / 2), "high"
+    )
+    padlen = 3 * (max(len(high_pass_filter[1]), len(high_pass_filter[0])) - 1)
+    eh2008_nse = scipy.signal.filtfilt(
+        *high_pass_filter, eh2008_nse, padlen=padlen
+    ).flatten()
 
     # this introduces a delay so remove it, ie time-ADVANCE audio
     # compensating shift to time-align all filter outputs
-    dly_shift = int(np.floor(len(b) / 2))
-    valid_len = int(np.size(eh2008_nse) - dly_shift)  # _advance_ filter outputs
+    delay_shift = int(np.floor(len(b) / 2))
+    valid_len = int(np.size(eh2008_nse) - delay_shift)  # _advance_ filter outputs
     # time advance
-    eh2008_nse[0:valid_len] = eh2008_nse[dly_shift:]
+    eh2008_nse[0:valid_len] = eh2008_nse[delay_shift:]
     eh2008_nse = eh2008_nse[0:n_samples]
 
     if level is not None:
@@ -312,9 +334,9 @@ def gen_eh2008_speech_noise(
 
 
 def generate_key_percent(
-    sig: np.ndarray,
-    thr_dB: float,
-    winlen: int,
+    signal: np.ndarray,
+    threshold_db: float,
+    window_length: int,
     percent_to_track: Optional[float] = None,
 ) -> Tuple[np.ndarray, float]:
     """Generate key percent.
@@ -323,28 +345,32 @@ def generate_key_percent(
     of rms, adaptively sets threshold after looking at histogram of whole recording
 
     Args:
-        sig (ndarray): The signal to analyse
-        thr_dB (float): fixed energy threshold (dB)
-        winlen (int): length of window in samples
-        percent_to_track (float, optional): Track a percentage of frames (default: {None})
+        signal (ndarray): The signal to analyse.
+        threshold_db (float): fixed energy threshold (dB).
+        window_length (int): length of window in samples.
+        percent_to_track (float, optional): Track a percentage of frames (default: {None}).
 
     Raises:
-        ValueError: percent_to_track is set too high
+        ValueError: percent_to_track is set too high.
 
     Returns:
-        (ndarray, float) -- "key" and rms threshold
-            The key array of indices of samples used in rms calculation,
+        (tuple): containig
+        - key (ndarray): The key array of indices of samples used in rms calculation.
+        - used_threshold_db (float): Root Mean Squared threshold.
+
             and the threshold used to get a more accurate rms calculation
     """
-    winlen = int(winlen)
-    sig = sig.flatten()
-    if winlen != math.floor(winlen):  # whoops on fractional indexing: 7-March 2002
-        winlen = math.floor(winlen)
-        logging.warning("Window length must be integer: now %s", winlen)
+    window_length = int(window_length)
+    signal = signal.flatten()
+    if window_length != math.floor(
+        window_length
+    ):  # whoops on fractional indexing: 7-March 2002
+        window_length = math.floor(window_length)
+        logging.warning(f"Window length must be integer: now {window_length}")
 
-    siglen = len(sig)
+    signal_length = len(signal)
 
-    expected = thr_dB
+    expected = threshold_db
     # new Dec 2003. Possibly track percentage of frames rather than fixed threshold
     if percent_to_track is not None:
         logging.info("tracking %s percentage of frames", percent_to_track)
@@ -354,28 +380,32 @@ def generate_key_percent(
     # put floor into histogram distribution
     non_zero = np.power(10, (expected - 30) / 10)
 
-    nframes = -1
-    totframes = math.floor(siglen / winlen)
-    every_dB = np.zeros(totframes)
+    n_frames = -1
+    total_frames = math.floor(signal_length / window_length)
+    every_db = np.zeros(total_frames)
 
-    for ix in np.arange(0, winlen * totframes - 1, winlen):
-        nframes += 1
-        this_sum = np.sum(np.power(sig[ix : (ix + winlen)].astype("float"), 2))
-        every_dB[nframes] = 10 * np.log10(non_zero + this_sum / winlen)
-    nframes += 1
+    for ix in np.arange(
+        0, window_length * total_frames - 1, window_length
+    ):  # pylint: disable=invalid-name
+        n_frames += 1
+        this_sum = np.sum(
+            np.power(signal[ix : (ix + window_length)].astype("float"), 2)
+        )
+        every_db[n_frames] = 10 * np.log10(non_zero + this_sum / window_length)
+    n_frames += 1
 
     # from now on save only those analysed
-    every_dB = every_dB[:nframes]
+    every_db = every_db[:n_frames]
 
     # Bec 2003, was 100 to give about a 0.5 dB quantising of levels
-    n_bins, levels = np.histogram(every_dB, 140)
+    n_bins, levels = np.histogram(every_db, 140)
     if percent_to_track is not None:
         # min number of bins to use
-        inactive_bins = (100 - percent_to_track) * nframes / 100
+        inactive_bins = (100 - percent_to_track) * n_frames / 100
         n_levels = len(levels)
         inactive_ix = 0
         ix_count = 0
-        for ix in np.arange(0, n_levels, 1):
+        for ix in np.arange(0, n_levels, 1):  # pylint: disable=invalid-name
             inactive_ix = inactive_ix + n_bins[ix]
             if inactive_ix > inactive_bins:
                 break
@@ -387,34 +417,35 @@ def generate_key_percent(
         expected = levels[max(1, ix_count)]
 
     # set new threshold conservatively to include more bins than desired
-    used_thr_dB = expected
+    used_threshold_db = expected
 
     # histogram should produce a two-peaked curve: thresh should be set in valley
     # between the two peaks, and set threshold a bit above that,
     # as it heads for main peak
-    frame_index = np.nonzero(every_dB >= expected)[0]
+    # FixMe : Could Otsu's method (from image processing) be used here? https://en.wikipedia.org/wiki/Otsu's_method
+    frame_index = np.nonzero(every_db >= expected)[0]
     valid_frames = len(frame_index)
-    key = np.zeros((1, valid_frames * winlen))[0]
+    key = np.zeros((1, valid_frames * window_length))[0]
 
     # convert frame numbers into indices for sig
-    for ix in np.arange(valid_frames):
+    for ix in np.arange(valid_frames):  # pylint: disable=invalid-name
         meas_span = np.arange(
-            (frame_index[ix] * winlen), (frame_index[ix] + 1) * winlen
+            (frame_index[ix] * window_length), (frame_index[ix] + 1) * window_length
         )
-        key_span = np.arange(((ix) * winlen), (ix + 1) * winlen, 1)
+        key_span = np.arange(((ix) * window_length), (ix + 1) * window_length, 1)
         key[key_span] = meas_span
         key = key.flatten()
 
-    return key, used_thr_dB
+    return key, used_threshold_db
 
 
 def measure_rms(
     signal: np.ndarray,
-    fs: float,
-    dB_rel_rms: float,
+    sample_frequency: float,
+    db_rel_rms: float,
     percent_to_track: Optional[float] = None,
 ) -> tuple:
-    """Measure rms.
+    """Measure Root Mean Square.
 
     A sophisticated method of measuring RMS in a file. It splits the signal up into
     short windows, performs  a histogram of levels, calculates an approximate RMS,
@@ -423,27 +454,30 @@ def measure_rms(
     threshold.
 
     Args:
-        signal (ndarray): the signal of which to measure the rms
-        fs (float): sampling frequency
-        dB_rel_rms (float): threshold for frames to track
+        signal (ndarray): the signal of which to measure the Root Mean Square.
+        sample_frequency (float): sampling frequency.
+        db_rel_rms (float): threshold for frames to track.
         percent_to_track (float, optional): track percentage of frames,
             rather than threshold (default: {None})
     Returns:
         (tuple): tuple containing
         - rms (float): overall calculated rms (linear)
         - key (ndarray): "key" array of indices of samples used in rms calculation
-        - rel_dB_thresh (float): fixed threshold value of -12 dB
+        - rel_db_thresh (float): fixed threshold value of -12 dB
         - active (float): proportion of values used in rms calculation
     """
-    fs = int(fs)
+    sample_frequency = int(sample_frequency)
     # first RMS is of all signal.
     first_stage_rms = np.sqrt(np.sum(np.power(signal, 2) / len(signal)))
     # use this RMS to generate key threshold to get more accurate RMS
-    key_thr_dB = max(20 * np.log10(first_stage_rms) + dB_rel_rms, -80)
-
-    # move key_thr_dB to account for noise less peakier than signal
-    key, used_thr_dB = generate_key_percent(
-        signal, key_thr_dB, round(WIN_SECS * fs), percent_to_track=percent_to_track
+    key_thr_db = max(20 * np.log10(first_stage_rms) + db_rel_rms, -80)
+
+    # move key_thr_db to account for noise less peakier than signal
+    key, used_thr_db = generate_key_percent(
+        signal,
+        key_thr_db,
+        round(WIN_SECS * sample_frequency),
+        percent_to_track=percent_to_track,
     )
 
     idx = key.astype(int)  # move into generate_key_percent
@@ -451,9 +485,9 @@ def measure_rms(
     # (for independent==1 loop where it sets a target for rms measure)
     active = 100 * len(key) / len(signal)
     rms = np.sqrt(np.sum(np.power(signal[idx], 2)) / len(key))
-    rel_dB_thresh = used_thr_dB - 20 * np.log10(rms)
+    rel_db_thresh = used_thr_db - 20 * np.log10(rms)
 
-    return rms, idx, rel_dB_thresh, active
+    return rms, idx, rel_db_thresh, active
 
 
 def pad(signal, length):
@@ -503,40 +537,47 @@ def read_signal(
     if offset != 0:
         wave_file.seek(offset)
 
-    x = wave_file.read(frames=nsamples)
+    signal = wave_file.read(frames=nsamples)
 
     if wave_file.samplerate != MSBG_FS:
-        x = scipy.signal.resample(x, int(MSBG_FS * x.shape[0] / wave_file.samplerate))
+        signal = scipy.signal.resample(
+            signal, int(MSBG_FS * signal.shape[0] / wave_file.samplerate)
+        )
 
-    return x
+    return signal
 
 
 def write_signal(
-    filename: str, x: np.ndarray, fs: float, floating_point: bool = True
+    filename: str,
+    signal: np.ndarray,
+    sample_frequency: float,
+    floating_point: bool = True,
 ) -> None:
     """Write a signal as fixed or floating point wav file.
 
     Args:
-        filename (str): name of file in to write to
-        x (ndarray): signal to write
-        fs (float): sampling frequency
-        floating_point (bool): write as floating point else an ints (default: True)
+        filename (str): 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).
     """
 
-    if fs != MSBG_FS:
-        logging.warning("Sampling rate mismatch: %s with sr=%s.", filename, fs)
+    if sample_frequency != MSBG_FS:
+        logging.warning(
+            f"Sampling rate mismatch: {filename} with sample frequency = {sample_frequency}."
+        )
         # raise ValueError("Sampling rate mismatch")
 
     if floating_point is False:
         if TEST_NBITS == 16:
             subtype = "PCM_16"
             # If signal is float and we want int16
-            x *= 32768
-            x = x.astype(np.dtype("int16"))
-            assert np.max(x) <= 32767 and np.min(x) >= -32768
+            signal *= 32768
+            signal = signal.astype(np.dtype("int16"))
+            assert np.max(signal) <= 32767 and np.min(signal) >= -32768
         elif TEST_NBITS == 24:
             subtype = "PCM_24"
     else:
         subtype = "FLOAT"
 
-    soundfile.write(filename, x, fs, subtype=subtype)
+    soundfile.write(filename, signal, sample_frequency, subtype=subtype)
diff --git a/docs/images/cadenza_logo.png b/docs/images/cadenza_logo.png
new file mode 100644
index 000000000..0d922c288
Binary files /dev/null and b/docs/images/cadenza_logo.png differ
diff --git a/pyproject.toml b/pyproject.toml
index 1b43b53b2..5b7665f38 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,13 +2,13 @@
 requires = [
   "setuptools >= 40.9.0",
   "wheel",
+  "versioneer"
   ]
-
 build-backend = "setuptools.build_meta"
 
 [tool.pytest.ini_options]
 minversion = "7.0"
-addopts = "--cov"
+addopts = "--cov clarity"
 testpaths = [
     "tests",
 ]
@@ -35,4 +35,9 @@ ignore_missing_imports = true
 exclude = [
     "docs/*",
     "build/*"
-    ]
\ No newline at end of file
+    ]
+
+[tool.coverage.run]
+omit = [
+    "clarity/_version.py"
+]
\ No newline at end of file
diff --git a/recipes/cec1/baseline/results/__init__.py b/recipes/cec1/baseline/results/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/recipes/cec2/baseline/evaluate.py b/recipes/cec2/baseline/evaluate.py
index 0b8761213..cd9cc1423 100644
--- a/recipes/cec2/baseline/evaluate.py
+++ b/recipes/cec2/baseline/evaluate.py
@@ -97,13 +97,13 @@ def run_calculate_SI(cfg: DictConfig) -> None:
             ref = ref_anechoic * rms_target / rms_anechoic
 
             si = haspi_v2_be(
-                xl=ref[:, 0],
-                xr=ref[:, 1],
-                yl=proc[:, 0],
-                yr=proc[:, 1],
+                reference_left=ref[:, 0],
+                reference_right=ref[:, 1],
+                processed_left=proc[:, 0],
+                processed_right=proc[:, 1],
                 fs_signal=fs_ref_anechoic,
-                audiogram_l=audiogram_left,
-                audiogram_r=audiogram_right,
+                audiogram_left=audiogram_left,
+                audiogram_right=audiogram_right,
                 audiogram_cfs=cfs,
             )
             logger.info(f"The HASPI score is {si}")
@@ -121,13 +121,13 @@ def run_calculate_SI(cfg: DictConfig) -> None:
                 )
                 unproc = unproc / 32768.0
                 si_unproc = haspi_v2_be(
-                    xl=ref[:, 0],
-                    xr=ref[:, 1],
-                    yl=unproc[:, 0],
-                    yr=unproc[:, 1],
+                    reference_left=ref[:, 0],
+                    reference_right=ref[:, 1],
+                    processed_left=unproc[:, 0],
+                    processed_right=unproc[:, 1],
                     fs_signal=fs_ref_anechoic,
-                    audiogram_l=audiogram_left,
-                    audiogram_r=audiogram_right,
+                    audiogram_left=audiogram_left,
+                    audiogram_right=audiogram_right,
                     audiogram_cfs=cfs,
                 )
                 logger.info(f"The unprocessed signal HASPI score is {si_unproc}")
diff --git a/recipes/cec2/baseline/exp/__init__.py b/recipes/cec2/baseline/exp/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/recipes/cpc1/e029_sheffield/prepare_data.py b/recipes/cpc1/e029_sheffield/prepare_data.py
index 03170ec99..fd101bb95 100644
--- a/recipes/cpc1/e029_sheffield/prepare_data.py
+++ b/recipes/cpc1/e029_sheffield/prepare_data.py
@@ -151,7 +151,6 @@ def generate_data_split(
     if_msbg=False,
     if_ref=False,
 ):
-
     with open(orig_data_json, "r") as f:
         all_data_list = json.load(f)
 
diff --git a/recipes/cpc1/e029_sheffield/train_asr.py b/recipes/cpc1/e029_sheffield/train_asr.py
index 8d9caa443..c001ee163 100644
--- a/recipes/cpc1/e029_sheffield/train_asr.py
+++ b/recipes/cpc1/e029_sheffield/train_asr.py
@@ -148,7 +148,6 @@ def on_stage_end(self, stage, stage_loss, epoch):
 
         # log stats and save checkpoint at end-of-epoch
         if stage == sb.Stage.VALID and sb.utils.distributed.if_main_process():
-
             # report different epoch stages according current stage
             current_epoch = self.hparams.epoch_counter.current
             if current_epoch <= self.hparams.stage_one_epochs:
@@ -224,7 +223,6 @@ def on_fit_start(self):
 
             # Load latest checkpoint to resume training if interrupted
             if self.checkpointer is not None:
-
                 # do not reload the weights if training is interrupted right before stage 2
                 group = current_optimizer.param_groups[0]
                 if "momentum" not in group:
diff --git a/recipes/cpc1/e032_sheffield/prepare_data.py b/recipes/cpc1/e032_sheffield/prepare_data.py
index 03170ec99..fd101bb95 100644
--- a/recipes/cpc1/e032_sheffield/prepare_data.py
+++ b/recipes/cpc1/e032_sheffield/prepare_data.py
@@ -151,7 +151,6 @@ def generate_data_split(
     if_msbg=False,
     if_ref=False,
 ):
-
     with open(orig_data_json, "r") as f:
         all_data_list = json.load(f)
 
diff --git a/recipes/cpc1/e032_sheffield/train_asr.py b/recipes/cpc1/e032_sheffield/train_asr.py
index 129a05ca1..896b02df7 100644
--- a/recipes/cpc1/e032_sheffield/train_asr.py
+++ b/recipes/cpc1/e032_sheffield/train_asr.py
@@ -148,7 +148,6 @@ def on_stage_end(self, stage, stage_loss, epoch):
 
         # log stats and save checkpoint at end-of-epoch
         if stage == sb.Stage.VALID and sb.utils.distributed.if_main_process():
-
             # report different epoch stages according current stage
             current_epoch = self.hparams.epoch_counter.current
             if current_epoch <= self.hparams.stage_one_epochs:
@@ -226,7 +225,6 @@ def on_fit_start(self):
 
             # Load latest checkpoint to resume training if interrupted
             if self.checkpointer is not None:
-
                 # do not reload the weights if training is interrupted right before stage 2
                 group = current_optimizer.param_groups[0]
                 if "momentum" not in group:
diff --git a/recipes/icassp_2023/README.md b/recipes/icassp_2023/README.md
new file mode 100644
index 000000000..c8f41d4e1
--- /dev/null
+++ b/recipes/icassp_2023/README.md
@@ -0,0 +1,153 @@
+# The ICASSP 2023 Clarity Enhancement Challenge (CEC_ICASSP2023)
+
+Clarity challenge code for the ICASSP 2023 Clarity Enhancement Challenge.
+
+For more information please visit the [challenge website](https://claritychallenge.org/docs/icassp2023/icassp2023_intro).
+
+Clarity tutorials are [now available](https://claritychallenge.github.io/clarity_CC_doc/tutorials). The tutorials introduce the Clarity installation, how to interact with Clarity metadata, and also provide examples of baseline systems and evaluation tools.
+
+## Data structure
+
+The ICASSP 2023 Clarity Enhancement Challenge is using the Clarity CEC2 dataset. To download data, please visit [here](https://mab.to/KjXsa3EskhQuU). The data is split into three packages: `clarity_CEC2_core.v1_1.tgz` [28 GB], `clarity_CEC2_train.v1_1.tgz` [69 GB] and `clarity_CEC2_hoairs.v1_0.tgz` [144 GB].
+
+Unpack packages under the same root directory using
+
+```bash
+tar -xvzf <PACKAGE_NAME>
+```
+
+**Core** contains metadata and development set signals, which can be used for validate existing systems
+
+```text
+clarity_data
+|   hrir/HRIRs_MAT 167M
+|
+└───dev
+|   └───rooms
+|   |   |   ac 20M
+|   |   |   rpf 79M
+|   |
+|   └───interferers
+|   |   |   music 5.8G
+|   |   |   noise 587M
+|   |   |   speech 1.4G
+|   |
+|   └───scenes 39G
+|   |
+|   └───targets 1.3G
+|   |
+|   └───speaker_adapt 20M
+|
+└───metadata
+    |   scenes.train.json
+    |   scenes.dev.json
+    |   rooms.train.json
+    |   rooms.dev.json
+    |   masker_music_list.json
+    |   masker_nonspeech_list.json
+    |   masker_speech_list.json
+    |   target_speech_list.json
+    |   hrir_data.json
+    |   listeners.json
+    |   scenes_listeners.dev.json
+    |   ...
+
+```
+
+**Train** contains training set, which can be used to optimise a system
+
+```text
+clarity_data
+└───train
+    └───rooms
+    |   |   ac 48M
+    |   |   rpf 190M
+    |
+    └───interferers
+    |   |   music 16GG
+    |   |   noise 3.9M
+    |   |   speech 4.5G
+    |
+    └───scenes 89G
+    |
+    └───targets 2.8G
+
+```
+
+**HOA_IRs** contains impulse responses for reproducing the scenes or for rendering more training data (scenes).
+
+```text
+clarity_data
+└───train/rooms/HOA_IRs 117G
+|
+└───dev/rooms/HOA_IRs 49G
+```
+
+## Baseline
+
+In the `baseline/' folder, we provide code for running the baseline enhancement system and performing the objective evaluation.
+
+### Enhancement
+
+The baseline enhancement simply takes the 6 channel hearing aid inputs and reduces this to a stereo hearing aid output by passing through the 'front' microphone signal of the left and right ear.
+
+To run the baseline enhancement system, firstly specify `root` in `config.yaml` to point to where you have installed the clarity data. You can also define your own `path.exp_folder` to store enhanced signals and evaluated results.
+
+
+Then run:
+
+```bash
+python enhance.py
+```
+
+Alternatively, you can provide the root variable on the command line, e.g.,
+
+```bash
+python enhance.py path.root=/Volumes/data/clarity_CEC2_data
+```
+
+The folder `enhanced_signals` will appear in the `exp` folder.
+
+### Evaluation
+
+The `evaluate.py`  will first pass signals through a provided hearing aid amplification stage using a NAL-R [[1](#references)] fitting amplification and a simple automatic gain compressor. The amplification is determined by the audiograms defined by the scene-listener pairs in `clarity_data/metadata/scenes_listeners.dev.json` for the development set. After amplification, the evaluate function calculates the better-ear HASPI  [[2](#references)] and better-ear HASQI  [[3](#references)] scores. The average of these two is computed and returned for each signal.
+
+To run the evaluation stage, make sure that `path.root` is set in the `config.yaml` file and then run
+
+```bash
+python evaluate.py
+```
+
+The full evaluation set is 7500 scene-listener pairs and will take a long time to run. A standard small set which uses 1/15 of the data has been defined and can be run with
+
+```bash
+python evaluate.py evaluate.small_test=True
+```
+
+A csv file containing the HASPI, HASQI and combined scores will be generated in the `exp_folder`.
+
+When computing HASPI and HASQI, the `_target_anechoic_CH1.wav` is used as the reference, with its level normalised to match that of the corresponding `_target_CH1.wav`.
+
+### Reporting results
+
+Once the evaluation script has completed, the final result can be reported with
+
+```bash
+python report_score.py
+```
+
+Or if you have run the small evaluation
+
+```bash
+python report_score.py evaluate.small_test=True
+```
+
+The score for the baseline enhancement is 0.185 overall (0.239 HASPI; 0.132 HASQI).
+
+Please note: HASPI and HASQI employ random thresholding noise so you will not get identical scores unless the random seed is set (in the given recipe, the random seed for each signal is set the last eight digits of the scene md5). However, if the seed is not set the differences between runs should be small (order of 1e-6).
+
+## References
+
+* [1] Byrne, Denis, and Harvey Dillon. "The National Acoustic Laboratories'(NAL) new procedure for selecting the gain and frequency response of a hearing aid." Ear and hearing 7.4 (1986): 257-265.
+* [2] Kates J M, Arehart K H. The hearing-aid speech perception index (HASPI)[J]. Speech Communication, 2014, 65: 75-93.
+* [3] Kates, J.M. and Arehart, K.H., 2014. "The hearing-aid speech quality index (HASQI) version 2". Journal of the Audio Engineering Society. 62 (3): 99–117.
diff --git a/recipes/icassp_2023/__init__.py b/recipes/icassp_2023/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/recipes/icassp_2023/baseline/__init__.py b/recipes/icassp_2023/baseline/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/recipes/icassp_2023/baseline/config.yaml b/recipes/icassp_2023/baseline/config.yaml
new file mode 100644
index 000000000..66b96dbba
--- /dev/null
+++ b/recipes/icassp_2023/baseline/config.yaml
@@ -0,0 +1,30 @@
+path:
+  root: ???
+  metadata_dir: ${path.root}/clarity_data/metadata
+  scenes_listeners_file: ${path.metadata_dir}/scenes_listeners.dev.json
+  listeners_file: ${path.metadata_dir}/listeners.json
+  scenes_folder: ${path.root}/clarity_data/dev/scenes
+  exp_folder: ./exp # folder to store enhanced signals and final results
+
+
+nalr:
+  nfir: 220
+  fs: 44100
+
+compressor:
+  threshold: 0.35
+  attenuation: 0.1
+  attack: 50
+  release: 1000
+  rms_buffer_size: 0.064
+
+soft_clip: True
+
+evaluate:
+  set_random_seed: True
+  small_test: False
+
+# hydra config
+hydra:
+  run:
+    dir: ${path.exp_folder}
diff --git a/recipes/icassp_2023/baseline/enhance.py b/recipes/icassp_2023/baseline/enhance.py
new file mode 100644
index 000000000..bd840cd5d
--- /dev/null
+++ b/recipes/icassp_2023/baseline/enhance.py
@@ -0,0 +1,63 @@
+""" Run the dummy enhancement. """
+import json
+import logging
+import pathlib
+
+import hydra
+import numpy as np
+from evaluate import make_scene_listener_list
+from omegaconf import DictConfig
+from scipy.io import wavfile
+from tqdm import tqdm
+
+logger = logging.getLogger(__name__)
+
+
+@hydra.main(config_path=".", config_name="config")
+def enhance(cfg: DictConfig) -> None:
+    """Run the dummy enhancement."""
+
+    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:
+        scenes_listeners = json.load(fp)
+
+    with open(cfg.path.listeners_file, "r", encoding="utf-8") as fp:
+        listener_audiograms = json.load(fp)  # noqa: F841
+
+    # Make list of all scene listener pairs that will be run
+    scene_listener_pairs = make_scene_listener_list(
+        scenes_listeners, cfg.evaluate.small_test
+    )
+
+    for scene, listener in tqdm(scene_listener_pairs):
+        sample_freq, signal = wavfile.read(
+            pathlib.Path(cfg.path.scenes_folder) / f"{scene}_mix_CH1.wav"
+        )
+
+        # Convert to 32-bit floating point scaled between -1 and 1
+        signal = (signal / 32768.0).astype(np.float32)
+
+        # # Audiograms can read like this, but they are not needed for the baseline
+        #
+        # cfs = np.array(listener_audiograms[listener]["audiogram_cfs"])
+        #
+        # audiogram_left = np.array(
+        #    listener_audiograms[listener]["audiogram_levels_l"]
+        # )
+        # audiogram_right = np.array(
+        #    listener_audiograms[listener]["audiogram_levels_r"]
+        # )
+
+        # Baseline just reads the signal from the front microphone pair
+        # and write it out as the enhanced signal
+        #
+
+        wavfile.write(
+            enhanced_folder / f"{scene}_{listener}_enhanced.wav", sample_freq, signal
+        )
+
+
+if __name__ == "__main__":
+    enhance()
diff --git a/recipes/icassp_2023/baseline/evaluate.py b/recipes/icassp_2023/baseline/evaluate.py
new file mode 100644
index 000000000..745decb92
--- /dev/null
+++ b/recipes/icassp_2023/baseline/evaluate.py
@@ -0,0 +1,178 @@
+"""Evaluate the enhanced signals using a combination of HASPI and HASQI metrics"""
+import csv
+import hashlib
+import json
+import logging
+import pathlib
+
+import hydra
+import numpy as np
+from omegaconf import DictConfig
+from scipy.io import wavfile
+from tqdm import tqdm
+
+from clarity.enhancer.compressor import Compressor
+from clarity.enhancer.nalr import NALR
+from clarity.evaluator.haspi import haspi_v2_be
+from clarity.evaluator.hasqi import hasqi_v2_better_ear
+
+logger = logging.getLogger(__name__)
+
+
+def amplify_signal(signal, bin_audiogram, ear, enhancer, compressor):
+    """Amplify signal for either left (l) or right (r) ear"""
+    cfs = np.array(bin_audiogram["audiogram_cfs"])
+    audiogram = np.array(bin_audiogram[f"audiogram_levels_{ear}"])
+    nalr_fir, _ = enhancer.build(audiogram, cfs)
+    out = enhancer.apply(nalr_fir, signal)
+    out, _, _ = compressor.process(out)
+    return out
+
+
+def set_scene_seed(scene):
+    """Set a seed that is unique for the given scene"""
+    scene_encoded = hashlib.md5(scene.encode("utf-8")).hexdigest()
+    scene_md5 = int(scene_encoded, 16) % (10**8)
+    np.random.seed(scene_md5)
+
+
+def compute_metric(metric, signal, ref, audiogram, fs_signal):
+    """Compute HASPI or HASQI metric"""
+    score = metric(
+        xl=ref[:, 0],
+        xr=ref[:, 1],
+        yl=signal[:, 0],
+        yr=signal[:, 1],
+        fs_signal=fs_signal,
+        audiogram_l=audiogram["audiogram_levels_l"],
+        audiogram_r=audiogram["audiogram_levels_r"],
+        audiogram_cfs=audiogram["audiogram_cfs"],
+    )
+    return score
+
+
+class ResultsFile:
+    """Class to write results to a CSV file"""
+
+    def __init__(self, file_name):
+        self.file_name = file_name
+
+    def write_header(self):
+        with open(self.file_name, "w", encoding="utf-8") as csv_f:
+            csv_writer = csv.writer(
+                csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
+            )
+            csv_writer.writerow(["scene", "listener", "combined", "haspi", "hasqi"])
+
+    def add_result(self, scene, listener, score, haspi, hasqi):
+        """Add a result to the CSV file"""
+
+        logger.info(f"The combined score is {score} (haspi {haspi}, hasqi {hasqi})")
+
+        with open(self.file_name, "a", encoding="utf-8") as csv_f:
+            csv_writer = csv.writer(
+                csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
+            )
+            csv_writer.writerow([scene, listener, str(score), str(haspi), str(hasqi)])
+
+
+def make_scene_listener_list(scenes_listeners, small_test=False):
+    """Make the list of scene-listener pairing to process"""
+    scene_listener_pairs = [
+        (scene, listener)
+        for scene in scenes_listeners
+        for listener in scenes_listeners[scene]
+    ]
+
+    # Can define a standard 'small_test' with just 1/15 of the data
+    if small_test:
+        scene_listener_pairs = scene_listener_pairs[::15]
+
+    return scene_listener_pairs
+
+
+@hydra.main(config_path=".", config_name="config")
+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:
+        scenes_listeners = json.load(fp)
+
+    with open(cfg.path.listeners_file, "r", encoding="utf-8") as fp:
+        listener_audiograms = json.load(fp)
+
+    enhancer = NALR(**cfg.nalr)
+    compressor = Compressor(**cfg.compressor)
+
+    enhanced_folder = pathlib.Path("enhanced_signals")
+    amplified_folder = pathlib.Path("amplified_signals")
+    scenes_folder = pathlib.Path(cfg.path.scenes_folder)
+    amplified_folder.mkdir(parents=True, exist_ok=True)
+
+    # Make list of all scene listener pairs that will be run
+
+    scene_listener_pairs = make_scene_listener_list(
+        scenes_listeners, cfg.evaluate.small_test
+    )
+
+    results_file = ResultsFile("scores.csv")
+    results_file.write_header()
+
+    for scene, listener in tqdm(scene_listener_pairs):
+        logger.info(f"Running evaluation: scene {scene}, listener {listener}")
+
+        if cfg.evaluate.set_random_seed:
+            set_scene_seed(scene)
+
+        # Read signals
+
+        fs_signal, signal = wavfile.read(
+            enhanced_folder / f"{scene}_{listener}_enhanced.wav"
+        )
+        fs_ref_anechoic, ref_anechoic = wavfile.read(
+            scenes_folder / f"{scene}_target_anechoic_CH1.wav"
+        )
+        fs_ref_target, ref_target = wavfile.read(
+            scenes_folder / f"{scene}_target_CH1.wav"
+        )
+        ref_anechoic = ref_anechoic / 32768.0
+        ref_target = ref_target / 32768.0
+
+        assert fs_ref_anechoic == fs_ref_target == fs_signal == cfg.nalr.fs
+
+        # amplify left and right ear signals
+        audiogram = listener_audiograms[listener]
+
+        out_l = amplify_signal(signal[:, 0], audiogram, "l", enhancer, compressor)
+        out_r = amplify_signal(signal[:, 1], audiogram, "r", enhancer, compressor)
+        amplified = np.stack([out_l, out_r], axis=1)
+
+        if cfg.soft_clip:
+            amplified = np.tanh(amplified)
+
+        wavfile.write(
+            amplified_folder / f"{scene}_{listener}_HA-output.wav",
+            fs_signal,
+            amplified.astype(np.float32),
+        )
+
+        # Evaluate the amplified signal
+
+        rms_target = np.mean(ref_target**2, axis=0) ** 0.5
+        rms_anechoic = np.mean(ref_anechoic**2, axis=0) ** 0.5
+        ref = ref_anechoic * rms_target / rms_anechoic
+
+        haspi_score = compute_metric(haspi_v2_be, amplified, ref, audiogram, fs_signal)
+        hasqi_score = compute_metric(
+            hasqi_v2_better_ear, amplified, ref, audiogram, fs_signal
+        )
+        score = 0.5 * (hasqi_score + haspi_score)
+
+        results_file.add_result(
+            scene, listener, score=score, haspi=haspi_score, hasqi=hasqi_score
+        )
+
+
+if __name__ == "__main__":
+    run_calculate_si()  # noqa
diff --git a/recipes/icassp_2023/baseline/report_score.py b/recipes/icassp_2023/baseline/report_score.py
new file mode 100644
index 000000000..faace37ff
--- /dev/null
+++ b/recipes/icassp_2023/baseline/report_score.py
@@ -0,0 +1,44 @@
+""" Run the dummy enhancement. """
+import json
+import logging
+
+import hydra
+import pandas as pd
+from evaluate import make_scene_listener_list
+from omegaconf import DictConfig
+
+logger = logging.getLogger(__name__)
+
+
+@hydra.main(config_path=".", config_name="config")
+def report_score(cfg: DictConfig) -> None:
+    """Run the dummy enhancement."""
+
+    with open(cfg.path.scenes_listeners_file, "r", encoding="utf-8") as fp:
+        scenes_listeners = json.load(fp)
+
+    results_df = pd.read_csv("scores.csv")
+
+    # Make list of all scene listener pairs that are expected in results file
+    scene_listener_pairs = make_scene_listener_list(
+        scenes_listeners, cfg.evaluate.small_test
+    )
+    selection_df = pd.DataFrame(scene_listener_pairs, columns=["scene", "listener"])
+
+    # Select the expected scene listener pairs from the results file
+    selected_results_df = pd.merge(results_df, selection_df, on=["scene", "listener"])
+
+    if len(selected_results_df) != len(selection_df):
+        print("The following results were not found:")
+        difference = pd.concat(
+            [selected_results_df[["scene", "listener"]], selection_df]
+        ).drop_duplicates(keep=False)
+        print(difference)
+    else:
+        # All expected results were found so report the mean score
+        print(f"Scores based on {len(selected_results_df)} scenes.")
+        print(selected_results_df[["haspi", "hasqi", "combined"]].mean(axis=0))
+
+
+if __name__ == "__main__":
+    report_score()
diff --git a/setup.cfg b/setup.cfg
index 36d756309..f134700f5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,5 @@
 [metadata]
 name = pyclarity
-version = 0.1.0
 description = Tools for the Clarity Challenge
 long_description = file: README.md,
 long_description_content_type = text/markdown
@@ -84,18 +83,25 @@ dev =
   isort
   mypy
   pre-commit
+  py-cpuinfo
   pycodestyle
   pytest
-  pytest-regtest
   pytest-cov
+  pytest-regtest
   yamllint
 
 pypi =
   build
+  versioneer
+  wheel
 
 [options.package_data]
 
-* = *.mat, *.yaml
+* =
+  *.csv
+  *.json
+  *.mat
+  *.yaml
 
 [flake8]
 ignore = E203, E501, W503
@@ -117,3 +123,11 @@ source =
 # FIXME : Is an entry_point required (https://setuptools.pypa.io/en/latest/userguide/entry_point.html)
 # [options.entry_points]
 # console_scripts =
+
+[versioneer]
+VCS = git
+style = pep440
+versionfile_source = clarity/_version.py
+versionfile_build = clarity/_version.py
+tag_prefix = v
+parentdir_prefix =
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 000000000..681075881
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,8 @@
+"""Package setup"""
+import versioneer
+from setuptools import setup
+
+setup(
+    version=versioneer.get_version(),
+    cmdclass=versioneer.get_cmdclass(),
+)


Test patch:
diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml
index 9ebc632f2..ba14b7d7d 100644
--- a/.github/workflows/run_tests.yml
+++ b/.github/workflows/run_tests.yml
@@ -20,7 +20,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Set up Python
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v4.3.0
         with:
           python-version: ${{ matrix.python-version }}
       - name: Install dependencies
@@ -33,6 +33,9 @@ jobs:
           pip install -e .[tests]
           pip install -e .[docs]
           pip install -e .[dev]
+      - name: CPU information
+        run: |
+          python -m cpuinfo --json
       - name: Run pytest
         run: |
           pytest --cov=clarity .
diff --git a/recipes/cpc1/test_listener_responses/__init__.py b/recipes/cpc1/test_listener_responses/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/data/test_HOA_tools_cec2.py b/tests/data/test_HOA_tools_cec2.py
index 2bbdeeb13..76d66bc1a 100644
--- a/tests/data/test_HOA_tools_cec2.py
+++ b/tests/data/test_HOA_tools_cec2.py
@@ -2,7 +2,7 @@
 import numpy as np
 import pytest
 
-from clarity.data.HOA_tools_cec2 import (  # HOARotator,; ambisonic_convolve,; binaural_mixdown,; compute_band_rotation,; compute_rotation_matrix,; dB_to_gain,; dot,; smoothstep,
+from clarity.data.HOA_tools_cec2 import (  # HOARotator,; ambisonic_convolve,; binaural_mixdown,; compute_band_rotation,; compute_rotation_matrix,;  dot,; dot,
     P,
     U,
     V,
@@ -10,9 +10,11 @@
     centred_element,
     compute_rms,
     compute_UVW_coefficients,
+    dB_to_gain,
     equalise_rms_levels,
     rotation_control_vector,
     rotation_vector,
+    smoothstep,
 )
 
 
@@ -22,7 +24,7 @@ def test_compute_rotation_matrix() -> None:
 
 
 @pytest.mark.parametrize(
-    "i, j, expected",
+    "row, col, expected",
     [
         (1, 1, 0.8840922122960315),
         (45, 54, 0.2378095805520779),
@@ -30,22 +32,22 @@ def test_compute_rotation_matrix() -> None:
     ],
 )
 def test_centred_element(
-    random_matrix: np.ndarray, i: int, j: int, expected: float
+    random_matrix: np.ndarray, row: int, col: int, expected: float
 ) -> None:
     """Test for centered_element() function."""
-    assert centred_element(random_matrix, i, j) == expected
+    assert centred_element(random_matrix, row, col) == expected
 
 
 def test_centred_element_index_error(random_matrix: np.ndarray) -> None:
     """Test centered_element() raises an IndexError if centering is outside of matrix dimensions."""
     with pytest.raises(IndexError):
-        centred_element(random_matrix, i=101, j=2)
+        centred_element(random_matrix, row=101, col=2)
     with pytest.raises(IndexError):
-        centred_element(random_matrix, i=1, j=102)
+        centred_element(random_matrix, row=1, col=102)
 
 
 @pytest.mark.parametrize(
-    "i, a, b, el, expected",
+    "i, a, b, order, expected",
     [
         (1, 1, 2, 2, -0.3475073926260553),
         (1, 1, 2, -2, 0.6814353682034401),
@@ -53,39 +55,58 @@ def test_centred_element_index_error(random_matrix: np.ndarray) -> None:
     ],
 )
 def test_P(
-    i: int, a: int, b: int, el: int, random_matrix: np.ndarray, expected: float
+    i: int, a: int, b: int, order: int, random_matrix: np.ndarray, expected: float
 ) -> None:
     """Test for P() function."""
     # FixMe : how long is the list of matrices that is passed in?
-    assert P(i, a, b, el, r=[random_matrix, random_matrix, random_matrix]) == expected
+    assert (
+        P(
+            i,
+            a,
+            b,
+            order,
+            rotation_matrices=[random_matrix, random_matrix, random_matrix],
+        )
+        == expected
+    )
 
 
 def test_P_index_error(random_matrix: np.ndarray) -> None:
     """Test P() raises IndexError if invalid indices to r are provided."""
     # FixMe : how long is the list of matrices that is passed in?
     with pytest.raises(IndexError):
-        assert P(i=1, a=5, b=1, el=1, r=[random_matrix])
+        assert P(i=1, a=5, b=1, order=1, rotation_matrices=[random_matrix])
     with pytest.raises(IndexError):
-        assert P(i=1, a=5, b=1, el=-1, r=[random_matrix])
+        assert P(i=1, a=5, b=1, order=-1, rotation_matrices=[random_matrix])
     with pytest.raises(IndexError):
-        assert P(i=1, a=5, b=4, el=3, r=[random_matrix])
+        assert P(i=1, a=5, b=4, order=3, rotation_matrices=[random_matrix])
 
 
 @pytest.mark.parametrize(
-    "m, n, el, expected",
+    "degree, n, order, expected",
     [
         (1, 2, 2, 0.8066335578037782),
         (1, 2, -2, 0.647932971103906),
         (1, 2, 2, 0.504340149676519),
     ],
 )
-def test_U(m: int, n: int, el: int, random_matrix: np.ndarray, expected: float) -> None:
-    """Test for XX function."""
-    assert U(m, n, el, r=[random_matrix, random_matrix, random_matrix]) == expected
+def test_U(
+    degree: int, n: int, order: int, random_matrix: np.ndarray, expected: float
+) -> None:
+    """Test for U function."""
+    assert (
+        U(
+            degree,
+            n,
+            order,
+            rotation_matrices=[random_matrix, random_matrix, random_matrix],
+        )
+        == expected
+    )
 
 
 @pytest.mark.parametrize(
-    "m, n, el, expected",
+    "degree, n, order, expected",
     [
         (0, 2, 2, -0.02607000059684761),
         (1, 2, 2, -0.8041211269599031),
@@ -93,26 +114,46 @@ def test_U(m: int, n: int, el: int, random_matrix: np.ndarray, expected: float)
         (-1, 2, -2, 0.5668224726503112),
     ],
 )
-def test_V(m: int, n: int, el: int, random_matrix: np.ndarray, expected: float) -> None:
+def test_V(
+    degree: int, n: int, order: int, random_matrix: np.ndarray, expected: float
+) -> None:
     """Test for V() function."""
-    assert V(m, n, el, r=[random_matrix, random_matrix, random_matrix]) == expected
+    assert (
+        V(
+            degree,
+            n,
+            order,
+            rotation_matrices=[random_matrix, random_matrix, random_matrix],
+        )
+        == expected
+    )
 
 
 @pytest.mark.parametrize(
-    "m, n, el, expected",
+    "degree, n, order, expected",
     [
         (0, 2, 2, 0.0),
         (1, 2, 2, -0.18322112315448064),
         (-1, 2, 2, -0.3501066433176404),
     ],
 )
-def test_W(m: int, n: int, el: int, random_matrix: np.ndarray, expected: float) -> None:
+def test_W(
+    degree: int, n: int, order: int, random_matrix: np.ndarray, expected: float
+) -> None:
     """Test for W() function."""
-    assert W(m, n, el, r=[random_matrix, random_matrix, random_matrix]) == expected
+    assert (
+        W(
+            degree,
+            n,
+            order,
+            rotation_matrices=[random_matrix, random_matrix, random_matrix],
+        )
+        == expected
+    )
 
 
 @pytest.mark.parametrize(
-    "m, n, el, expected",
+    "degree, n, order, expected",
     [
         (0, 2, 2, (0.5773502691896257, -0.28867513459481287, -0.0)),
         (1, 2, 2, (0.5, 0.3535533905932738, -0.0)),
@@ -120,15 +161,17 @@ def test_W(m: int, n: int, el: int, random_matrix: np.ndarray, expected: float)
         # (-1, 2, 1, (-0.0, np.nan, -0.0)),  # FixMe : This is probably going to cause problems how to capture?
     ],
 )
-def test_compute_UVW_coefficients(m: int, n: int, el: int, expected: float) -> None:
+def test_compute_UVW_coefficients(
+    degree: int, n: int, order: int, expected: float
+) -> None:
     """Test for computer_UVW_coefficients() function."""
-    assert compute_UVW_coefficients(m, n, el) == expected
+    assert compute_UVW_coefficients(degree, n, order) == expected
 
 
 def test_compute_UVW_coefficients_zero_division_error() -> None:
     """Test computer_UVW_coefficients() raises ZeroDivisionError"""
     with pytest.raises(ZeroDivisionError):
-        compute_UVW_coefficients(m=-1, n=2, el=-2)
+        compute_UVW_coefficients(degree=-1, n=2, order=-2)
 
 
 # FixMe : Not yet working, I don't understand how to get the `output` passed in correctly
@@ -152,7 +195,7 @@ def test_compute_UVW_coefficients_zero_division_error() -> None:
 #     ],
 # )
 # def test_dot(A: np.ndarray, B: np.ndarray, expected: np.ndarray) -> None:
-#     """Test for XX function."""
+#     """Test for dot() function."""
 #     np.testing.assert_array_equal(dot(A, B), expected)
 
 
@@ -161,6 +204,7 @@ def test_HOARotator() -> None:
     assert True
 
 
+# FixMe : need examples of hrir_metadata dictionary to be able to write a tests for this
 def test_binaural_mixdown() -> None:
     """Test for binaural_mixdown() function."""
     assert True
@@ -204,14 +248,52 @@ def test_equalise_rms_levels(random_matrix: np.ndarray) -> None:
     assert equalise_rms_levels(inputs=[random_matrix, random_matrix])
 
 
-def test_dB_to_gain() -> None:
+@pytest.mark.parametrize(
+    "db, gain", [(10, 3.1622776601683795), (20, 10.0), (0.045, 1.0051942600951387)]
+)
+def test_dB_to_gain(db: float, gain: float) -> None:
     """Test for XX function."""
-    assert True
+    assert dB_to_gain(db) == gain
 
 
-def test_smoothstep() -> None:
-    """Test for XX function."""
-    assert True
+@pytest.mark.parametrize(
+    "x, x_min, x_max, N, expected",
+    [
+        (
+            np.asarray([100, 200, 300, 400]),
+            10,
+            300,
+            1,
+            np.asarray([0.229161, 0.725286, 1.0, 1.0]),
+        ),
+        (
+            np.asarray([10, 200, 300, 4000]),
+            10,
+            300,
+            1,
+            np.asarray([0.0, 0.725286, 1.0, 1.0]),
+        ),
+        (
+            np.asarray([10, -200, 300, 4000]),
+            -200,
+            300,
+            1,
+            np.asarray([0.381024, 0.0, 1.0, 1.0]),
+        ),
+        (
+            np.asarray([10, -50, 300, 4000]),
+            -200,
+            300,
+            1,
+            np.asarray([0.381024, 0.216, 1.0, 1.0]),
+        ),
+    ],
+)
+def test_smoothstep(
+    x: np.ndarray, x_min: float, x_max: float, N: int, expected
+) -> None:
+    """Test for smoothstep() function."""
+    np.testing.assert_array_almost_equal(smoothstep(x, x_min, x_max, N), expected)
 
 
 @pytest.mark.parametrize(
@@ -294,7 +376,6 @@ def test_rotation_vector(
         rotation_vector(start_angle, end_angle, signal_length, start_idx, end_idx),
         expected,
     )
-    assert True
 
 
 def test_rotation_vector_floating_point_error() -> None:
diff --git a/tests/enhancer/test_nalr.py b/tests/enhancer/test_nalr.py
index b1b514e77..8c2e3ffff 100644
--- a/tests/enhancer/test_nalr.py
+++ b/tests/enhancer/test_nalr.py
@@ -12,13 +12,13 @@
     [
         (
             np.array([45, 45, 35, 45, 60, 65]),
-            np.array([250, 500, 1000, 2000, 3000, 6000]),
-            4.167028379280711e-05,
+            np.array([250, 500, 1000, 2000, 4000, 6000]),
+            3.9861248204486366e-05,
         ),
         (
             np.array([45, 45, 35, 45, 60, 65]),
             None,
-            4.167028379280711e-05,
+            3.9861248204486366e-05,
         ),
     ],
 )
diff --git a/tests/regression/_regtest_outputs/test_predictors.test_torch_msbg_stoi.out b/tests/regression/_regtest_outputs/test_predictors.test_torch_msbg_stoi_non_xeon_e5_2673_cpu.out
similarity index 100%
rename from tests/regression/_regtest_outputs/test_predictors.test_torch_msbg_stoi.out
rename to tests/regression/_regtest_outputs/test_predictors.test_torch_msbg_stoi_non_xeon_e5_2673_cpu.out
diff --git a/tests/regression/_regtest_outputs/test_predictors.test_torch_msbg_stoi_xeon_e5_2673_cpu.out b/tests/regression/_regtest_outputs/test_predictors.test_torch_msbg_stoi_xeon_e5_2673_cpu.out
new file mode 100644
index 000000000..ee679816e
--- /dev/null
+++ b/tests/regression/_regtest_outputs/test_predictors.test_torch_msbg_stoi_xeon_e5_2673_cpu.out
@@ -0,0 +1,1 @@
+Torch MSBG STOILoss -0.46198, ESTOILoss -0.32999
diff --git a/tests/regression/test_CEC2_scene_builder.py b/tests/regression/test_CEC2_scene_builder.py
index c2aab83b5..fc85f956c 100644
--- a/tests/regression/test_CEC2_scene_builder.py
+++ b/tests/regression/test_CEC2_scene_builder.py
@@ -10,7 +10,6 @@
 
 
 def test_CEC2_scene_builder(regtest):
-
     cfg = OmegaConf.load("tests/test_data/configs/test_CEC2_scene_builder.yaml")
     cfg.path.root = "tests"
     set_random_seed(cfg.random_seed)
diff --git a/tests/regression/test_engine_losses.py b/tests/regression/test_engine_losses.py
index 3cbba3386..8ee867cd4 100644
--- a/tests/regression/test_engine_losses.py
+++ b/tests/regression/test_engine_losses.py
@@ -6,7 +6,6 @@
 
 
 def test_sisnr_loss(regtest):
-
     torch.manual_seed(0)
     torch.set_num_threads(1)
     si_snr_loss = SISNRLoss()
@@ -18,7 +17,6 @@ def test_sisnr_loss(regtest):
 
 
 def test_snr_loss(regtest):
-
     torch.manual_seed(0)
     torch.set_num_threads(1)
     snr_loss = SNRLoss()
@@ -30,7 +28,6 @@ def test_snr_loss(regtest):
 
 
 def test_stoi_loss(regtest):
-
     torch.manual_seed(0)
     torch.set_num_threads(1)
     stoi_loss = STOILoss(sr=16000)
@@ -42,7 +39,6 @@ def test_stoi_loss(regtest):
 
 
 def test_stoi_level_loss(regtest):
-
     torch.manual_seed(0)
     torch.set_num_threads(1)
     stoi_level_loss = STOILevelLoss(sr=16000, alpha=0.5)
diff --git a/tests/regression/test_enhancers.py b/tests/regression/test_enhancers.py
index 15615c332..1e52de9c9 100644
--- a/tests/regression/test_enhancers.py
+++ b/tests/regression/test_enhancers.py
@@ -50,7 +50,6 @@ def test_gha_audiogram(regtest):
 
 
 def test_GHA_inputs(regtest):
-
     enhancer = gha(**gha_params)
 
     infile_names = [
@@ -67,7 +66,6 @@ def test_GHA_inputs(regtest):
 
 
 def test_GHA_config(regtest):
-
     enhancer = gha(**gha_params)
     listeners = json.load(open("tests/test_data/metadata/listeners.json"))
     listener = listeners["L0001"]
diff --git a/tests/regression/test_full_CEC1_pipeline.py b/tests/regression/test_full_CEC1_pipeline.py
index c6c1a92ad..b287a4d58 100644
--- a/tests/regression/test_full_CEC1_pipeline.py
+++ b/tests/regression/test_full_CEC1_pipeline.py
@@ -70,7 +70,6 @@ def test_full_CEC1_pipeline(regtest):
     }
 
     with tempfile.TemporaryDirectory() as output_path:
-
         renderer = Renderer(
             input_path="tests/test_data",
             output_path=output_path,
@@ -113,9 +112,14 @@ def test_full_CEC1_pipeline(regtest):
         "rms_buffer_size": 0.064,
     }
 
-    fs = 44100
+    sample_frequency = 44100
 
-    msbg_ear_cfg = {"src_pos": "ff", "fs": fs, "equiv0dBSPL": 100, "ahr": 20}
+    msbg_ear_cfg = {
+        "src_pos": "ff",
+        "sample_frequency": sample_frequency,
+        "equiv_0db_spl": 100,
+        "ahr": 20,
+    }
 
     audiogram_l = np.array([45, 20, 30, 35, 30, 45, 50, 50])
     audiogram_r = np.array([45, 25, 30, 40, 30, 40, 50, 50])
diff --git a/tests/regression/test_full_CEC2_pipeline.py b/tests/regression/test_full_CEC2_pipeline.py
index a60e59352..2ddb4802c 100644
--- a/tests/regression/test_full_CEC2_pipeline.py
+++ b/tests/regression/test_full_CEC2_pipeline.py
@@ -1,4 +1,4 @@
-# Regression test
+"""Regression Tests for CEC2"""
 # Pass some random data through code and compare with reference output
 # scene_renderer, enhancer, compressor, haspi
 
@@ -94,7 +94,6 @@ def test_full_CEC2_pipeline(regtest):
     output_path = "tmp"
 
     with tempfile.TemporaryDirectory() as output_path:
-
         target, interferers, anechoic, _head_turn = scene_renderer.generate_hoa_signals(
             scene
         )
@@ -149,18 +148,16 @@ def test_full_CEC2_pipeline(regtest):
     enhanced_audio = np.tanh(enhanced_audio)
 
     sii_enhanced = haspi_v2_be(
-        xl=reference[:, 0],
-        xr=reference[:, 1],
-        yl=enhanced_audio[:, 0],
-        yr=enhanced_audio[:, 1],
+        reference_left=reference[:, 0],
+        reference_right=reference[:, 1],
+        processed_left=enhanced_audio[:, 0],
+        processed_right=enhanced_audio[:, 1],
         fs_signal=fs,
-        audiogram_l=audiogram_l,
-        audiogram_r=audiogram_r,
+        audiogram_left=audiogram_l,
+        audiogram_right=audiogram_r,
         audiogram_cfs=audiogram_cfs,
     )
 
-    print(f"Enhanced audio HASPI score is {sii_enhanced}")
-
-    regtest.write(f"Enhanced audio HASPI score is {sii_enhanced:0.7f}\n")
+    regtest.write(f"Enhanced audio HASPI score is {sii_enhanced[0]:0.7f}\n")
 
     # Enhanced audio HASPI score is 0.2994066
diff --git a/tests/regression/test_predictors.py b/tests/regression/test_predictors.py
index 49cbcc74b..6a3b079e5 100644
--- a/tests/regression/test_predictors.py
+++ b/tests/regression/test_predictors.py
@@ -1,13 +1,21 @@
-# Regression test
+""""Regression tests for Predictors"""
+import re
 
+import pytest
 import torch
+from cpuinfo import get_cpu_info
 
 from clarity.predictor.torch_msbg import MSBGHearingModel, torchloudnorm
 from clarity.predictor.torch_stoi import NegSTOILoss
 
+CPUINFO = get_cpu_info()
 
-def test_torch_msbg_stoi(regtest):
 
+@pytest.mark.skipif(
+    re.search("E5-2673", CPUINFO["brand_raw"]),
+    reason="Xeon E5-2673 CPU arch gives a different value",
+)
+def test_torch_msbg_stoi_non_xeon_e5_2673_cpu(regtest):
     torch.manual_seed(0)
     torch.set_num_threads(1)
     stoi_loss = NegSTOILoss(sample_rate=44100)
@@ -27,8 +35,31 @@ def test_torch_msbg_stoi(regtest):
     )
 
 
-def test_torchloudnorm(regtest):
+@pytest.mark.skipif(
+    not re.search("E5-2673", CPUINFO["brand_raw"]),
+    reason="Test value obtained with Xeon E5-2673",
+)
+def test_torch_msbg_stoi_xeon_e5_2673_cpu(regtest):
+    torch.manual_seed(0)
+    torch.set_num_threads(1)
+    stoi_loss = NegSTOILoss(sample_rate=44100)
+    estoi_loss = NegSTOILoss(sample_rate=44100, extended=True)
+
+    audiogram = [45, 35, 30, 45, 50, 50]
+    audiometric = [250, 500, 1000, 2000, 4000, 6000]
+    msbg = MSBGHearingModel(audiogram=audiogram, audiometric=audiometric, device="cpu")
+
+    x = torch.randn(2, 44100)
+    y = msbg(x)
+    stoi_loss = stoi_loss.forward(x.cpu(), y.cpu()).mean()
+    estoi_loss = estoi_loss.forward(x.cpu(), y.cpu()).mean()
+
+    regtest.write(
+        f"Torch MSBG STOILoss {stoi_loss:0.5f}, ESTOILoss {estoi_loss:0.5f}\n"
+    )
 
+
+def test_torchloudnorm(regtest):
     torch.manual_seed(0)
     torch.set_num_threads(1)
     ln = torchloudnorm()