Repository: claritychallenge/clarity (language: python)

Issue:
222 fix issues raised by pylint



Discussion / hints:

Fix patch:
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 4b7add1b3..af760092a 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -1,3 +1,34 @@
+# pylint compliance (https://github.com/claritychallenge/clarity/pull/224)
+1c4f7def50b63e449c1292ebcb1072a333752dc2 # Ignoring commit that fix pylint errors
+bd5f3068d3d149a2c169bcf2fe1ba42e00f04c22 # Dealt with F6401 false positives, and final, final missed issues.
+1e4d8d6b634cb08d2c75d8b360bef1a87a03f83c # Mopped up a few missed issues
+072b2ca24c3c8e7e1bd5efd2c2a199440a2184bf # Disabled missing docstrings from .pylintrc.precommit.ini
+e14f2306c6ad98ae06628f249911b4f6b3ecd6a0 # Added the .pylintrc to use for precommit
+77cb687d09721d32dec33df03f9aa260b21b533b # Dealt with W0223 missing abstract method definition warnings
+08f04ac78b576bb254a7f213de7a4b5589223921 # Fixed error introduce when dealing with exceptions
+b80f63be56d4911c1222371d815b232e94eff339 # Fixed W0221, W0222, W0237 overridden signature change issues
+154f398410bdc391b8e84f304ca4112260627434 # Fixed all W0718 and W0719 exception issues
+d2b7285866fad9e5adf140e319ebbce2f4cda374 # Fixing long line wrapping on merged commits
+eea79f3ea082c505ef76ccd699a0e47e5ee6e8ac # Merge branch 'main' into 222-fix-issues-raised-by-pylint
+23d6ef58bfdf3b07089790a2da9de6da7961a9fb # Fixed W0106 expression not assigned issue
+b05d8d7949d3300db3e8a320f4748a7701648e9a # Fixed all R1725 removing default values from super() issues
+885a3b4aa23986b4d91f29b081c3c2dd8207d713 # Fixed all C0201, C0206 - dictionary iteration issues
+4f5de575980cdce4635674986a0424113615d4a3 # Fixed all C0209 f-string issues
+42d3d86565da49bb068c38f9d5cea1696933a6fa # Fixed test that was broken by previous commit
+3a9a3116bb786e432e19c9aa502955d28c797602 # Added torch<2.0 in setup.cfg to stay on old version
+ec57cda3e9be1b5005893926bd0e36b031de9485 # Fixed a bunch of rare pylint issues. See below.
+4e9889b8e6b024fe30e3a9a20579409214eb9630 # Fixed all W0612 and W0613 unused vars and params issues
+5e491d7db7ef76b40826f26eedd2eb623cac4664 # Fixed all W0201: Attribute defined outside __init__
+00c36d1e6e646af0cbbf2f5fcdb19f42b6b7468b # Fixed all R1732 consider using with issues
+7bc62369914dd71d802ff315d7c0d3523bd6808f # Fixed all R1705 Unnecessary else after return issues
+abd4992d14ed357bde180132f80c3772ba65fe3a # Fixed all C0200 use enumerate issues
+626af27bea3ae56f0a7b4bab911d417d098c5d26 # Fixed all W1514 using open without explicit encoding issues
+18ff821ce058be8011d23436126ee0f456c56902 # Fixed all W0621 redefined from outer scoper issues
+73cdab6028d5a8320ae3031c4e28cc933b5f90af # Install pylint-pytest to fix pytest related false positives
+65f263d073f1e6c6d03cafd6e2f2232ecf4cd36b # Fixed all lines over the 88 character limit used in Black
+3ddfdab42496db024610d265120639ac6731c8c6 # Merge pull request #223 from claritychallenge/jpb/220-enable-some-pylint-error-checking-in-run_tests-github-workflow
+
+
 # PEP8 compliance - clarity/evaluators/hasqi
 14e21eff76a1350cabaa19ad26f2556892d51761
 961846ca661b5b97f0d1a75585452aed6f15c5f7
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ff816009e..06e1fcf59 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -52,11 +52,6 @@ repos:
         args: ["--extend-ignore=E203,E402,E501"]
       - id: nbqa-isort
 
-#  - repo: https://github.com/datarootsio/databooks
-#    rev: 1.0.5
-#    hooks:
-#      - id: databooks-meta
-
   - repo: https://github.com/kynan/nbstripout
     rev: 0.6.1
     hooks:
@@ -68,17 +63,34 @@ repos:
     hooks:
       - id: mypy
 
-  - repo: local
+  - repo: https://github.com/charliermarsh/ruff-pre-commit
+  # Ruff version.
+    rev: 'v0.0.245'
+    hooks:
+    - id: ruff
+
+  # Serious pylint errors that will be enforced by CI
+  - repo: https://github.com/pycqa/pylint
+    rev: v2.17.0
     hooks:
       - id: pylint
-        args: ["--rcfile=.pylintrc"]
-        name: Pylint
+        args:
+        - --errors-only
+        - --disable=E0401
+
+  # Pylint warnings that are bing skipped by CI (see skip: [pylint_warnings] below)
+  - repo: local
+    hooks:
+      - id: pylint_warnings
+        args: ["--rcfile=.pylintrc.precommit.ini"]
+        name: pylint-warnings
         entry: python -m pylint
         language: system
         files: \.py$
+        exclude: clarity/_version.py # 3rd party code that doesn't conform
 ci:
   autofix_prs: true
   autofix_commit_msg: '[pre-commit.ci] Fixing issues with pre-commit'
   autoupdate_schedule: weekly
   autoupdate_commit_msg: '[pre-commit.ci] pre-commit-autoupdate'
-  skip: [pylint] # Optionally list ids of hooks to skip on CI
+  skip: [pylint_warnings] # Optionally list ids of hooks to skip on CI
diff --git a/.pylintrc b/.pylintrc
index f837b7e3e..9a531134d 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -28,7 +28,7 @@ ignore-paths=
 
 # Files or directories matching the regex patterns are skipped. The regex
 # matches against base names, not paths.
-ignore-patterns=
+ignore-patterns=clarity/_version.py
 
 # Python code to execute, usually for sys.path manipulation such as
 # pygtk.require().
@@ -45,7 +45,7 @@ limit-inference-results=100
 
 # List of plugins (as comma separated values of python module names) to load,
 # usually to register additional checkers.
-load-plugins=
+load-plugins=pylint_pytest
 
 # Pickle collected data for later comparisons.
 persistent=yes
@@ -88,7 +88,7 @@ disable=R0801,
         useless-suppression,
         deprecated-pragma,
         use-symbolic-message-instead,
-        logging-fstring-interpolation
+        # 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
@@ -173,8 +173,7 @@ spelling-store-unknown-words=no
 
 # List of note tags to take in consideration, separated by a comma.
 notes=FIXME,
-      XXX,
-      TODO
+      XXX
 
 # Regular expression of note tags to take in consideration.
 #notes-rgx=
@@ -190,7 +189,7 @@ contextmanager-decorators=contextlib.contextmanager
 # List of members which are set dynamically and missed by pylint inference
 # system, and so shouldn't trigger E1101 when accessed. Python regular
 # expressions are accepted.
-generated-members=torch.*
+generated-members=torch.*,device
 
 # Tells whether missing members accessed in mixin class should be ignored. A
 # class is considered mixin if its name matches the mixin-class-rgx option.
@@ -289,7 +288,7 @@ indent-after-paren=4
 indent-string='    '
 
 # Maximum number of characters on a single line.
-max-line-length=100
+max-line-length=88
 
 # Maximum number of lines in a module.
 max-module-lines=1000
@@ -584,5 +583,5 @@ min-public-methods=2
 
 # Exceptions that will emit a warning when being caught. Defaults to
 # "BaseException, Exception".
-overgeneral-exceptions=BaseException,
-                       Exception
+overgeneral-exceptions=builtins.BaseException,
+                       builtins.Exception
diff --git a/.pylintrc.precommit.ini b/.pylintrc.precommit.ini
new file mode 100644
index 000000000..a0bd126dc
--- /dev/null
+++ b/.pylintrc.precommit.ini
@@ -0,0 +1,602 @@
+[MASTER]
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code.
+extension-pkg-allow-list=
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
+# for backward compatibility.)
+extension-pkg-whitelist=
+
+# Return non-zero exit code if any of these messages/categories are detected,
+# even if score is above --fail-under value. Syntax same as enable. Messages
+# specified are enabled, while categories only check already-enabled messages.
+fail-on=
+
+# Specify a score threshold to be exceeded before program exits with error.
+fail-under=10.0
+
+# Files or directories to be skipped. They should be base names, not paths.
+ignore=CVS
+
+# Add files or directories matching the regex patterns to the ignore-list. The
+# regex matches against paths and can be in Posix or Windows format.
+ignore-paths=
+
+# Files or directories matching the regex patterns are skipped. The regex
+# matches against base names, not paths.
+ignore-patterns=clarity/_version.py
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
+# number of processors available to use.
+jobs=1
+
+# Control the amount of potential inferred values when inferring a single
+# object. This can help the performance when dealing with large functions or
+# complex, nested conditions.
+limit-inference-results=100
+
+# List of plugins (as comma separated values of python module names) to load,
+# usually to register additional checkers.
+load-plugins=pylint_pytest
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Minimum Python version to use for version dependent checks. Will default to
+# the version used to run pylint.
+py-version=3.9
+
+# When enabled, pylint would attempt to guess common misconfiguration and emit
+# user-friendly hints instead of false-positive error messages.
+suggestion-mode=yes
+
+# Allow loading of arbitrary C extensions. Extensions are imported into the
+# active Python interpreter and may run arbitrary code.
+unsafe-load-any-extension=no
+
+
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
+confidence=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once). You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use "--disable=all --enable=classes
+# --disable=W".
+disable=R0801,  # duplicate-code
+        R0901,  # too-many-ancestors
+        R0902,  # too-many-instance-attributes
+        R0903,  # too-few-public-methods
+        R0912,  # too-many-branches
+        R0913,  # too-many-arguments
+        R0914,  # too-many-locals
+        R0915,  # too-many-statements
+        C0103,  # invalid-name
+        C0302,  # too-many-lines
+        W1203,  # logging-fstring-interpolation
+        F6401,  # cannot-enumerate-pytest-fixtures - false positives
+        C0114,  # missing-module-docstring # WILL BE REMOVED IN LATER PR
+        C0115,  # missing-class-docstring # WILL BE REMOVED IN LATER PR
+        C0116,  # missing-function-docstring # WILL BE REMOVED IN LATER PR
+        raw-checker-failed,
+        bad-inline-option,
+        locally-disabled,
+        file-ignored,
+        suppressed-message,
+        useless-suppression,
+        deprecated-pragma,
+        use-symbolic-message-instead,
+
+# 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
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=c-extension-no-member
+
+
+[REPORTS]
+
+# Python expression which should return a score less than or equal to 10. You
+# have access to the variables 'error', 'warning', 'refactor', and 'convention'
+# which contain the number of messages in each category, as well as 'statement'
+# which is the total number of statements analyzed. This score is used by the
+# global evaluation report (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details.
+#msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, json
+# and msvs (visual studio). You can also give a reporter class, e.g.
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Tells whether to display a full report or only the messages.
+reports=no
+
+# Activate the evaluation score.
+score=yes
+
+
+[REFACTORING]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=5
+
+# Complete name of functions that never returns. When checking for
+# inconsistent-return-statements if a never returning function is called then
+# it will be considered as an explicit return statement and no message will be
+# printed.
+never-returning-functions=sys.exit,argparse.parse_error
+
+
+[LOGGING]
+
+# The type of string formatting that logging methods do. `old` means using %
+# formatting, `new` is for `{}` formatting.
+logging-format-style=old
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format.
+logging-modules=logging
+
+
+[SPELLING]
+
+# Limits count of emitted suggestions for spelling mistakes.
+max-spelling-suggestions=4
+
+# Spelling dictionary name. Available dictionaries: none. To make it work,
+# install the 'python-enchant' package.
+spelling-dict=
+
+# List of comma separated words that should be considered directives if they
+# appear and the beginning of a comment and should not be checked.
+spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
+
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
+
+# A path to a file that contains the private dictionary; one word per line.
+spelling-private-dict-file=
+
+# Tells whether to store unknown words to the private dictionary (see the
+# --spelling-private-dict-file option) instead of raising a message.
+spelling-store-unknown-words=no
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+# We're allowing TODO notes in the code but not FIXME or XXX.
+# (The former just means not done yet, but FIXME suggests a bug)
+notes=FIXME,
+      XXX
+
+# Regular expression of note tags to take in consideration.
+#notes-rgx=
+
+
+[TYPECHECK]
+
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=torch.*,device
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# class is considered mixin if its name matches the mixin-class-rgx option.
+ignore-mixin-members=yes
+
+# Tells whether to warn about missing members when the owner of the attribute
+# is inferred to be None.
+ignore-none=yes
+
+
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference
+# can return multiple potential results while evaluating a Python object, but
+# some branches might not be evaluated, which results in partial inference. In
+# that case, it might be useful to still emit no-member and other checks for
+# the rest of the inferred objects.
+ignore-on-opaque-inference=yes
+
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis). It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
+
+# Show a hint with possible names when a member name was not found. The aspect
+# of finding the hint is based on edit distance.
+missing-member-hint=yes
+
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance=1
+
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices=1
+
+# Regex pattern to define which classes are considered mixins ignore-mixin-
+# members is set to 'yes'
+mixin-class-rgx=.*[Mm]ixin
+
+# List of decorators that change the signature of a decorated function.
+signature-mutators=
+
+
+[VARIABLES]
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid defining new builtins when possible.
+additional-builtins=
+
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables=yes
+
+# List of names allowed to shadow builtins
+allowed-redefined-builtins=
+
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,
+          _cb
+
+# A regular expression matching the name of dummy variables (i.e. expected to
+# not be used).
+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore.
+ignored-argument-names=_.*|^ignored_|^unused_
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
+
+
+[FORMAT]
+
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
+
+# Maximum number of characters on a single line.
+max-line-length=88
+
+# Maximum number of lines in a module.
+max-module-lines=1000
+
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+single-line-class-stmt=no
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+
+[SIMILARITIES]
+
+# Comments are removed from the similarity computation
+ignore-comments=yes
+
+# Docstrings are removed from the similarity computation
+ignore-docstrings=yes
+
+# Imports are removed from the similarity computation
+ignore-imports=no
+
+# Signatures are removed from the similarity computation
+ignore-signatures=no
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+
+[BASIC]
+
+# Naming style matching correct argument names.
+argument-naming-style=snake_case
+
+# Regular expression matching correct argument names. Overrides argument-
+# naming-style.
+#argument-rgx=
+
+# Naming style matching correct attribute names.
+attr-naming-style=snake_case
+
+# Regular expression matching correct attribute names. Overrides attr-naming-
+# style.
+#attr-rgx=
+
+# Bad variable names which should always be refused, separated by a comma.
+bad-names=foo,
+          bar,
+          baz,
+          toto,
+          tutu,
+          tata
+
+# Bad variable names regexes, separated by a comma. If names match any regex,
+# they will always be refused
+bad-names-rgxs=
+
+# Naming style matching correct class attribute names.
+class-attribute-naming-style=any
+
+# Regular expression matching correct class attribute names. Overrides class-
+# attribute-naming-style.
+#class-attribute-rgx=
+
+# Naming style matching correct class constant names.
+class-const-naming-style=UPPER_CASE
+
+# Regular expression matching correct class constant names. Overrides class-
+# const-naming-style.
+#class-const-rgx=
+
+# Naming style matching correct class names.
+class-naming-style=PascalCase
+
+# Regular expression matching correct class names. Overrides class-naming-
+# style.
+#class-rgx=
+
+# Naming style matching correct constant names.
+const-naming-style=UPPER_CASE
+
+# Regular expression matching correct constant names. Overrides const-naming-
+# style.
+#const-rgx=
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+# Naming style matching correct function names.
+function-naming-style=snake_case
+
+# Regular expression matching correct function names. Overrides function-
+# naming-style.
+#function-rgx=
+
+# Good variable names which should always be accepted, separated by a comma.
+good-names=a,
+           b,
+           c,
+           df,  # pandas.DataFrame
+           e,   # useful for exceptions
+           i,
+           j,
+           k,
+           m,
+           n,
+           x,
+           fx,
+           y,
+           fy,
+           ex,
+           fp,  # use for file pointers in "with fp:" blocks
+           Run,
+           _
+
+# Good variable names regexes, separated by a comma. If names match any regex,
+# they will always be accepted
+good-names-rgxs=
+
+# Include a hint for the correct naming format with invalid-name.
+include-naming-hint=no
+
+# Naming style matching correct inline iteration names.
+inlinevar-naming-style=any
+
+# Regular expression matching correct inline iteration names. Overrides
+# inlinevar-naming-style.
+#inlinevar-rgx=
+
+# Naming style matching correct method names.
+method-naming-style=snake_case
+
+# Regular expression matching correct method names. Overrides method-naming-
+# style.
+#method-rgx=
+
+# Naming style matching correct module names.
+module-naming-style=snake_case
+
+# Regular expression matching correct module names. Overrides module-naming-
+# style.
+#module-rgx=
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
+
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+# These decorators are taken in consideration only for invalid-name.
+property-classes=abc.abstractproperty
+
+# Naming style matching correct variable names.
+variable-naming-style=snake_case
+
+# Regular expression matching correct variable names. Overrides variable-
+# naming-style.
+#variable-rgx=
+
+
+[STRING]
+
+# This flag controls whether inconsistent-quotes generates a warning when the
+# character used as a quote delimiter is used inconsistently within a module.
+check-quote-consistency=no
+
+# This flag controls whether the implicit-str-concat should generate a warning
+# on implicit string concatenation in sequences defined over several lines.
+check-str-concat-over-line-jumps=no
+
+
+[IMPORTS]
+
+# List of modules that can be imported at any level, not just the top level
+# one.
+allow-any-import-level=
+
+
+# Allow wildcard imports from modules that define __all__.
+allow-wildcard-with-all=no
+
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+# Deprecated modules which should not be used, separated by a comma.
+deprecated-modules=
+
+# Output a graph (.gv or any supported image format) of external dependencies
+# to the given file (report RP0402 must not be disabled).
+ext-import-graph=
+
+# Output a graph (.gv or any supported image format) of all (i.e. internal and
+# external) dependencies to the given file (report RP0402 must not be
+# disabled).
+import-graph=
+
+# Output a graph (.gv or any supported image format) of internal dependencies
+# to the given file (report RP0402 must not be disabled).
+
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+# Couples of modules and preferred modules, separated by a comma.
+preferred-modules=
+
+
+[CLASSES]
+
+# Warn about protected attribute access inside special methods
+check-protected-access-in-special-methods=no
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,
+                      __new__,
+                      setUp,
+                      __post_init__
+
+
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,
+                  _fields,
+                  _replace,
+                  _source,
+                  _make
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=cls
+
+
+[DESIGN]
+
+# List of regular expressions of class ancestor names to ignore when counting
+# public methods (see R0903)
+exclude-too-few-public-methods=
+
+# List of qualified class names to ignore when counting class parents (see
+# R0901)
+ignored-parents=
+
+# Maximum number of arguments for function / method.
+max-args=7  # Raised this from 5
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Maximum number of boolean expressions in an if statement (see R0916).
+max-bool-expr=5
+
+# Maximum number of branch for function / method body.
+max-branches=12
+
+# Maximum number of locals for function / method body.
+max-locals=15
+
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+# Maximum number of return / yield for function / method body.
+max-returns=6
+
+# Maximum number of statements in function / method body.
+max-statements=50
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "BaseException, Exception".
+overgeneral-exceptions=builtins.BaseException,
+                       builtins.Exception
diff --git a/clarity/_version.py b/clarity/_version.py
index 0bb0c56de..f59d79b08 100644
--- a/clarity/_version.py
+++ b/clarity/_version.py
@@ -8,6 +8,8 @@
 # Generated by versioneer-0.26
 # https://github.com/python-versioneer/python-versioneer
 
+# pylint: disable=W0201  # allow "attribute defined outside __init__"
+
 """Git implementation of _version.py."""
 
 import errno
@@ -84,10 +86,11 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=
         popen_kwargs["startupinfo"] = startupinfo
 
     for command in commands:
+        dispcmd = None
         try:
             dispcmd = str([command] + args)
             # remember shell=False, so use git.cmd on windows, not just git
-            process = subprocess.Popen(
+            process = subprocess.Popen(  # pylint: disable=consider-using-with
                 [command] + args,
                 cwd=cwd,
                 env=env,
@@ -156,7 +159,7 @@ def git_get_keywords(versionfile_abs):
     # _version.py.
     keywords = {}
     try:
-        with open(versionfile_abs, "r") as fobj:
+        with open(versionfile_abs, "r", encoding="utf-8") as fobj:
             for line in fobj:
                 if line.strip().startswith("git_refnames ="):
                     mo = re.search(r'=\s*"(.*)"', line)
diff --git a/clarity/data/HOA_tools_cec2.py b/clarity/data/HOA_tools_cec2.py
index 3e85c777c..ae565b4c0 100644
--- a/clarity/data/HOA_tools_cec2.py
+++ b/clarity/data/HOA_tools_cec2.py
@@ -4,7 +4,7 @@
 
 import numpy as np
 from numba import njit
-from numba.typed import List as TypedList
+from numba.typed import List as TypedList  # pylint: disable=no-name-in-module
 from scipy.signal import convolve
 from scipy.spatial.transform import Rotation as R
 from scipy.special import comb
@@ -19,7 +19,8 @@ def convert_a_to_b_format(
     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).
+    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
@@ -92,7 +93,8 @@ def compute_rotation_matrix(n: int, foa_rotmat: np.ndarray) -> np.ndarray:
     sub_matrices[1] = foa_rotmat
 
     typed_sub_matrices = TypedList()
-    [typed_sub_matrices.append(x) for x in sub_matrices]
+    for x in sub_matrices:
+        typed_sub_matrices.append(x)
 
     if n > 1:
         for i in np.arange(2, n + 1):
@@ -166,7 +168,8 @@ def U(degree, n, order, rotation_matrices):
     """U coefficient initialiser for rotation matrix calculation.
 
     Args:
-        rotation_degree (int): Upper parameters of spherical harmonic component Y and n the lower.
+        rotation_degree (int): Upper parameters of spherical harmonic
+                               component Y and n the lower.
         n (int): index
         order (int): order
         rotation_matrices (list(matrix)): rotation matrices
@@ -192,21 +195,22 @@ def V(degree, n, order, rotation_matrices):
     """
     d = 0
     if degree == 0:
-        return P(1, 1, n, order, rotation_matrices) + P(
+        v_coeff = 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(
+        v_coeff = P(1, degree - 1, n, order, rotation_matrices) * np.sqrt(1 + d) - P(
             -1, -degree + 1, n, order, rotation_matrices
         ) * (1 - d)
     else:
         if degree == -1:
             d = 1.0
-        return P(1, degree + 1, n, order, rotation_matrices) * (1 - d) + P(
+        v_coeff = P(1, degree + 1, n, order, rotation_matrices) * (1 - d) + P(
             -1, -degree - 1, n, order, rotation_matrices
         ) * np.sqrt(1 + d)
+    return v_coeff
 
 
 @njit
@@ -223,15 +227,16 @@ def W(degree, n, order, rotation_matrices):
         float: W value
     """
     if degree == 0:
-        return 0.0
+        w_value = 0.0
     elif degree > 0:
-        return P(1, degree + 1, n, order, rotation_matrices) + P(
+        w_value = P(1, degree + 1, n, order, rotation_matrices) + P(
             -1, -degree - 1, n, order, rotation_matrices
         )
     else:
-        return P(1, degree - 1, n, order, rotation_matrices) - P(
+        w_value = P(1, degree - 1, n, order, rotation_matrices) - P(
             -1, -degree + 1, n, order, rotation_matrices
         )
+    return w_value
 
 
 @njit
@@ -401,7 +406,8 @@ def binaural_mixdown(ambisonic_signals, hrir, hrir_metadata):
 
     # Decode to loudspeaker positions
 
-    # # No need to apply max-rE weights given we are using virtual speakers and headphones
+    # # No need to apply max-rE weights given we are using
+    # # virtual speakers and headphones
     # y = np.dot(ambisonic_signals * weights, inv_matrix)
 
     n_chans = ambisonic_signals.shape[1]
@@ -528,7 +534,7 @@ def rotation_control_vector(
     return np.array(np.floor(idx * (array_length - 1)), dtype=int)
 
 
-def rotation_vector(
+def compute_rotation_vector(
     start_angle: float,
     end_angle: float,
     signal_length: int,
@@ -549,12 +555,8 @@ def rotation_vector(
     """
     turn_direction = -np.sign(start_angle - end_angle)
     with np.errstate(divide="raise"):
-        try:
-            increment = (
-                np.abs(start_angle - end_angle) / signal_length
-            ) * turn_direction
-        except FloatingPointError:
-            raise
+        increment = (np.abs(start_angle - end_angle) / signal_length) * turn_direction
+
     theta = np.arange(start_angle, end_angle, increment)
     idx = rotation_control_vector(signal_length, start_idx, end_idx)
     return theta[idx]
diff --git a/clarity/data/demo_data.py b/clarity/data/demo_data.py
index 7fcc54b96..3c9575826 100644
--- a/clarity/data/demo_data.py
+++ b/clarity/data/demo_data.py
@@ -12,9 +12,9 @@ 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.
+        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
@@ -29,35 +29,53 @@ def get_demo_data(metadata_url: str, target_dir: str) -> None:
 
 def get_metadata_demo(target_dir: str = TARGET_DIR) -> None:
     """Download metadata."""
-    url = "https://drive.google.com/uc?export=download&id=14KGm2GaRgwlrZvtmMwWTYu7itaRVQV8f"
+    url = (
+        "https://drive.google.com/"
+        "uc?export=download&id=14KGm2GaRgwlrZvtmMwWTYu7itaRVQV8f"
+    )
     get_demo_data(url, 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"
+    url = (
+        "https://drive.google.com/"
+        "uc?export=download&id=1uu2Hes1fzqNaZSCiFNhxZM3bE_fAVKsD"
+    )
     get_demo_data(url, 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"
+    url = (
+        "https://drive.google.com/"
+        "uc?export=download&id=1_ssD238Qv-EETzC0hJze7JhLE7bHyqwG"
+    )
     get_demo_data(url, 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"
+    url = (
+        "https://drive.google.com/"
+        "uc?export=download&id=1FBC8DI4Ru-g3Set0fDzoKmXTqHqNXV8n"
+    )
     get_demo_data(url, target_dir)
 
 
 def get_scenes_demo(target_dir: str = TARGET_DIR) -> None:
     """Download secnes."""
-    url = "https://drive.google.com/uc?export=download&id=1PB0CfGXhpkYNk8HbE5lTWowm2016x6Hl"
+    url = (
+        "https://drive.google.com/"
+        "uc?export=download&id=1PB0CfGXhpkYNk8HbE5lTWowm2016x6Hl"
+    )
     get_demo_data(url, 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"
+    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 1759a45d2..050bd9d36 100644
--- a/clarity/data/scene_builder_cec2.py
+++ b/clarity/data/scene_builder_cec2.py
@@ -62,8 +62,8 @@ def get_room_dims(text: str) -> List:
     """Find the room dimensions in the rpf file.
 
     Args:
-        text (str): String to be searched for room dimensions (string to be searched for is of the form 'ProjectName =
-    CuboidRoom_5.9x3.4186x2.9').
+        text (str): String to be searched for room dimensions (string to be searched
+            for is of the form 'ProjectName = CuboidRoom_5.9x3.4186x2.9').
 
 
     Returns:
@@ -94,7 +94,8 @@ def read_rpf_file(rpf_filename: str) -> Dict:
         rpf_filename (str): Path to an rpf file to be read.
 
     Returns:
-        dict: dictionary of rpf file contents {"position": sourcePositions, "view_vector": sourceViewVectors}
+        dict: dictionary of rpf file contents
+            {"position": sourcePositions, "view_vector": sourceViewVectors}
     """
     with open(rpf_filename, "r", encoding="utf-8") as f:
         text = f.read()
@@ -190,7 +191,7 @@ 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.
+        pre_samples_range (list): parameters for number of samples prior to target onset
 
     Returns:
     """
@@ -214,13 +215,13 @@ def add_this_target_to_scene(
 ):
     """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 dictionary.
-        pre_samples_range (list): parameters for number of samples prior to target onset.
+        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.
     """
@@ -236,11 +237,10 @@ def add_this_target_to_scene(
 
 # SNR handling
 def generate_snr(snr_range: List[int]) -> float:
-    """Generate a random Signal Noise Ratio.
+    """Generate a random Signal Noise Ratio (SNR).
 
     Args:
-        snr_range (list): Range of values for a Uniform distribution from which to sample
-            Signal Noise Ratio.
+        snr_range (list): Range from which to uniformly sample SNR.
 
     Returns:
         float: random number from uniform distribution in given range.
@@ -367,8 +367,10 @@ def add_interferer_to_scene_inner(
         scene (dict): the scene description
         interferers (dict): the interferer metadata
         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.
+        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)
@@ -429,7 +431,8 @@ def generate_rotation(
 
     Args:
         scene (dict): the scene description
-        relative_start_time_range (list): Range from which start time is selected at random.
+        relative_start_time_range (list): Range from which start time is
+            uniformly 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):
@@ -594,7 +597,7 @@ def save_scenes(self, filename: str) -> None:
         Returns:
             None
         """
-        scenes = [s for s in self.scenes]
+        scenes = self.scenes.copy()
         # Replace the room structure with the room ID
         for scene in scenes:
             scene["room"] = scene["room"]["name"]
@@ -602,7 +605,7 @@ def save_scenes(self, filename: str) -> None:
             json.dump(self.scenes, f, indent=2)
 
     def instantiate_scenes(self, dataset) -> None:
-        """Instantiate scenes with targets, interferers, signal noise ratio and listeners.
+        """Instantiate scenes with targets, interferers, SNR and listeners.
 
         Args:
             dataset:
@@ -629,6 +632,8 @@ def initialise_scenes(
             n_scenes (int): number of scenes to generate
             room_selection (str): SEQUENTIAL or RANDOM
             scene_start_index (int): index to start for scene IDs
+
+        Raises: TypeError if room_selection is not SEQUENTIAL or RANDOM
         """
         rooms = self.rb.rooms.copy()
         if self.shuffle_rooms:
@@ -647,7 +652,7 @@ def initialise_scenes(
             for scene in scenes:
                 scene["room"] = random.choice(rooms)
         else:
-            assert False, "Unknown room selection mode"
+            raise TypeError(f"Invalid room selection mode: {room_selection}")
         self.scenes.extend(scenes)
 
         # Set the scene ID
@@ -663,18 +668,20 @@ def add_target_to_scene(
         post_samples_range: list,
     ):
         """Add target info to the scenes.
-        Target speaker file set via config.
+
+        Uses 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_
+            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 of samples to continue
+                player after target offsets.
+
+        Raises: TypeError if room_selection is not SEQUENTIAL or RANDOM
         """
         with open(target_speakers, "r", encoding="utf-8") as f:
             targets = json.load(f)
@@ -700,13 +707,13 @@ def add_target_to_scene(
                     post_samples_range,
                 )
         else:
-            assert False, "Unknown target selection mode"
+            raise TypeError(f"Invalid room selection mode: {target_selection}")
 
     def add_SNR_to_scene(self, snr_range: list):
-        """Add the Signal Noise Ratio info to the scenes.
+        """Add the Signal Noise Ratio (SNR) info to the scenes.
 
         Args:
-            snr_range (list): Range of values from which Signal Noise Ratio will be sampled.
+            snr_range (list): Range of values from which SNR will be sampled.
 
         Returns:
         """
@@ -791,7 +798,8 @@ def add_listener_details_to_scene(
         Args:
             heads ():
             channels ():
-            relative_start_time_range (list): Range from which start time is selected at random.
+            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):
diff --git a/clarity/data/scene_renderer_cec1.py b/clarity/data/scene_renderer_cec1.py
index eae0df73b..c3ae323cf 100644
--- a/clarity/data/scene_renderer_cec1.py
+++ b/clarity/data/scene_renderer_cec1.py
@@ -13,8 +13,8 @@
 
 class Renderer:
     """
-    SceneGenerator of CEC1 training and development sets. The render() function generates all
-    simulated signals for each scene given the parameters specified in the
+    SceneGenerator of CEC1 training and development sets. The render() function
+    generates all simulated signals for each scene given the parameters specified in the
     metadata/scenes.train.json or metadata/scenes.dev.json file.
     """
 
@@ -40,11 +40,11 @@ def __init__(
         self.test_nbits = test_nbits
 
         if num_channels == 0:
-            # This will only generate the initial target, masker and anechoic target signal
+            # Generate just the initial target, masker and anechoic target signal
             self.channels = []
         else:
-            # ... as above plus N hearing aid input channels plus 'channel 0' (the eardrum signal).
-            # e.g. num_channel = 2  => channels [1, 2, 0]
+            # ... as above plus N hearing aid input channels plus 'channel 0'
+            # (the eardrum signal). e.g. num_channel = 2  => channels [1, 2, 0]
             self.channels = list(range(1, num_channels + 1)) + [0]
 
     def read_signal(
@@ -54,26 +54,23 @@ def read_signal(
 
         Args:
             filename (string): Name of file to read
-            offset (int, optional): Offset in samples or seconds (from start). Defaults to 0.
+            offset (int, optional): Offset in samples or seconds from start.
+                Defaults to 0.
             nchannels: expected number of channel (default: 0 = any number OK)
             offset_is_samples (bool): measurement units for offset (default: False)
 
         Returns:
             np.ndarray: audio signal
         """
-        try:
-            wave_file = SoundFile(filename)
-        except Exception as e:
-            # Ensure incorrect error (24 bit) is not generated
-            raise Exception(f"Unable to read {filename}.") from e
+        wave_file = SoundFile(filename)
 
         if nchannels not in (0, wave_file.channels):
-            raise Exception(
+            raise ValueError(
                 f"Wav file ({filename}) was expected to have {nchannels} channels."
             )
 
         if wave_file.samplerate != self.sample_rate:
-            raise Exception(
+            raise ValueError(
                 f"Sampling rate is not {self.sample_rate} for filename {filename}."
             )
 
@@ -145,11 +142,11 @@ def apply_ramp(self, signal, ramp_duration):
         return signal_ramped
 
     def apply_brir(self, signal, brir):
-        """Convolve a signal with a BRIR.
+        """Convolve a signal with a binaural room impulse response (BRIR).
 
         Args:
             signal (ndarray): The mono or stereo signal stored as array of floats
-            brir (ndarray): The binaural room impulse response stored a 2xN array of floats
+            brir (ndarray): The BRIR stored a 2xN array of floats
             n_tail (int): Truncate output to input signal length + n_tail
 
         Returns:
@@ -195,7 +192,8 @@ def compute_snr(
             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)})"
+                f"Target ({len(segment_target)}) "
+                f"differs in length from Noise ({len(segment_noise)})"
             ) from e
 
         snr = better_ear_speechweighted_snr(segment_target, segment_noise)
@@ -268,7 +266,7 @@ def render(
                 )
                 logging.debug("Using channel %s as reference.", channel)
 
-            # Apply snr_ref reference scaling to get 0 dB and then scale to target snr_dB
+            # Apply snr_ref reference scaling to get 0 dB, then scale to target snr_dB
             interferer_at_ear = interferer_at_ear * snr_ref
             interferer_at_ear = interferer_at_ear * 10 ** ((-snr_dB) / 20)
 
@@ -320,8 +318,8 @@ def check_scene_exists(scene: dict, output_path: str, num_channels: int) -> bool
         # This will only generate the initial target, masker and anechoic target signal
         pass
     else:
-        # ... as above plus N hearing aid input channels plus 'channel 0' (the eardrum signal).
-        # e.g. num_channel = 2  => channels [1, 2, 0]
+        # ... as above plus N hearing aid input channels plus 'channel 0' (the eardrum
+        # signal), e.g., num_channel = 2  => channels [1, 2, 0]
         channels = list(range(1, num_channels + 1)) + [0]
 
     pattern = f"{output_path}/{scene['scene']}"
diff --git a/clarity/data/scene_renderer_cec2.py b/clarity/data/scene_renderer_cec2.py
index 8765c1cd3..584cbfe16 100644
--- a/clarity/data/scene_renderer_cec2.py
+++ b/clarity/data/scene_renderer_cec2.py
@@ -38,7 +38,7 @@ def two_point_rotation(rotation: dict, origin: np.ndarray, duration: int) -> np.
     angle_origin = np.arctan2(origin[1], origin[0])
     angles = [math.radians(r["angle"]) - angle_origin for r in rotation]
     logger.info("angles=%s", angles)
-    theta = hoa.rotation_vector(
+    theta = hoa.compute_rotation_vector(
         angles[0], angles[1], duration, rotation[0]["sample"], rotation[1]["sample"]
     )
     return theta
@@ -193,8 +193,8 @@ def load_interferer_signals(self, scene):
     def make_hoa_target_anechoic(self, target, room):
         """Make the HOA anechoic target.
 
-        Applies an anechoic HOA IR that models a source straight in front of the listener.
-        The signal is delayed to match the propagation delay of the room.
+        Applies an anechoic HOA IR that models a source straight in front of the
+        listener. The signal is delayed to match the propagation delay of the room.
 
         Args:
             target ():
@@ -371,7 +371,8 @@ def generate_binaural_signals(
         ]
         hrirs = [loadmat(hrir_filename) for hrir_filename in hrir_filenames]
 
-        # Target and (flattened) interferer mixed down to binaural using each set of hrirs
+        # Target and (flattened) interferer mixed down to binaural using each
+        # set of hrirs
         targets = [
             hoa.binaural_mixdown(hoa_target, hrir, self.metadata.hrir_metadata)
             for hrir in hrirs
diff --git a/clarity/data/utils.py b/clarity/data/utils.py
index 15682e29c..1c93e88ef 100644
--- a/clarity/data/utils.py
+++ b/clarity/data/utils.py
@@ -1,4 +1,5 @@
 """Utilities for data generation."""
+# pylint: disable=wrong-import-position
 import os
 import sys
 
@@ -35,11 +36,8 @@ def better_ear_speechweighted_snr(target: np.ndarray, noise: np.ndarray) -> floa
     """
     if np.ndim(target) == 1:
         # analysis left ear and right ear for single channel target
-        try:
-            left_snr = speechweighted_snr(target, noise[:, 0])
-            right_snr = speechweighted_snr(target, noise[:, 1])
-        except IndexError:
-            raise
+        left_snr = speechweighted_snr(target, noise[:, 0])
+        right_snr = speechweighted_snr(target, noise[:, 1])
     else:
         # analysis left ear and right ear for two channel target
         left_snr = speechweighted_snr(target[:, 0], noise[:, 0])
@@ -60,15 +58,12 @@ def speechweighted_snr(target: np.ndarray, noise: np.ndarray) -> float:
         (float):
     Signal Noise Ratio
     """
-    try:
-        target_filt = scipy.signal.convolve(
-            target, SPEECH_FILTER, mode="full", method="fft"
-        )
-        noise_filt = scipy.signal.convolve(
-            noise, SPEECH_FILTER, mode="full", method="fft"
-        )
-    except ValueError:
-        raise
+
+    target_filt = scipy.signal.convolve(
+        target, SPEECH_FILTER, mode="full", method="fft"
+    )
+    noise_filt = scipy.signal.convolve(noise, SPEECH_FILTER, mode="full", method="fft")
+
     # rms of the target after speech weighted filter
     targ_rms = np.sqrt(np.mean(target_filt**2))
 
@@ -108,10 +103,10 @@ def pad(signal: np.ndarray, length: int) -> np.ndarray:
     Returns:
         np.array:
     """
-    try:
-        assert length >= signal.shape[0]
-    except AssertionError:
-        raise
+
+    if length < signal.shape[0]:
+        raise ValueError("Length must be greater than signal length")
+
     return np.pad(
         signal, ([(0, length - signal.shape[0])] + ([(0, 0)] * (len(signal.shape) - 1)))
     )
diff --git a/clarity/dataset/cec1_dataset.py b/clarity/dataset/cec1_dataset.py
index 95ad92830..b4a8e190c 100644
--- a/clarity/dataset/cec1_dataset.py
+++ b/clarity/dataset/cec1_dataset.py
@@ -37,15 +37,14 @@ def __init__(
         self.testing = testing
 
         self.scene_list = []
-        with open(scenes_file, "r") as f:
-            scene_json = json.load(f)
+        with open(scenes_file, "r", encoding="utf-8") as fp:
+            scene_json = json.load(fp)
             if not testing:
-                for i in range(len(scene_json)):
-                    self.scene_list.append(scene_json[i]["scene"])
+                for scene in scene_json:
+                    self.scene_list.append(scene["scene"])
             else:
                 for scene in scene_json.keys():
                     self.scene_list.append(scene)
-            f.close()
 
         if self.num_channels == 2:
             self.mixed_suffix = "_mixed_CH1.wav"
@@ -66,7 +65,8 @@ def __init__(
     def wav_sample(self, x, y):
         """
         A 2 second silence is in the beginning of clarity data
-        Get rid of the silence segment in the beginning & sample a constant wav length for training.
+        Get rid of the silence segment in the beginning & sample a
+        constant wav length for training.
         """
         silence_len = int(self.wav_silence_len * self.sr)
         x = x[:, silence_len:]
@@ -79,7 +79,6 @@ def wav_sample(self, x, y):
             end = start + sample_len
             x = x[:, start:end]
             y = y[:, start:end]
-            return x, y
         elif wav_len < sample_len:
             x = np.append(
                 x, np.zeros([x.shape[1], sample_len - wav_len], dtype=np.float32)
@@ -87,9 +86,8 @@ def wav_sample(self, x, y):
             y = np.append(
                 y, np.zeros([x.shape[1], sample_len - wav_len], dtype=np.float32)
             )
-            return x, y
-        else:
-            return x, y
+
+        return x, y
 
     def lowpass_filtering(self, x):
         return lfilter(self.lowpass_filter, 1, x)
@@ -122,11 +120,15 @@ def __getitem__(self, item):
         if self.sr != 44100:
             mixed_resampled, target_resampled = [], []
             for i in range(mixed.shape[0]):
-                mixed_resampled.append(librosa.resample(mixed[i], 44100, self.sr))
+                mixed_resampled.append(
+                    librosa.resample(mixed[i], target_sr=44100, orig_sr=self.sr)
+                )
             mixed = np.array(mixed_resampled)
             if not self.testing:
                 for i in range(target.shape[0]):
-                    target_resampled.append(librosa.resample(target[i], 44100, self.sr))
+                    target_resampled.append(
+                        librosa.resample(target[i], target_sr=44100, orig_sr=self.sr)
+                    )
                 target = np.array(target_resampled)
 
         if self.wav_sample_len is not None:
@@ -138,12 +140,17 @@ def __getitem__(self, item):
             target = target / mixed_max
 
         if not self.testing:
-            return (
+            return_data = (
                 torch.tensor(mixed, dtype=torch.float32),
                 torch.tensor(target, dtype=torch.float32),
             )
         else:
-            return torch.tensor(mixed, dtype=torch.float32), self.scene_list[item]
+            return_data = (
+                torch.tensor(mixed, dtype=torch.float32),
+                self.scene_list[item],
+            )
+
+        return return_data
 
     def __len__(self):
         return len(self.scene_list)
diff --git a/clarity/engine/system.py b/clarity/engine/system.py
index 5550a6c82..c6fdf35a6 100644
--- a/clarity/engine/system.py
+++ b/clarity/engine/system.py
@@ -5,6 +5,8 @@
 import pytorch_lightning as pl
 from torch.optim.lr_scheduler import ReduceLROnPlateau
 
+# pylint: disable=W0221  # false positives
+
 
 class System(pl.LightningModule):
     def __init__(
diff --git a/clarity/enhancer/compressor.py b/clarity/enhancer/compressor.py
index cdf208632..e9371da4e 100644
--- a/clarity/enhancer/compressor.py
+++ b/clarity/enhancer/compressor.py
@@ -16,7 +16,7 @@ def __init__(
         attenuation: float = 0.0001,
         rms_buffer_size: float = 0.2,
         makeup_gain: int = 1,
-        **kwargs,
+        **_kwargs,
     ):
         """Instantiate the Compressor Class.
 
diff --git a/clarity/enhancer/dnn/mc_conv_tasnet.py b/clarity/enhancer/dnn/mc_conv_tasnet.py
index 8d03bb7a4..2bcf71e1a 100644
--- a/clarity/enhancer/dnn/mc_conv_tasnet.py
+++ b/clarity/enhancer/dnn/mc_conv_tasnet.py
@@ -5,8 +5,8 @@
 from typing import Optional
 
 import torch
-import torch.nn as nn
 import torch.nn.functional as F
+from torch import nn
 
 EPS = 1e-8
 
@@ -23,7 +23,8 @@ def overlap_and_add(signal, frame_step, device):
     Args:
         signal: A [..., frames, frame_length] Tensor. All dimensions may be unknown,
             and rank must be at least 2.
-        frame_step: An integer denoting overlap offsets. Must be less than or equal to frame_length.
+        frame_step: An integer denoting overlap offsets. Must be less than or equal to
+            frame_length.
         device: Whether to use 'cpu' or 'cuda' for processing.
 
     Returns:
@@ -31,7 +32,8 @@ def overlap_and_add(signal, frame_step, device):
             signal's inner-most two dimensions.
         output_size = (frames - 1) * frame_step + frame_length
 
-    Based on https://github.com/tensorflow/tensorflow/blob/r1.12/tensorflow/contrib/signal/python/ops/reconstruction_ops.py
+    Based on https://github.com/tensorflow/tensorflow/blob/r1.12/tensorflow/
+        contrib/signal/python/ops/reconstruction_ops.py
     """
     outer_dimensions = signal.size()[:-2]
     frames, frame_length = signal.size()[-2:]
@@ -174,7 +176,7 @@ class SpatialEncoder(nn.Module):
     """Estimation of the nonnegative mixture weight by a 1-D conv layer."""
 
     def __init__(self, L, N, num_channels):
-        super(SpatialEncoder, self).__init__()
+        super().__init__()
         # Hyper-parameter
         self.L, self.N = L, N
         # Components
diff --git a/clarity/enhancer/dsp/filter.py b/clarity/enhancer/dsp/filter.py
index 8d0bf2bd5..f402c069c 100644
--- a/clarity/enhancer/dsp/filter.py
+++ b/clarity/enhancer/dsp/filter.py
@@ -1,7 +1,7 @@
 import numpy as np
 import torch
-import torch.nn as nn
 import torch.nn.functional as F
+from torch import nn
 
 EPS = 1e-8
 
diff --git a/clarity/enhancer/gha/gainrule_camfit.py b/clarity/enhancer/gha/gainrule_camfit.py
index adc33e033..bc2e821c7 100644
--- a/clarity/enhancer/gha/gainrule_camfit.py
+++ b/clarity/enhancer/gha/gainrule_camfit.py
@@ -237,7 +237,7 @@ def gainrule_camfit_linear(
             intercepts = freq_interp_sh(
                 intercept_frequencies, intercepts, sFitmodel["frequencies"][0][0]
             )
-    except Exception:  # noqa E722
+    except TypeError:  # noqa E722
         intercepts = freq_interp_sh(
             intercept_frequencies, intercepts, sFitmodel["frequencies"]
         )
diff --git a/clarity/enhancer/gha/gha_interface.py b/clarity/enhancer/gha/gha_interface.py
index ab4e9e70f..ebf534be8 100644
--- a/clarity/enhancer/gha/gha_interface.py
+++ b/clarity/enhancer/gha/gha_interface.py
@@ -81,7 +81,9 @@ def create_configured_cfgfile(
         output = template.render(
             io_in=input_file,
             io_out=output_file,
-            peaklevel_in=f"[{peaklevel_in} {peaklevel_in} {peaklevel_in} {peaklevel_in}]",
+            peaklevel_in=(
+                f"[{peaklevel_in} {peaklevel_in} {peaklevel_in} {peaklevel_in}]"
+            ),
             peaklevel_out=f"[{peaklevel_out} {peaklevel_out}]",
             gtdata=formatted_sGt,
         )
@@ -182,25 +184,23 @@ def read_signal(
 
         Args:
             filename (string): Name of file to read
-            offset (int, optional): Offset in samples or seconds (from start). Defaults to 0.
+            offset (int, optional): Offset in samples or seconds (from start).
+                Defaults to 0.
             nchannels: expected number of channel (default: 0 = any number OK)
             offset_is_samples (bool): measurement units for offset (default: False)
         Returns:
             ndarray: audio signal
         """
-        try:
-            wave_file = SoundFile(filename)
-        except Exception as e:
-            # Ensure incorrect error (24 bit) is not generated
-            raise Exception(f"Unable to read {filename}.") from e
+
+        wave_file = SoundFile(filename)
 
         if nchannels not in (0, wave_file.channels):
-            raise Exception(
+            raise ValueError(
                 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}.")
+            raise ValueError(f"Sampling rate is not {self.fs} for filename {filename}.")
 
         if not offset_is_samples:  # Default behaviour
             offset = int(offset * wave_file.samplerate)
@@ -235,7 +235,7 @@ def create_HA_inputs(self, infile_names, merged_filename):
         """Create input signal for baseline hearing aids."""
 
         if (infile_names[0][-5] != "1") or (infile_names[2][-5] != "3"):
-            raise Exception("HA-input signal error: channel mismatch!")
+            raise ValueError("HA-input signal error: channel mismatch!")
 
         signal_CH1 = self.read_signal(infile_names[0])
         signal_CH3 = self.read_signal(infile_names[2])
diff --git a/clarity/enhancer/nalr.py b/clarity/enhancer/nalr.py
index 3e76799be..c285c8ebb 100644
--- a/clarity/enhancer/nalr.py
+++ b/clarity/enhancer/nalr.py
@@ -11,7 +11,7 @@ def firwin2(
     f: np.ndarray,
     a: np.ndarray,
     window: Optional[Union[str, float]] = None,
-    antisymmetric: Optional[bool] = None,
+    antisymmetric: Optional[bool] = None,  # pylint: disable=unused-argument
 ) -> np.ndarray:
     """FIR filter design using the window method.
     Partial implementation of scipy firwin2 but using our own MATLAB-derived fir2.
@@ -20,11 +20,11 @@ def firwin2(
         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.
-        window (string or (string, float), optional): See scipy.firwin2 (default: (None))
-        _antisymmetric (bool, optional): Unused but present to main compatability
+        window (string or (string, float), optional): See scipy.firwin2 (default: None)
+        _antisymmetric (bool, optional): Unused but present to main compatibility
             with scipy firwin2.
     Returns:
-        np.ndarray:  The filter coefficients of the FIR filter, as a 1-D array of length n.
+        np.ndarray:  The FIR filter coefficients as a 1-D array of length n.
     """
     window_shape = None
     window_type: Union[str, float, None]
@@ -133,10 +133,10 @@ def __init__(self, nfir: int, fs: int):
     def hl_interp(self, hl: np.ndarray, cfs: np.ndarray):
         try:
             hl_interpf = scipy.interpolate.interp1d(cfs, hl)
-        except ValueError:
+        except ValueError as exception:
             raise ValueError(
                 "Hearing losses (hl) and center frequencies (cfs) don't match!"
-            )
+            ) from exception
         return hl_interpf(self.aud)
 
     def build(
diff --git a/clarity/evaluator/haaqi/haaqi.py b/clarity/evaluator/haaqi/haaqi.py
index 5641bb404..5efcd3ad7 100644
--- a/clarity/evaluator/haaqi/haaqi.py
+++ b/clarity/evaluator/haaqi/haaqi.py
@@ -29,22 +29,26 @@ def haaqi_v1(
     impaired hearing.
 
     Arguments:
-    reference (ndarray):  Clear input reference speech signal with no noise or distortion.
+        reference (ndarray):  Input reference speech signal with no noise or distortion.
             If a hearing loss is specified, NAL-R equalization is optional
-    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.
-    equalisation (int): Flag to provide equalization for the hearing loss to signal processed:
+        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): (1,6) vector of hearing loss at the 6 audiometric
+            frequencies [250, 500, 1000, 2000, 4000, 6000] Hz.
+        equalisation (int): hearing loss equalization mode for 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 (int): 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
+        silence_threshold (float): Silence threshold sum across bands,
+            dB above auditory threshold. Default : 2.5
+        add_noise (float): Additive noise dB SL to condition cross-covariances.
+            Defaults to 0.0
+        segment_covariance (int): Segment size for the covariance calculation.
+            Defaults to 16
 
     Returns:
         combined : Quality is the polynomial sum of the nonlinear and linear terms
diff --git a/clarity/evaluator/haspi/eb.py b/clarity/evaluator/haspi/eb.py
index 6ad6c3bf4..d69a0d32c 100644
--- a/clarity/evaluator/haspi/eb.py
+++ b/clarity/evaluator/haspi/eb.py
@@ -18,13 +18,6 @@
 
 logger = logging.getLogger(__name__)
 
-# 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,
@@ -53,31 +46,33 @@ def ear_model(
     and IHC loss attenuation.
 
     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)
-    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 (int): purpose for the calculation:
-             0=intelligibility: reference is nornal hearing and must not
+        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)
+        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 6
+            audiometric frequencies: [250, 500, 1000, 2000, 4000, 6000] Hz
+        itype (int): purpose for the calculation:
+             0=intelligibility: reference is normal 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
-    nchan (int): auditory frequency bands
-    m_delay (int): Compensate for the gammatone group delay.
-    shift (float): Basal shift of the basilar membrane length
+        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:
-    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
+        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.
@@ -106,7 +101,8 @@ def ear_model(
     ) = 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.
+    # loss if calculating quality, but are for normal hearing if calculating
+    # intelligibility.
     if itype == 0:
         hearing_loss_x = [0] * len(hearing_loss)
     else:
@@ -143,7 +139,8 @@ def ear_model(
     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.
+    # 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, freq_sample)
@@ -322,21 +319,20 @@ def center_frequency(
     min_bw=24.7,
 ):
     """
-    Compute the Equivalent Rectangular Bandwidth_[1] frequency spacing for the gammatone filter bank. The equation
-    comes from Malcolm Slaney[2]_ .
+    Compute the Equivalent Rectangular Bandwidth_[1] frequency spacing for the
+    gammatone filter bank. The equation comes from Malcolm Slaney[2].
 
     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[3]_.
-    ear_q (float):
-    min_bw (float):
+        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[3].
+        ear_q (float):
+        min_bw (float):
 
     Returns:
 
@@ -380,7 +376,8 @@ def center_frequency(
 
     # All of the following expressions are derived in Apple TR #35,
     # "An Efficient Implementation of the Patterson-Holdsworth Cochlear
-    # Filter Bank" by Malcolm Slaney. https://engineering.purdue.edu/~malcolm/apple/tr35/PattersonsEar.pdf
+    # 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(high_freq + ear_q * min_bw) + np.log(low_freq + ear_q * min_bw))
@@ -400,18 +397,18 @@ def loss_parameters(hearing_loss, center_freq, audiometric_freq=None):
     in proportion to the OHC fraction of the total loss.
 
     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):
+        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:
-    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
+        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.
@@ -447,8 +444,8 @@ def loss_parameters(hearing_loss, center_freq, audiometric_freq=None):
     # frequency band to 3.5:1 in the 8-kHz frequency band
     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.
+    # 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.
     max_ohc = 70 * (
         1 - (1 / compression_ratio)
     )  # HC loss that results in 1:1 compression
@@ -667,33 +664,33 @@ def gammatone_basilar_membrane(
     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[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,
+    4th-order gammatone auditory filter. This implementation is based 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.
 
-    This version filters two signals that have the same sampling rate and the
-    same gammatone filter center frequencies. The lengths of the two signals
-    should match; if they don't, the signals are truncated to the shorter of
-    the two lengths.
+    This version filters two signals that have the same sampling rate and the same
+    gammatone filter center frequencies. The lengths of the two signals should match;
+    if they don't, the signals are truncated to the shorter of the two lengths.
 
     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): ???
+        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:
-    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
+        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
@@ -712,7 +709,8 @@ def gammatone_basilar_membrane(
     Cosine/sine loop speed increased, 9 August 2013.
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
-    # Filter Equivalent Rectangular Bandwidth from Moore and Glasberg (1983) doi: 10.1121/1.389861
+    # 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 and trim to shortest
@@ -779,15 +777,15 @@ def gammatone_bandwidth_demodulation(
     """Gamma tone bandwidth demodulation
 
     Arguments:
-    npts (): ???
-    tpt (): ???
-    center_freq (): ???
-    center_freq_cos (): ???
-    sincf (): ???
+        npts (): ???
+        tpt (): ???
+        center_freq (): ???
+        center_freq_cos (): ???
+        sincf (): ???
 
     Returns:
-    sincf (): ???
-    coscf (): ???
+        sincf (): ???
+        coscf (): ???
     """
     cos_n = np.cos(tpt * center_freq)
     sin_n = np.sin(tpt * center_freq)
@@ -807,17 +805,16 @@ def gammatone_bandwidth_demodulation(
 
 def bandwidth_adjust(control, bandwidth_min, bandwidth_max, level1):
     """
-    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.
 
     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
+        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
 
     Returns:
-    bandwidth (): filter bandwidth increased for high signal levels
+        bandwidth (): filter bandwidth increased for high signal levels
 
     Updates:
     James M. Kates, 21 June 2011.
@@ -851,30 +848,29 @@ def env_compress_basilar_membrane(
     threshold_high=100,
 ):
     """
-    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.
+    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 threshold is 0 dB SPL.
 
     Arguments:
-    envsig (): analytic signal envelope (magnitude) returned by the
+        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
-    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
+        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
+        small (): ???
+        threshold_high: kneepoint for the high-level linear amplification
 
     Returns:
-    compressed_signal (): compressed version of the signal envelope
-    compressed_basilar_membrane (): compressed version of the BM motion
+        compressed_signal (): compressed version of the signal envelope
+        compressed_basilar_membrane (): compressed version of the BM motion
 
     Updates:
     James M. Kates, 19 January 2007.
@@ -913,16 +909,16 @@ def env_compress_basilar_membrane(
 
 def envelope_align(reference, output, freq_sample=24000, corr_range=100):
     """
-    Align the envelope of the processed signal to that of the reference
-    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
+        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.
@@ -931,9 +927,9 @@ def envelope_align(reference, output, freq_sample=24000, corr_range=100):
     Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
 
-    # 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
+    # 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
     lags = round(0.001 * corr_range * freq_sample)  # Range in samples
     npts = len(reference)
     lags = min(lags, npts)
@@ -956,15 +952,16 @@ def envelope_sl(reference, basilar_membrane, attnenuated_ihc, level1, small=1e-3
     Convert the compressed envelope returned by cochlear_envcomp to dB SL.
 
     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): ???
+        reference (): linear envelope after compression
+        basilar_membrane (): linear Basilar Membrane vibration after compression
+        attenuated_ihc (): IHC attenuation at the input to the synapse
+        level1 (): level in dB SPL corresponding to 1 RMS
+        small (float): ???
 
     Returns:
-    _reference (): reference envelope in dB SL
-    _basilar_membrane (): Basilar Membrane vibration with envelope converted to dB SL
+        _reference (): reference envelope in dB SL
+        _basilar_membrane (): Basilar Membrane vibration withenvelope converted to
+            dB SL
 
     Updates:
     James M. Kates, 20 Feb 07.
@@ -999,15 +996,17 @@ def inner_hair_cell_adaptation(
     auditory threshold are provided by a subsequent call to eb_BMatten.
 
     Arguments:
-    reference_db (np.ndarray): signal envelope in one frequency band in dB SL
+        reference_db (np.ndarray): signal envelope in one frequency band in dB SL
              contains OHC compression and IHC attenuation
-    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
+        reference_basilar_membrane (): basilar membrane vibration with OHC compression
+            but no IHC attenuation
+        delta (): overshoot factor = delta x steady-state
+        freq_sample (int): sampling rate in Hz
 
     Returns:
-    output_db ():     envelope in dB SL with IHC adaptation
-    output_basilar_membrane (): Basilar Membrane 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.
@@ -1074,21 +1073,21 @@ def inner_hair_cell_adaptation(
 
 def basilar_membrane_add_noise(reference, threshold, level1):
     """
-    Apply the IHC attenuation to the BM motion and to add a
-    low-level Gaussian noise to give the auditory threshold.
+    Apply the IHC attenuation to the BM motion and to add a low-level Gaussian noise to
+    give the auditory threshold.
 
     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
+        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:
-    Attenuated signal with threhsold noise added
+        Attenuated signal with threshold 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.
+        James M. Kates, 19 June 2012.
+        Just additive noise, 2 Oct 2012.
+        Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     gain = 10 ** ((threshold - level1) / 20)  # Linear gain for the noise
 
@@ -1114,19 +1113,19 @@ def group_delay_compensate(
     the same group delay.
 
     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) :
+        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:
-    processed (): 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.
+        James M. Kates, 28 October 2011.
+        Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Processing parameters
     nchan = len(bandwidths)
@@ -1191,25 +1190,25 @@ def convert_rms_to_sl(
     assumes that auditory thresold is 0 dB SPL.
 
     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
+        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):
+        threshold_high (int):
+        small (float):
 
     Returns:
-    reference_db (): compressed output in dB above the impaired threshold
+        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.
+        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
@@ -1246,17 +1245,17 @@ def env_smooth(envelopes: np.ndarray, segment_size, freq_sample):
     2*(1000/segsize).
 
     Arguments:
-    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
+        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.
+        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
@@ -1321,21 +1320,22 @@ def mel_cepstrum_correlation(reference, distorted, threshold, addnoise):
     then computed.
 
     Arguments:
-    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
+        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:
-    average_cepstral_correlation : average cepstral correlation 2-6, input vs output
-    individual_cepstral_correlations : 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.
+        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
@@ -1405,7 +1405,6 @@ def mel_cepstrum_correlation(reference, distorted, threshold, addnoise):
                 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).
     average_cepstral_correlation = np.sum(
@@ -1433,29 +1432,29 @@ def melcor9(
     the 8 modulation frequencies.
 
     Arguments:
-    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
+        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:
-    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
+        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.
+        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
@@ -1611,16 +1610,16 @@ def melcor9_crosscovmatrix(b, nmod, nbasis, nsamp, nfir, reference_cep, processe
     """Compute the cross-covariance matrix.
 
     Arguments:
-    b (): ???
-    nmod (): ???
-    nbasis (): ???
-    nsamp (): ???
-    nfir (): ???
-    xcep (): ???
-    ycep (): ???
+        b (): ???
+        nmod (): ???
+        nbasis (): ???
+        nsamp (): ???
+        nfir (): ???
+        xcep (): ???
+        ycep (): ???
 
     Returns:
-    cross_covariance_matrix ():
+        cross_covariance_matrix ():
     """
     small = 1.0e-30
     nfir2 = nfir / 2
@@ -1695,8 +1694,8 @@ def spectrum_diff(reference_sl, processed_sl):
            <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.
+        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
@@ -1758,25 +1757,26 @@ def bm_covary(
     reference_basilar_membrane, processed_basilar_membrane, segment_size, freq_sample
 ):
     """
-    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.
+    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:
-    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
+        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:
-    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
+        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.
+        James M. Kates, 28 August 2012.
+        Output amplitude adjustment added, 30 october 2012.
+        Translated from MATLAB to Python by Gerardo Roa Dabike, September 2022.
     """
 
     # Initialize parameters
@@ -1946,19 +1946,19 @@ def ave_covary2(
     frequencies Johnson[2]_.
 
     Arguments:
-    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
+        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:
-    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:
+        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
@@ -1975,12 +1975,13 @@ def ave_covary2(
            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.
+        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
diff --git a/clarity/evaluator/haspi/ebm.py b/clarity/evaluator/haspi/ebm.py
index ebe912c59..d5c19cc96 100644
--- a/clarity/evaluator/haspi/ebm.py
+++ b/clarity/evaluator/haspi/ebm.py
@@ -13,21 +13,22 @@ def env_filter(reference_db, processed_db, filter_cutoff, freq_sub_sample, freq_
     the filtering operation.
 
     Args:
-    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
+        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: reference_env - LP filtered and subsampled reference 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.
+        James M. Kates, 12 September 2019.
+        Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Check the filter design parameters
     assert (
@@ -53,10 +54,11 @@ def env_filter(reference_db, processed_db, filter_cutoff, freq_sub_sample, freq_
     nhalf = floor(nfilt / 2)
     nfilt = int(2 * nhalf)  # Force an even filter length
 
-    # Design the FIR LP filter using a von Hann window to ensure that there are no negative
-    # 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
+    # Design the FIR LP filter using a von Hann window to ensure that there are no
+    # negative 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
     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)
@@ -87,21 +89,22 @@ def cepstral_correlation_coef(
     calculation.
 
     Args:
-    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
+        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: 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
+        tuple: reference_cep cepstral coefficient matrix for the ref signal
+        (nsamp,nbasis) processed_cep cepstral coefficient matrix for the output signal
+        (nsamp,nbasis) each column is a separate basis function, from low to high
 
     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.
+        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 = reference_db.shape[1]
@@ -139,8 +142,9 @@ def cepstral_correlation_coef(
     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.
+    # 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 = reference_cep[:, n]
         x = x - np.mean(x)
@@ -158,15 +162,15 @@ def add_noise(reference_db, thresh_db):
     in each auditory frequency band.
 
     Args:
-    reference_db (): subsampled envelope in dB re:auditory threshold
-    thresh_db (): additive noise RMS level (in dB)
+        reference_db (): subsampled envelope in dB re:auditory threshold
+        thresh_db (): additive noise RMS level (in dB)
 
     Returns:
       () 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.
+    Updates:
+        James M. Kates, 23 April 2019.
+        Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
     # Additive noise sequence
     # Gaussian noise with RMS=1, then scaled
@@ -194,30 +198,31 @@ def fir_modulation_filter(
     filter outputs.
 
     Args:
-    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.
-    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
+        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.
+        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:  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
+        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 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.
+        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
@@ -340,28 +345,28 @@ def fir_modulation_filter(
 
 def modulation_cross_correlation(reference_modulation, processed_modulation):
     """
-     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.
+    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 = reference_modulation.shape[0]  # Number of basis functions
diff --git a/clarity/evaluator/haspi/haspi.py b/clarity/evaluator/haspi/haspi.py
index 8a19c3d4d..e20b05f62 100644
--- a/clarity/evaluator/haspi/haspi.py
+++ b/clarity/evaluator/haspi/haspi.py
@@ -31,28 +31,31 @@ def haspi_v2(  # pylint: disable=too-many-arguments too-many-locals
     neural networks to compute the estimated intelligibility.
 
     Args:
-    reference (np.ndarray): Clear input reference speech signal with no noise or distortion.
-              If a hearing loss is specified, no amplification should be provided.
-    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 (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
+        reference (np.ndarray): Clear input reference speech signal with no noise or
+            distortion. If a hearing loss is specified, no amplification should be
+            provided.
+        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 (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
+        tuple(Intel: float, raw: nd-array)
+        Intel: Intelligibility estimated by passing the cepstral coefficients
               through a modulation filterbank followed by an ensemble of
               neural networks.
-    raw       vector of 10 cep corr modulation filterbank outputs, averaged
+        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.
+        James M. Kates, 5 August 2013.
+        Translated from MATLAB to Python by Zuzanna Podwinska, March 2022.
     """
 
     # Auditory model for intelligibility
@@ -75,7 +78,7 @@ def haspi_v2(  # pylint: disable=too-many-arguments too-many-locals
         reference_env, processed_env, f_lp, fsub, fsamp
     )
 
-    # Compute the ceptstral coefficients as a function of subsampled time
+    # Compute the cepstral 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
@@ -83,7 +86,7 @@ def haspi_v2(  # pylint: disable=too-many-arguments too-many-locals
         reference_lp, processed_lp, thr, dither, nbasis
     )
 
-    # Cepstral coeffifiencts filtered at each modulation rate
+    # Cepstral coefficients 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
     reference_mod, processed_mod, _ = fir_modulation_filter(
@@ -131,21 +134,21 @@ def haspi_v2_be(  # pylint: disable=too-many-arguments
     Calculates HASPI for left and right ear and selects the better result.
 
     Args:
-    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
+        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
+        Zuzanna Podwinska, March 2022
     """
 
     # HASPI assumes the following audiogram frequencies:
diff --git a/clarity/evaluator/haspi/ip.py b/clarity/evaluator/haspi/ip.py
index ef14182a6..1184e9d68 100644
--- a/clarity/evaluator/haspi/ip.py
+++ b/clarity/evaluator/haspi/ip.py
@@ -14,17 +14,18 @@ def get_neural_net():
     Arguments: None
 
     Returned values:
-    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
-    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.
-    normalization_factor (): normalization so that the maximum neural net output is exactly 1.
+        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
+        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.
+        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.
+        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
     neural_net_params = {}
@@ -235,7 +236,7 @@ def nn_feed_forward_ensemble(
     ncond = data.shape[0]  # Number of conditions in the input data
     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
+    # Ensemble average of predictions over set of neural networks used for training
     predict = np.zeros((ncond, n_networks))
     for k in range(n_networks):
         for n in range(ncond):
diff --git a/clarity/evaluator/hasqi/__init__.py b/clarity/evaluator/hasqi/__init__.py
index ac5734168..6002c2851 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, hasqi_v2_better_ear  # noqa F401
 
-__all__ = ["hasqi_v2", "hasqi_v2_be"]
+__all__ = ["hasqi_v2", "hasqi_v2_better_ear"]
diff --git a/clarity/evaluator/hasqi/hasqi.py b/clarity/evaluator/hasqi/hasqi.py
index f09ad7af1..f742652c3 100644
--- a/clarity/evaluator/hasqi/hasqi.py
+++ b/clarity/evaluator/hasqi/hasqi.py
@@ -22,27 +22,32 @@ def hasqi_v2(
     impaired hearing.
 
     Arguments:
-    reference (np.ndarray): Clear input reference speech signal with no noise or distortion.
-              If a hearing loss is specified, NAL-R equalization is optional
-    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.
-    equalisation (int): Flag to provide equalization for the hearing loss to reference signal:
+        reference (np.ndarray): Clear input reference speech signal with no noise or
+            distortion. If a hearing loss is specified, NAL-R equalization is optional
+        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.
+        equalisation (int): Mode to use when equalising the 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
+        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 is 0.0
+        segment_covariance (int): Segment size for the covariance calculation.
+            Default is 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
-    Linear    Linear quality component = std of spectrum and spectrum slope
-    raw       Vector of raw values = [CepCorr, BMsync5, Dloud, Dslope]
+        tuple(Combined, Nonlin, Linear, raw)
+            Combined: Quality estimate is the product of the nonlinear and linear terms
+            Nonlin: Nonlinear quality component = (cepstral corr)^2 x seg BM coherence
+            Linear: Linear quality component = std of spectrum and spectrum slope
+            raw: Vector of raw values = [CepCorr, BMsync5, Dloud, Dslope]
 
     James M. Kates, 5 August 2013.
     Translated from MATLAB to Python by Gerardo Roa Dabike, October 2022.
@@ -76,7 +81,7 @@ def hasqi_v2(
     # Mel cepstrum correlation using smoothed envelopes
     (
         average_cepstral_correlation,
-        individual_cepstral_correlations,
+        _individual_cepstral_correlations,
     ) = eb.mel_cepstrum_correlation(
         reference_smooth, processed_smooth, silence_threshold, add_noise
     )
@@ -85,14 +90,14 @@ def hasqi_v2(
     # 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
-    d_loud, d_norm, d_slope = eb.spectrum_diff(reference_sl, processed_sl)
+    d_loud, _d_norm, d_slope = eb.spectrum_diff(reference_sl, processed_sl)
 
     # Temporal fine structure correlation measurements
     # Compute the time-frequency segment covariances
     (
         signal_cross_covariance,
         reference_mean_square,
-        processed_mean_square,
+        _processed_mean_square,
     ) = eb.bm_covary(
         reference_basilar_membrane,
         processed_basilar_membrane,
@@ -150,7 +155,7 @@ def hasqi_v2_better_ear(
     audiogram_right,
     audiogram_frequencies,
     level=100,
-    audiogram_freq=[250, 500, 1000, 2000, 4000, 6000],
+    audiogram_freq=None,
 ) -> float:
     """Better ear HASQI.
 
@@ -166,12 +171,17 @@ def hasqi_v2_better_ear(
         audiogram_r: right ear audiogram
         audiogram_cfs: audiogram frequencies
         level: level in dB SPL corresponding to RMS=1
+        audiogram_freq: selected frequencies to use for audiogram
 
     Returns:
         float: beHASQI score
 
     Gerardo Roa Dabike, November 2022
     """
+    # Default frequencies
+    if audiogram_freq is None:
+        audiogram_freq = [250, 500, 1000, 2000, 4000, 6000]
+
     # Adjust to match the frequencies
     adjusted_left = [
         audiogram_left[i]
diff --git a/clarity/evaluator/mbstoi/mbstoi.py b/clarity/evaluator/mbstoi/mbstoi.py
index bc5d4fe98..6b47ce663 100644
--- a/clarity/evaluator/mbstoi/mbstoi.py
+++ b/clarity/evaluator/mbstoi/mbstoi.py
@@ -46,7 +46,7 @@ def mbstoi(
     tau_0: float = 1.6e-3,
     level_shift_deviation: float = 1.6,
 ) -> float:
-    """Implementation of the Modified Binaural Short-Time Objective Intelligibility (mbstoi) measure.
+    """The Modified Binaural Short-Time Objective Intelligibility (mbstoi) measure.
 
     Args:
         left_ear_clean (np.ndarray): Clean speech signal from left ear.
@@ -54,7 +54,8 @@ def mbstoi(
         left_ear_noisy (np.ndarray) : Noisy/processed speech signal from left ear.
         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).
+        gridcoarseness (int) : Grid coarseness as denominator of ntaus and ngammas.
+            Defaults to 1.
         sample_rate (int) :  Sample Rate.
         n_frame (int) :  Number of Frames.
         fft_size_in_samples (int) :  ??? size in samples.
@@ -76,14 +77,15 @@ def mbstoi(
         float : mbtsoi index d.
 
     Notes:
-        All title, copyrights and pending patents pertaining to mbtsoi[1]_ in and to the original Matlab software are
-        owned by oticon a/s and/or Aalborg University. please see details at
-        `http://ah-andersen.net/code/<http://ah-andersen.net/code/>`
+        All title, copyrights and pending patents pertaining to mbtsoi[1]_ in and to the
+        original Matlab software are owned by oticon a/s and/or Aalborg University.
+        Please see `http://ah-andersen.net/code/<http://ah-andersen.net/code/>`
 
 
-    .. [1] A. H. Andersen, J. M. de Haan, Z.-H. Tan, and J. Jensen (2018) Refinement and validation of the binaural
-    short time objective intelligibility measure for spatially diverse conditions. Speech Communication vol. 102,
-    pp. 1-13 `doi:10.1016/j.specom.2018.06.001 <https://doi.org/10.1016/j.specom.2018.06.001>`_
+    .. [1] A. H. Andersen, J. M. de Haan, Z.-H. Tan, and J. Jensen (2018) Refinement and
+        validation of the binaural short time objective intelligibility measure for
+        spatially diverse conditions. Speech Communication vol. 102, pp. 1-13
+        doi:10.1016/j.specom.2018.06.001 <https://doi.org/10.1016/j.specom.2018.06.001>
     """
 
     n_taus = math.ceil(100 / gridcoarseness)  # number of tau values to try out
diff --git a/clarity/evaluator/mbstoi/mbstoi_utils.py b/clarity/evaluator/mbstoi/mbstoi_utils.py
index 4f6e3856e..53a1fdba1 100644
--- a/clarity/evaluator/mbstoi/mbstoi_utils.py
+++ b/clarity/evaluator/mbstoi/mbstoi_utils.py
@@ -27,35 +27,40 @@ def equalisation_cancellation(
 ):
     """Run the equalisation-cancellation (EC) stage of the MBSTOI metric.
 
-    The EC loop evaluates one huge equation in every iteration (see referenced notes for details). The left and right
-    ear signals are level adjusted by gamma (in dB) and time shifted by tau relative to one-another and are thereafter
-    subtracted. The processed signals are treated similarly. To obtain performance similar to that of humans,the EC
-    stage adds jitter. We are searching for the level and time adjustments that maximise the intermediate correlation
-    coefficients d. Could add location of source and interferer to this to reduce search space.
+    The EC loop evaluates one huge equation in every iteration (see referenced notes
+    for details). The left and right ear signals are level adjusted by gamma (in dB) and
+    time shifted by tau relative to one-another and are thereafter subtracted. The
+    processed signals are treated similarly. To obtain performance similar to that of
+    humans,the EC stage adds jitter. We are searching for the level and time adjustments
+    that maximise the intermediate correlation coefficients d. Could add location of
+    source and interferer to this to reduce search space.
 
     Args:
-        left_ear_clean_hat (np.ndarray) : Clean left ear short-time DFT coefficients (single-sided) per frequency
-    bin and frame.
-        right_ear_clean_hat (np.ndarray) : Clean right ear short-time DFT coefficients (single-sided) per frequency bin and frame.
-        left_ear_noisy_hat (np.ndarray) : Noisy/processed left ear short-time DFT coefficients (single-sided) per
-    frequency bin and frame.
-        right_ear_noisy_hat (np.ndarray) : Noisy/processed right eat short-time DFT coefficients (single-sided) per
-    frequency bin and frame.
-    n_third_octave_bands (int) : Number of one-third octave bands.
-    n_frames (int) : Number of frames for intermediate intelligibility measure.
-    fids (np.ndarray) : Indices of frequency band edges.
-    cf (np.ndarray) : Centre frequencies.
-    taus (np.ndarray) : Interaural delay (tau) values.
-    n_taus (int) : Number of tau values.
-    gammas (np.ndarray) : Interaural level difference (gamma) values.
-    ngammas (int) : Number of gamma values.
-    intermediate_intelligibility_measure_grid (np.ndarray) : Grid for intermediate intelligibility measure.
-    p_ec_max (np.ndarray) : Empty grid for maximum values.
-    sigma_epsilon (np.ndarray) : Jitter for gammas.
-    sigma_delta (np.ndarray) : Jitter for taus.
+        left_ear_clean_hat (np.ndarray) : Clean left ear short-time DFT coefficients
+            (single-sided) per frequency bin and frame.
+        right_ear_clean_hat (np.ndarray) : Clean right ear short-time DFT coefficients
+            (single-sided) per frequency bin and frame.
+        left_ear_noisy_hat (np.ndarray) : Noisy/processed left ear short-time DFT
+            coefficients (single-sided) per frequency bin and frame.
+        right_ear_noisy_hat (np.ndarray) : Noisy/processed right eat short-time DFT
+            coefficients (single-sided) per frequency bin and frame.
+        n_third_octave_bands (int) : Number of one-third octave bands.
+        n_frames (int) : Number of frames for intermediate intelligibility measure.
+        fids (np.ndarray) : Indices of frequency band edges.
+        cf (np.ndarray) : Centre frequencies.
+        taus (np.ndarray) : Interaural delay (tau) values.
+        n_taus (int) : Number of tau values.
+        gammas (np.ndarray) : Interaural level difference (gamma) values.
+        ngammas (int) : Number of gamma values.
+        intermediate_intelligibility_measure_grid (np.ndarray) : Grid for intermediate
+            intelligibility measure.
+        p_ec_max (np.ndarray) : Empty grid for maximum values.
+        sigma_epsilon (np.ndarray) : Jitter for gammas.
+        sigma_delta (np.ndarray) : Jitter for taus.
 
     Returns:
-        intermediate_intelligibility_measure_grid (np.ndarray) : updated grid for intermediate intelligibility measure
+        intermediate_intelligibility_measure_grid (np.ndarray) : updated grid for
+            intermediate intelligibility measure
         p_ec_max (np.ndarray) : grid containing maximum values.
     """
     taus = np.expand_dims(taus, axis=0)
@@ -382,7 +387,8 @@ def remove_silent_frames(
         right_ear_clean (np.ndarray) : Clean input signal right channel.
         left_ear_noisy (np.ndarray) : Degraded/processed signal left channel.
         right_ear_noisy (np.ndarray) : Degraded/processed signal right channel.
-        dyn_range (np.ndarray) : Range, energy range to determine which frame is silent (default : 40).
+        dyn_range (np.ndarray) : Range, energy range to determine which frame is silent
+            Default is 40.
         framelen (int) : Window size for energy evaluation (default : 256).
         hop (int) : Hop size for energy evaluation (default : 128).
 
@@ -457,7 +463,8 @@ def remove_silent_frames(
 
 
 def thirdoct(frequency_sampling: int, nfft: int, num_bands: int, min_freq: int):
-    """Returns the 1/3 octave band matrix and its center frequencies based on mpariente/pystoi.
+    """Returns the 1/3 octave band matrix and its center frequencies
+    based on mpariente/pystoi.
 
     Args:
         fs (int) : Frequency sampling rate.
@@ -507,14 +514,16 @@ def thirdoct(frequency_sampling: int, nfft: int, num_bands: int, min_freq: int):
 
 
 def find_delay_impulse(ddf: np.ndarray, initial_value: int = 22050):
-    """Find binaural delay in signal ddf given initial location of unit impulse, initial_value.
+    """Find binaural delay in signal ddf.
+
+    Finds delay given initial location of unit impulse, initial_value.
 
     Args:
         ddf (np.ndarray) :
         initial_value: (int) : Initial value (default: 22050)
 
     Returns:
-
+        delay (np.ndarray) : Binaural delay.
     """
     pk0 = find_peaks(ddf[:, 0])
     pk1 = find_peaks(ddf[:, 1])
diff --git a/clarity/evaluator/msbg/cochlea.py b/clarity/evaluator/msbg/cochlea.py
index ba0526fe4..b69f85b64 100644
--- a/clarity/evaluator/msbg/cochlea.py
+++ b/clarity/evaluator/msbg/cochlea.py
@@ -230,7 +230,8 @@ def __init__(
 
         Args:
             audiogram (Audiogram): Audiogram characterising hearing loss
-            catch_up_level (float, optional): loudness catch-up level in dB (default: {105})
+            catch_up_level (float, optional): loudness catch-up level in dB
+                Default is 105 dB
             fs (float, optional): sampling frequency
 
         """
diff --git a/clarity/evaluator/msbg/msbg.py b/clarity/evaluator/msbg/msbg.py
index fb119b56d..fd00425ed 100644
--- a/clarity/evaluator/msbg/msbg.py
+++ b/clarity/evaluator/msbg/msbg.py
@@ -3,7 +3,7 @@
 import math
 
 import numpy as np
-from scipy import interpolate, signal
+import scipy
 from scipy.signal import firwin, lfilter
 
 from clarity.evaluator.msbg.cochlea import Cochlea
@@ -49,8 +49,8 @@ def set_audiogram(self, audiogram):
         """Set the audiogram to be used."""
         if np.max(audiogram.levels[audiogram.levels is not None]) > 80:
             logging.warning(
-                "Impairment too severe: Suggest you limit audiogram max to 80-90 dB HL, \
-                otherwise things go wrong/unrealistic."
+                "Impairment too severe: Suggest you limit audiogram max to"
+                "80-90 dB HL, otherwise things go wrong/unrealistic."
             )
         self.cochlea = Cochlea(audiogram=audiogram)
 
@@ -67,7 +67,7 @@ def get_src_correction(src_pos: str) -> np.array:
         elif src_pos == "df":
             src_correction = DF_ED
         elif src_pos == "ITU":  # transfer to same grid
-            field = interpolate.interp1d(ITU_HZ, ITU_ERP_DRP, kind="linear")
+            field = scipy.interpolate.interp1d(ITU_HZ, ITU_ERP_DRP, kind="linear")
             src_correction = field(HZ)
         else:
             logging.error(
@@ -82,12 +82,12 @@ def src_to_cochlea_filt(
     ) -> np.ndarray:
         """Simulate middle and outer ear transfer functions.
 
-        Made more general, Mar2012, to include diffuse field as well as ITU reference points,
-        that were included in DOS-versions of recruitment simulator, released ca 1999-2001,
-        and on hearing group website, Mar2012 variable [src_pos] takes one of 3
-        values: 'ff', 'df' and 'ITU' free-field to cochlea filter forwards or backward direction,
-        depends on 'backward' switch. NO LONGER via 2 steps. ff to eardrum and then via middle ear:
-        use same length FIR 5-12-97.
+        Made more general, Mar2012, to include diffuse field as well as ITU reference
+        points, that were included in DOS-versions of recruitment simulator, released
+        ca 1999-2001, and on hearing group website, Mar2012 variable [src_pos] takes one
+        of 3 values: 'ff', 'df' and 'ITU' free-field to cochlea filter forwards or
+        backward direction, depends on 'backward' switch. NO LONGER via 2 steps. ff to
+        eardrum and then via middle ear: use same length FIR 5-12-97.
 
         Args:
             ip_sig (ndarray): signal to process
@@ -110,7 +110,7 @@ def src_to_cochlea_filt(
 
         # sig from free field to cochlea: 0 dB at 1kHz
         correction = src_correction - MIDEAR
-        field = interpolate.interp1d(HZ, correction, kind="linear")
+        field = scipy.interpolate.interp1d(HZ, correction, kind="linear")
         last_correction = field(nyquist)  # generate synthetic response at Nyquist
 
         correction_used = np.append(correction[ixf_useful], last_correction)
@@ -120,12 +120,12 @@ def src_to_cochlea_filt(
 
         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
+        # Scales with fs, fails with longer windows in fir2 in original MATLAB version
         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))
-        op_sig = signal.lfilter(b, 1, ip_sig)
+        op_sig = scipy.signal.lfilter(b, 1, ip_sig)
 
         return op_sig
 
@@ -255,10 +255,10 @@ def process(self, signal: np.ndarray, add_calibration: bool = False) -> np.ndarr
             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
+        # 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 * sample_frequency) + 1
         lpf44d1 = firwin(
             winlen, UPPER_CUTOFF_HZ / int(sample_frequency / 2), window=("kaiser", 8)
diff --git a/clarity/evaluator/msbg/msbg_utils.py b/clarity/evaluator/msbg/msbg_utils.py
index b93b2027f..bede043ec 100644
--- a/clarity/evaluator/msbg/msbg_utils.py
+++ b/clarity/evaluator/msbg/msbg_utils.py
@@ -126,9 +126,10 @@ def firwin2(
 
     Args:
         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.
+        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))
+        window (string or (string, float), optional): See scipy.firwin2. Default is None
         antisymmetric (bool, optional): Unused but present to main compatability
             with scipy firwin2.
 
@@ -168,8 +169,8 @@ def fir2(
 
     Args:
         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.
+        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))
@@ -238,8 +239,9 @@ def gen_tone(
     Args:
         freq (float): Frequency of tone in Hz.
         duration (float): Duration of tone in seconds.
-        sample_frequency (float, optional): Sample rate of generated tone in Hz. (default: 44100)
-        level (float, optional): Level of tone in dB SPL. (default: 0)
+        sample_frequency (float, optional): Sample rate of generated tone in Hz.
+            Default is 44100.
+        level (float, optional): Level of tone in dB SPL. Default is 0.
 
     Returns:
         np.ndarray
@@ -265,7 +267,8 @@ def gen_eh2008_speech_noise(
 ) -> 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.
 
@@ -273,7 +276,8 @@ def gen_eh2008_speech_noise(
         duration (float): Duration of signal in seconds
         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
@@ -348,7 +352,8 @@ def generate_key_percent(
         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}).
+        percent_to_track (float, optional): Track a percentage of frames.
+            Default is None
 
     Raises:
         ValueError: percent_to_track is set too high.
@@ -422,7 +427,8 @@ def generate_key_percent(
     # 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
-    # FixMe : Could Otsu's method (from image processing) be used here? https://en.wikipedia.org/wiki/Otsu's_method
+    # TODO : 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 * window_length))[0]
@@ -512,7 +518,7 @@ def read_signal(
 
     Args:
         filename (string): Name of file to read
-        offset (int, optional): Offset in samples or seconds (from start). Defaults to 0.
+        offset (int, optional): Offset in samples or seconds (from start). Default is 0.
         nsamples (int): Number of samples.
         nchannels (int): expected number of channel (default: 0 = any number OK)
         offset_is_samples (bool): measurement units for offset (default: False)
@@ -520,14 +526,11 @@ def read_signal(
     Returns:
         np.ndarray: audio signal
     """
-    try:
-        wave_file = SoundFile(filename)
-    except Exception as e:
-        # Ensure incorrect error (24 bit) is not generated
-        raise Exception(f"Unable to read {filename}.") from e
+
+    wave_file = SoundFile(filename)
 
     if nchannels not in (0, wave_file.channels):
-        raise Exception(
+        raise ValueError(
             f"Wav file ({filename}) was expected to have {nchannels} channels."
         )
 
@@ -564,7 +567,8 @@ def write_signal(
 
     if sample_frequency != MSBG_FS:
         logging.warning(
-            f"Sampling rate mismatch: {filename} with sample frequency = {sample_frequency}."
+            f"Sampling rate mismatch: {filename} with "
+            f"sample frequency = {sample_frequency}."
         )
         # raise ValueError("Sampling rate mismatch")
 
diff --git a/clarity/predictor/torch_msbg.py b/clarity/predictor/torch_msbg.py
index 62798a508..578e02d82 100644
--- a/clarity/predictor/torch_msbg.py
+++ b/clarity/predictor/torch_msbg.py
@@ -6,12 +6,12 @@
 
 import numpy as np
 import torch
-import torch.nn as nn
 import torch.nn.functional as F
 import torchaudio
 from scipy.fftpack import fft
 from scipy.interpolate import interp1d
 from scipy.signal import ellip, firwin, firwin2, freqz
+from torch import nn
 
 from clarity.evaluator.msbg.msbg_utils import (
     DF_ED,
@@ -27,12 +27,13 @@
 # set RMS so that peak of output file so that no clipping occurs, set so that
 # equiv0dBfileSPL > 100dB for LOUD input files
 ref_RMSdB = -31.2
-calib_dBSPL = (
-    65  # what RMS of INPUT speech file translates to in real world (unweighted)
-)
-equiv0dBfileSPL = (
-    calib_dBSPL - ref_RMSdB
-)  # what 0dB file signal would translate to in dB SPL: constant for cochlear_simulate function
+
+# what RMS of INPUT speech file translates to in real world (unweighted)
+calib_dBSPL = 65
+
+# what 0dB file signal would translate to in dB SPL:
+# constant for cochlear_simulate function
+equiv0dBfileSPL = calib_dBSPL - ref_RMSdB
 
 # clarity msbg
 equiv0dBSPL = 100
@@ -76,16 +77,19 @@ def generate_key_percent(signal, thr_dB, winlen):
 
 
 def measure_rms(signal, sr, dB_rel_rms):
-    """Measures toatal power of all 10 msec frams that are above a user-specified threshold
+    """Compute RMS level of a signal.
+
+    Measures total power of all 10 msec frames that are above a user-specified threshold
 
     Args:
         signal: input signal
         sr: sampling rate
-        dB_rel_rms: threshold relative to first-stage rms (if it is made of a 2*1 array, second value over rules.)
-                    only single value supported currently
+        dB_rel_rms: threshold relative to first-stage rms (if it is made of a 2*1 array,
+            second value over rules. only single value supported currently)
+
     Returns:
-        tuple: The percentage of frames that are required to be tracked for measuring RMS (useful when DR compression
-               changes histogram shape)
+        tuple: The percentage of frames that are required to be tracked for measuring
+        RMS (useful when DR compression changes histogram shape)
     """
     win_secs = 0.01
     # first RMS is of all signal
diff --git a/clarity/utils/car_noise_simulator/__init__.py b/clarity/utils/car_noise_simulator/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/clarity/utils/car_noise_simulator/carnoise_parameters_generator.py b/clarity/utils/car_noise_simulator/carnoise_parameters_generator.py
new file mode 100644
index 000000000..83dc83a92
--- /dev/null
+++ b/clarity/utils/car_noise_simulator/carnoise_parameters_generator.py
@@ -0,0 +1,236 @@
+"""
+Class to generate random parameters for the Car noise signal generation
+
+These are 2 separated class to keep the logic separated
+"""
+from typing import Dict, Optional, Tuple
+
+import numpy as np
+
+
+class CarNoiseParametersGenerator:
+    """
+    A class to generate noise parameters for a car.
+    The constructor takes a boolean flag to indicate whether some
+    parameters should be randomized or not.
+
+    The method `gen_parameters` takes a speed in kilometers per hour
+    and returns a dictionary of noise parameters.
+
+    The global variables `GEAR_LOOKUP` and `RPM_LOOKUP`
+    are used to determine the gear and RPM for a given speed.
+
+    Example:
+        >>> car_noise_parameters = CarNoiseParameters(random_flag=True)
+        >>> parameters = car_noise_parameters.gen_parameters(speed_kph=100)
+        >>> parameters
+        {
+        "speed": 70,
+        "gear": 4,
+        "rpm": 1890.0,
+        "primary_filter": {
+            "order": 1,
+            "btype": "lowpass",
+            "cutoff_hz": 12.685320000000003,
+        },
+        "secondary_filter": {
+            "order": 2,
+            "btype": "lowpass",
+            "cutoff_hz": 276.22400000000005,
+        },
+        "bump": {"order": 1, "btype": "bandpass", "cutoff_hz": [34, 66.64000000000001]},
+        "dip_low": {"order": 1, "btype": "lowpass", "cutoff_hz": 90},
+        "dip_high": {"order": 1, "btype": "highpass", "cutoff_hz": 220.12494775682322},
+    }
+    """
+
+    GEAR_LOOKUP = {110: [6], 100: [5, 6], 85: [5], 75: [4, 5], 60: [4], 50: [3]}
+    RPM_LOOKUP = {6: 0.28, 5: 0.34, 4: 0.45, 3: 0.60}
+    REFERENCE_CONSTANT_DB = 30
+
+    def __init__(
+        self,
+        primary_filter_cutoff: float = 0.14,
+        primary_filter_constant_hz: float = 2.86,
+        secondary_filter_cutoff: float = 0.8,
+        secondary_filter_constant_hz: float = 200,
+        random_flag: bool = True,
+        random_seed: Optional[int] = None,
+    ) -> None:
+        """
+        Constructor takes a boolean flag to indicate whether
+        some parameters should be randomized or not.
+
+        It preset several speed independent parameters for the noise generation.
+
+        Args:
+            primary_filter_cutoff (float, optional): The speed dependent cutoff in Hz
+                for Primary Filter
+            primary_filter_constant_hz (float, optional): The constant cutoff in Hz
+                for Primary Filter
+            secondary_filter_cutoff (float, optional): The speed dependent cutoff in Hz
+                for Secondary Filter
+            secondary_filter_constant_hz (float, optional): The constant cutoff in Hz
+                for Secondary Filter
+            random_flag (bool, optional): Flag to indicate whether some parameters
+                should be randomized or not
+            random_seed (int, optional): Random seed to use for randomization
+
+        """
+
+        self.random_flag = random_flag
+
+        if self.random_flag and random_seed is not None:
+            np.random.seed(random_seed)
+
+        # .. randomization range for frequency multiplier
+        self.randomisationrange_freqmultiplier = (
+            np.arange(0.9, 1.1, 0.001) if self.random_flag else [1]
+        )
+
+        # primray filter .. fix at 6 dB per octave and a
+        # slight speed-dependent Hz + randomization
+        self.primary_filter = {
+            "speeddependence_cutoff_hzperkph": primary_filter_cutoff,
+            "constant_hz": primary_filter_constant_hz,
+        }
+
+        # .. secondary filter .. fix at 12 dB per octave and a
+        # slight speed-dependent Hz + randomization
+        self.secondary_filter = {
+            "speeddependence_cutoff_hzperkph": secondary_filter_cutoff,
+            "constant_hz": secondary_filter_constant_hz,
+        }
+
+        # bump
+        self.bump_lowerlimitforrandomization_db = 0 if self.random_flag else 6
+        self.bump_upperlimitforrandomization_db = 6
+
+    def set_new_randomisationrange_freqmultiplier(self):
+        """Set a new freqeuncy multiplier use in the
+        primary and secondary filters
+        """
+        self.randomisationrange_freqmultiplier = (
+            np.arange(0.9, 1.1, 0.001) if self.random_flag else [1]
+        )
+
+    def gen_parameters(self, speed_kph: float) -> Dict:
+        """
+        Generate a dictionary of noise parameters for a given speed.
+
+        Parameters
+        ----------
+        speed_kph : float
+            The speed of the car in kilometers per hour between 50 and 120 km/h.
+
+        Returns
+        -------
+        parameters : dict
+            A dictionary of noise parameters.
+        """
+
+        if not 50 <= speed_kph <= 120:
+            raise ValueError("Speed should be between 50 and 120 kph.")
+
+        gear = self._get_gear(speed_kph)
+        rpm = self._get_rpm(gear, speed_kph)
+
+        referencelevel_db = self.REFERENCE_CONSTANT_DB
+        if self.random_flag:
+            referencelevel_db += np.random.choice(np.arange(0, 3.1, 0.1))
+
+        engine_num_harmonics = (
+            25 if not self.random_flag else np.random.choice(np.arange(10, 41))
+        )
+
+        primary_filter = self._generate_primary_filter(speed_kph)
+        secondary_filter = self._generate_secondary_filter(speed_kph)
+        bump_filter = self._generate_bump_filter()
+        dip_filter_low, dip_filter_high = self._generate_dip_filter()
+
+        return {
+            "speed": float(speed_kph),
+            "gear": int(gear),
+            "reference_level_db": float(referencelevel_db),
+            "engine_num_harmonics": int(engine_num_harmonics),
+            "rpm": float(rpm),
+            "primary_filter": primary_filter,
+            "secondary_filter": secondary_filter,
+            "bump": bump_filter,
+            "dip_low": dip_filter_low,
+            "dip_high": dip_filter_high,
+        }
+
+    def _get_gear(self, speed_kph: float) -> int:
+        for speed, possible_gears in self.GEAR_LOOKUP.items():
+            if speed_kph >= speed:
+                return int(
+                    possible_gears[-1]
+                    if not self.random_flag
+                    else np.random.choice(possible_gears)
+                )
+        return 0
+
+    def _get_rpm(self, gear: int, speed_kph: float) -> float:
+        rpm = self.RPM_LOOKUP[gear] * speed_kph * 60
+        return rpm
+
+    def _generate_primary_filter(self, speed_kph: float) -> Dict:
+        return {
+            "order": 1,
+            "btype": "lowpass",
+            "cutoff_hz": (
+                self.primary_filter["speeddependence_cutoff_hzperkph"] * speed_kph
+                + self.primary_filter["constant_hz"]
+            )
+            * np.random.choice(self.randomisationrange_freqmultiplier),
+        }
+
+    def _generate_secondary_filter(self, speed_kph: float) -> Dict:
+        return {
+            "order": 2,
+            "btype": "lowpass",
+            "cutoff_hz": (
+                self.secondary_filter["speeddependence_cutoff_hzperkph"] * speed_kph
+                + self.secondary_filter["constant_hz"]
+            )
+            * np.random.choice(self.randomisationrange_freqmultiplier),
+        }
+
+    def _generate_bump_filter(self) -> Dict:
+        bumpfilter_order = 1 if not self.random_flag else np.random.choice([1, 2])
+        bumpfilter_f1_hz = (
+            30 if not self.random_flag else np.random.randint(20, 101, size=1)[0]
+        )
+        bumpfilter_f2_hz = (
+            60
+            if not self.random_flag
+            else bumpfilter_f1_hz * np.random.choice(np.arange(1.25, 2.01, 0.01))
+        )
+        filter_dict = {
+            "order": int(bumpfilter_order),
+            "btype": "bandpass",
+            "cutoff_hz": [int(bumpfilter_f1_hz), int(bumpfilter_f2_hz)],
+        }
+        return filter_dict
+
+    def _generate_dip_filter(self) -> Tuple[Dict, Dict]:
+        dipfilter_order = 2 if not self.random_flag else np.random.choice([1, 2])
+        dipfilter_f1_hz = (
+            200 if not self.random_flag else np.random.choice(range(30, 201, 10))
+        )
+        dipfilter_f2_hz = (
+            300 if not self.random_flag else dipfilter_f1_hz * np.random.uniform(2, 3)
+        )
+        filter_dict_low = {
+            "order": int(dipfilter_order),
+            "btype": "lowpass",
+            "cutoff_hz": int(dipfilter_f1_hz),
+        }
+        filter_dict_high = {
+            "order": int(dipfilter_order),
+            "btype": "highpass",
+            "cutoff_hz": int(dipfilter_f2_hz),
+        }
+
+        return filter_dict_low, filter_dict_high
diff --git a/clarity/utils/car_noise_simulator/carnoise_signal_generator.py b/clarity/utils/car_noise_simulator/carnoise_signal_generator.py
new file mode 100644
index 000000000..7d87f0f33
--- /dev/null
+++ b/clarity/utils/car_noise_simulator/carnoise_signal_generator.py
@@ -0,0 +1,395 @@
+"""
+Class to generate the car noise signal.
+"""
+from typing import Dict, Tuple, Union
+
+import numpy as np
+from scipy.signal import butter, lfilter
+
+
+def _butter_filter(
+    order: int, cutoff_hz: Union[list, np.ndarray], btype: str, sample_rate: int
+) -> Tuple[np.ndarray, np.ndarray]:
+    """
+    Function that creates a butter filter
+    Args:
+        order (int): Order of the filter
+        cutoff_hz (list or np.ndarray): Cutoff frequency of the filter
+        btype (str): Type of the filter
+        sample_rate (int): Sampling frequency
+    Returns:
+        numer (np.ndarray): Numerator of the filter
+        denom (np.ndarray): Denominator of the filter
+    """
+    if isinstance(cutoff_hz, list):
+        cutoff_hz = np.array(cutoff_hz)
+    numer, denom = butter(
+        order, cutoff_hz / (sample_rate / 2.0), btype=btype, output="ba"
+    )
+    return numer, denom
+
+
+class CarNoiseSignalGenerator:
+    """
+    A class to generate car noise signal.
+
+    The constructor takes the sample_rate and duration of
+    the generated signals.
+
+    The method `generate_car_noise` takes parameters for the
+    noise and generates the signal. These parameters
+    can be generated by the CarNoiseParameters class.
+
+
+    Example:
+        >>> car_noise_parameters = CarNoiseParameters(random_flag=True)
+        >>> parameters = car_noise_parameters.gen_parameters(speed_kph=100)
+
+        >>> car_noise = CarNoiseGenerator(sample_rate=44100, duration_secs=5,
+                random_flag=True)
+        >>> car_noise_signal = car_noise.generate_car_noise(parameters, 3, 0.5)
+    """
+
+    REFERENCE_CONSTANT_DB = 30
+    FINAl_MULTIPLIER = 1 / 2000
+
+    def __init__(
+        self,
+        sample_rate: int,
+        duration_secs: float,
+        random_flag: bool = True,
+    ):
+        """Constructor takes the sample_rate and duration of the generated signals.
+
+        Args:
+            sample_rate (int): Sample rate of the generated signal
+            duration_secs (float): Duration of the generated signal
+            random_flag (bool, optional): Flag to indicate whether add some randomness
+            to the output signals. Defaults to True.
+        """
+        self.sample_rate = sample_rate
+        self.duration_secs = duration_secs
+        self.random_flag = random_flag
+
+        self.sampleduration_secs = 1 / self.sample_rate
+        self.duration_samples = round(self.duration_secs) * self.sample_rate
+        self.timevector_ms = np.arange(self.duration_samples) * self.sampleduration_secs
+
+    def generate_car_noise(
+        self,
+        noise_parameters: dict,
+        number_noise_sources: int,
+        commonness_factor: float,
+    ) -> np.ndarray:
+        """
+        Method that generates the car noise signal.
+        It organizes the parameters and calls the methods to generate the independent
+        parts.
+
+        Args:
+            noise_parameters (dict): Dictionary with the parameters for the noise
+                as generated by the CarNoiseParameters class.
+            number_noise_sources (int): Number of noise sources. First source is the
+                engine noise. Following sources are other noise sources.
+            commonness_factor (float): Commonness factor
+        Returns:
+            np.ndarray: Car noise signal
+        """
+
+        # .. reference level = no speed dependence  plus a small randomization
+        referencelevel_db = noise_parameters["reference_level_db"]
+
+        filter_numer_denom: Dict = {}
+        for filter_type in [
+            "primary_filter",
+            "secondary_filter",
+            "bump",
+            "dip_low",
+            "dip_high",
+        ]:
+            if filter_type in noise_parameters:
+                filter_numer_denom[filter_type] = {}
+                numer, denom = _butter_filter(
+                    **noise_parameters[filter_type], sample_rate=self.sample_rate
+                )
+                filter_numer_denom[filter_type]["numer"] = numer
+                filter_numer_denom[filter_type]["denom"] = denom
+            else:
+                raise ValueError(f"Parameter for filter {filter_type} are mandatory.")
+
+        # initialise waveform matrix
+        # global_noise = Main array storing the different sources
+        # extra_noise_for_coherence = Array to add some correlation between sources
+        car_noise = np.zeros((number_noise_sources + 1, self.duration_samples))
+        extra_noise_for_coherence = np.zeros((1, self.duration_samples))
+
+        # Generate Engine Noise
+        # stored in index 0 in source_noise
+        engine_noise = self.generate_engine_noise(
+            speed=noise_parameters["speed"],
+            rpm=noise_parameters["rpm"],
+            engine_num_harmonics=noise_parameters["engine_num_harmonics"],
+            reference_level_db=referencelevel_db,
+            primary_filter=filter_numer_denom["primary_filter"],
+            secondary_filter=filter_numer_denom["secondary_filter"],
+        )
+        car_noise[0, :] = engine_noise
+
+        # Generate Noise for N sources
+        for n in range(1, number_noise_sources + 2):
+            source_n_noise = self.generate_source_noise(
+                reference_level_db=referencelevel_db,
+                primary_filter=filter_numer_denom["primary_filter"],
+                secondary_filter=filter_numer_denom["secondary_filter"],
+                bump_filter=filter_numer_denom["bump"],
+                dip_low_filter=filter_numer_denom["dip_low"],
+                dip_high_filter=filter_numer_denom["dip_high"],
+            )
+
+            if n <= number_noise_sources:
+                car_noise[n, :] = source_n_noise
+            else:
+                extra_noise_for_coherence[0, :] = source_n_noise
+
+        # Add some correlation between sources
+        for n in range(1, number_noise_sources + 1):
+            car_noise[n, :] = self.apply_commonness(
+                car_noise[n, :], extra_noise_for_coherence[0, :], commonness_factor
+            )
+
+        return car_noise * self.FINAl_MULTIPLIER
+
+    def generate_source_noise(
+        self,
+        reference_level_db: float,
+        primary_filter: Dict[str, np.ndarray],
+        secondary_filter: Dict[str, np.ndarray],
+        bump_filter: Dict[str, np.ndarray],
+        dip_low_filter: Dict[str, np.ndarray],
+        dip_high_filter: Dict[str, np.ndarray],
+    ) -> np.ndarray:
+        """
+        Method that generates the noise of a single source.
+
+        Args:
+            reference_level_db (float): Reference level in dB
+            primary_filter (Dict[str, np.ndarray]): Primary filter
+            secondary_filter (Dict[str, np.ndarray]): Secondary filter
+            bump_filter (Dict[str, np.ndarray]): Bump filter
+            dip_low_filter (Dict[str, np.ndarray]): Low dip filter
+            dip_high_filter (Dict[str, np.ndarray]): High dip filter
+
+        Returns:
+            np.ndarray: Noise of a single source
+
+        """
+        lowpasslevel_db = (
+            reference_level_db + 35
+        )  # puts it slightly quieter than the tones
+        lowpassnoise_gaussianstd = 10 ** (lowpasslevel_db / 20)
+
+        source_noise = (
+            np.random.normal(size=self.duration_samples) * lowpassnoise_gaussianstd
+        )
+
+        source_noise = lfilter(
+            primary_filter["numer"], primary_filter["denom"], source_noise
+        )
+
+        # Add bump to the noise
+        # Get Bump noise parameters
+        lowpassnoise_gaussianstd, bump_gaussianstd = self.get_bump_params(
+            reference_level_db
+        )
+        bump_waveform = np.random.normal(size=self.duration_samples) * bump_gaussianstd
+        bump_waveform = lfilter(
+            bump_filter["numer"], bump_filter["denom"], bump_waveform
+        )
+
+        source_noise = source_noise + bump_waveform
+
+        # Add dip to the noise
+        dip_lowerpart = lfilter(
+            dip_low_filter["numer"], dip_low_filter["denom"], source_noise
+        )
+        dip_upperpart = lfilter(
+            dip_high_filter["numer"], dip_high_filter["denom"], source_noise
+        )
+        source_noise = dip_lowerpart + dip_upperpart
+
+        # final secondary filter
+        source_noise = lfilter(
+            secondary_filter["numer"], secondary_filter["denom"], source_noise
+        )
+
+        return source_noise
+
+    def generate_engine_noise(
+        self,
+        speed: float,
+        rpm: float,
+        reference_level_db: float,
+        engine_num_harmonics: int,
+        primary_filter: Dict[str, np.ndarray],
+        secondary_filter: Dict[str, np.ndarray],
+    ) -> np.ndarray:
+        """
+        Method that generates the noise of the engine.
+        Args:
+            speed (float): Speed of the car in km/h
+            rpm (float): RPM of the engine
+            reference_level_db (float): Reference level in dB
+            engine_num_harmonics (int): Number of harmonics of the engine
+            primary_filter (Dict[str, np.ndarray]): Primary filter
+            secondary_filter (Dict[str, np.ndarray]): Secondary filter
+
+        Returns:
+            np.ndarray: Noise of the engine
+
+        """
+        # Creating butter filters for engine noise
+
+        (
+            harmonic_complex_freqs_hz,
+            harmonic_complex_power_db,
+        ) = self.get_engine_params(speed, rpm, reference_level_db, engine_num_harmonics)
+
+        engine_noise = np.zeros(self.duration_samples)
+
+        for c in range(engine_num_harmonics):
+            component = {}
+            # set amplitude of tone
+            component["amplitude"] = 10 ** (harmonic_complex_power_db[c] / 20)
+            # set random phase
+            component["phase_rads"] = np.random.rand() * 2 * np.pi
+            # set angular frequency
+            component["angularfreq_rads"] = 2 * np.pi * harmonic_complex_freqs_hz[c]
+            # make :-)
+            component_waveform = component["amplitude"] * np.sin(
+                component["angularfreq_rads"] * self.timevector_ms
+                + component["phase_rads"]
+            )
+            # store
+            engine_noise += component_waveform
+
+        # Applies Primary and Secondary filters
+        engine_noise = lfilter(
+            primary_filter["numer"],
+            primary_filter["denom"],
+            engine_noise,
+        )
+        engine_noise = lfilter(
+            secondary_filter["numer"],
+            secondary_filter["denom"],
+            engine_noise,
+        )
+
+        return engine_noise
+
+    def get_bump_params(self, reference_level_db: float) -> Tuple[float, float]:
+        """
+        Method that gets the parameters of the bump noise
+        Args:
+            reference_level_db (float): Reference level in dB
+
+        Returns:
+            lowpass_noise_gaussian_std (float): Standard deviation of the low pass noise
+            bump_gaussian_std (float): Standard deviation of the bump noise
+        """
+        lowpasslevel_db = (
+            reference_level_db + 35
+        )  # puts it slightly quieter than the tones
+        lowpass_noise_gaussian_std = 10 ** (lowpasslevel_db / 20)
+
+        bump_level_db = lowpasslevel_db + np.random.choice(
+            np.arange(6 if not self.random_flag else 0, 6 + 1, 1)
+        )
+        bump_gaussian_std = 10 ** (bump_level_db / 20)
+
+        return lowpass_noise_gaussian_std, bump_gaussian_std
+
+    def get_engine_params(
+        self,
+        speed: float,
+        rpm: float,
+        reference_level_db: float,
+        engine_num_harmonics: int,
+    ) -> Tuple[np.ndarray, np.ndarray]:
+        """
+        Method that gets the parameters of the engine noise
+
+        Args:
+            speed (float): Speed of the car in km/h
+            rpm (float): RPM of the engine
+            reference_level_db (float): Reference level in dB
+            engine_num_harmonics (int): Number of harmonics of the engine
+
+        Returns:
+            harmoniccomplex_freqs_hz (np.ndarray): Frequency of the harmonic complex
+            harmoniccomplex_ntones (int): Number of tones of the harmonic complex
+            harmoniccomplex_power_db (np.ndarray): Power of the harmonic complex
+        """
+
+        harmoniccomplex_f0 = rpm / 60
+
+        harmoniccomplex_freqs_hz = harmoniccomplex_f0 * np.arange(
+            1, engine_num_harmonics + 1
+        )
+
+        tone_speeddependence_level_dbperkph = 2 * 0.067
+        toneconstant_db = -3
+
+        harmoniccomplex_power_db = np.zeros(engine_num_harmonics)
+
+        # Generating power levels for each component
+        for n in range(engine_num_harmonics):
+            harmoniccomplex_power_db[n] = (
+                reference_level_db
+                + toneconstant_db
+                + tone_speeddependence_level_dbperkph * speed
+                + np.random.choice(
+                    np.arange(
+                        0 if not self.random_flag else -2,
+                        0 if not self.random_flag else 2.1,
+                        0.1,
+                    )
+                )
+            )
+            if self.random_flag == 0:
+                if n + 1 in [5, 7, 13, 14, 15, 19, 21, 23]:
+                    harmoniccomplex_power_db[n] = -99
+            else:
+                if np.random.uniform(0, 1) < 0.333:
+                    harmoniccomplex_power_db[n] = -99
+
+        return (
+            harmoniccomplex_freqs_hz,
+            harmoniccomplex_power_db,
+        )
+
+    @staticmethod
+    def apply_commonness(
+        target_signal: np.ndarray,
+        coherence_signal: np.ndarray,
+        commonness_factor: float,
+    ) -> np.ndarray:
+        """Function to apply correlation between the target signal
+        using the coherence signal.
+
+        Args:
+            target_signal (np.ndarray): Target signal
+            coherence_signal (np.ndarray): Coherence signal
+            commonness_factor (float): Commonness factor
+
+        Returns:
+            target_signal (np.ndarray): Target signal with the coherence signal
+        """
+        target_signal = (
+            1 - commonness_factor
+        ) * target_signal + commonness_factor * coherence_signal
+
+        # correct amplitude for the sum of two gaussians
+        correctionfactor = 1 / np.sqrt(
+            (1 - commonness_factor) ** 2 + commonness_factor**2
+        )
+        return target_signal * correctionfactor
diff --git a/docs/conf.py b/docs/conf.py
index b0e7f6701..27a645b87 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -39,7 +39,7 @@
 # -- Project information -----------------------------------------------------
 
 project = "pyclarity"
-copyright = "2022, pyclarity authors"
+copyright = "2022, pyclarity authors"  # pylint: disable=redefined-builtin
 author = "pyclarity authors"
 
 # The short X.Y version
@@ -123,7 +123,8 @@
 #         {
 #             "rel": "icon",
 #             "sizes": "156x20",
-#             "href": "https://github.com/Mesnage-Org/clarity/actions/workflows/ci-tests.yml/badge.svg",
+#             "href": "https://github.com/Mesnage-Org/clarity/actions/workflows/\
+#                           ci-tests.yml/badge.svg",
 #         },
 #         {
 #             "rel": "icon",
diff --git a/pyproject.toml b/pyproject.toml
index 5b7665f38..2862389b8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -40,4 +40,11 @@ exclude = [
 [tool.coverage.run]
 omit = [
     "clarity/_version.py"
-]
\ No newline at end of file
+]
+
+
+[tool.ruff]
+line-length = 88
+# pyflakes, pycodestyle, isort
+select = ["F", "E", "W", "B", "I001"]
+ignore = ["B905"]
\ No newline at end of file
diff --git a/recipes/cad1/README.md b/recipes/cad1/README.md
index 423f93bae..9b991ac21 100644
--- a/recipes/cad1/README.md
+++ b/recipes/cad1/README.md
@@ -21,7 +21,23 @@ The performance of each system on the validation set is reported below.
 
 ### Task 1 - Listening music via headphones
 
-TODO - add Table with results
+The average HAAQI score per song is:
+
+| Song                                            |   HAAQI    |
+|:------------------------------------------------|:----------:|
+| Actions - One Minute Smile                      |   0.3066   |
+| Alexander Ross - Goodbye Bolero                 |   0.4257   |
+| ANiMAL - Rockshow                               |   0.2389   |
+| Clara Berry And Wooldog - Waltz For My Victims  |   0.4202   |
+| Fergessen - Nos Palpitants                      |   0.4554   |
+| James May - On The Line                         |   0.3889   |
+| Johnny Lokke - Promises & Lies                  |   0.3395   |
+| Leaf - Summerghost                              |   0.3595   |
+| Meaxic - Take A Step                            |   0.3470   |
+| Patrick Talbot - A Reason To Leave              |   0.4545   |
+| Skelpolu - Human Mistakes                       |   0.3055   |
+| Triviul - Angelsaint                            |   0.2883   |
+| **Total Result**                                | **0.3608** |
 
 ### Task 2 - Listening music in a car wearing hearing aids
 
diff --git a/recipes/cad1/task1/baseline/README.md b/recipes/cad1/task1/baseline/README.md
index 0ed4109e4..0f45ae941 100644
--- a/recipes/cad1/task1/baseline/README.md
+++ b/recipes/cad1/task1/baseline/README.md
@@ -176,7 +176,7 @@ Please note: you will not get identical HAAQI scores for the same signals if the
 (in the given recipe, the random seed for each signal is set as the last eight digits of the song md5).
 As there are random noises generated within HAAQI, but the differences should be sufficiently small.
 
-**Baseline performance on the validation set will be updated soon.**
+The score for the baseline enhancement is 0.3608 HAAQI overall.
 
 ## References
 
diff --git a/recipes/cad1/task1/baseline/enhance.py b/recipes/cad1/task1/baseline/enhance.py
index d5985a7ea..17a7fd29a 100644
--- a/recipes/cad1/task1/baseline/enhance.py
+++ b/recipes/cad1/task1/baseline/enhance.py
@@ -311,7 +311,8 @@ def enhance(config: DictConfig) -> None:
     for idx, song_listener in enumerate(valid_song_listener_pairs, 1):
         song_name, listener_name = song_listener
         logger.info(
-            f"[{idx:03d}/{num_song_list_pair:03d}] Processing {song_name} for {listener_name}..."
+            f"[{idx:03d}/{num_song_list_pair:03d}] "
+            f"Processing {song_name} for {listener_name}..."
         )
         # Get the listener's audiogram
         listener_info = listener_valid_audiograms[listener_name]
@@ -353,9 +354,9 @@ def enhance(config: DictConfig) -> None:
                 audiogram_right,
             )
 
-        # Baseline applies NALR prescription to each stem
-        # instead of using the listener's audiograms in the decomposition
-        # This stemp can be skipped if the listener's audiograms are used in the decomposition
+        # Baseline applies NALR prescription to each stem instead of using the
+        # listener's audiograms in the decomposition. This stem can be skipped
+        # if the listener's audiograms are used in the decomposition
         processed_stems = process_stems_for_listener(
             stems,
             enhancer,
diff --git a/recipes/cad1/task2/baseline/README.md b/recipes/cad1/task2/baseline/README.md
new file mode 100644
index 000000000..9ff7779f5
--- /dev/null
+++ b/recipes/cad1/task2/baseline/README.md
@@ -0,0 +1,148 @@
+# The First Cadenza Challenge (CAD1) - Task 2: Listening music in a car
+
+Cadenza challenge code for the First Cadenza Challenge (CAD1) Task2.
+
+For more information please visit the [challenge website](https://cadenzachallenge.org/docs/cadenza1/cc1_intro).
+
+## 1. Data structure
+
+### 1.1 Obtaining the CAD1 - Task2 data
+
+The music dataset for the First Cadenza Challenge - Task 2 is based on the small subset of the FMA [2] dataset
+(FMA-small) and the MTG-Jamendo dataset [4]. The dataset contains 1000 samples from seven musical genres,
+totalling 7000 songs with a distribution of 80% / 10% / 10% for `train`, `valid` and `test`.
+
+From FMA small:
+
+* Hip-Hop
+* Instrumental
+* International
+* Pop
+* Rock
+
+From MTG-Jamendo:
+
+* Classical
+* Orchestral
+
+The HRTFs data is based on the eBrIRD - ELOSPHERES binaural room impulse response database.
+
+To download the data, please visit [here](https://forms.gle/9L5ncYKe2YhD5c828).
+
+The data will download into a package file called `cadenza_cad1_task2_core.v1_1.tar.gz`.
+
+Unpack this package using
+
+```bash
+tar -xvzf cadenza_cad1_task2_core.v1_1.tar.gz
+```
+
+Once unpacked the directory structure will be as follows
+
+**cadenza_cad1_task2_core.v1.0** contains the training and validation data:
+
+```bash
+clarity_CPC2_data
+└── cadenza_data
+   └── cad1  # The hearing aid output signals
+        └── taks2
+            ├── audio
+            |   ├── eBrird  # HRTFs directory
+            |   └── music
+            |       ├── training
+            |       └── validation
+            ├── metadata  # Metadata
+            └── manifest  # Lists the package contents
+```
+
+### 1.2 Demo data
+
+To help you to start with the challenge, we provide a small subset of the data.
+The `demo_data` folder contains a single song and two listeners from the validation set.
+
+To use the demo data, simply download the package `cadenza_task2_data_demo.tar.tar.xz`
+from [here](https://drive.google.com/drive/folders/1On5Bv7Sd6zLZWfA76jdkM-FmGS61Mbi-?usp=share_link)
+and unpack it under `recipes/cad1/task2/`, i.e., one level above the baseline directory.
+Note that the `root.path` variable in `config.yaml` is already set to the demo data by default.
+
+To unpack the demo data, run:
+
+```bash
+tar -xvf cadenza_data_demo.tar.xz
+```
+
+## 2. Baseline
+
+In the `baseline/` folder, we provide code for running the baseline enhancement system and performing
+the objective evaluation. Note that we use [hydra](https://hydra.cc/docs/intro/) for config handling.
+
+The baseline uses librosa to read the MP3 audio files. Librosa will raise error is libsoundfile and ffmpeg are not installed.
+If you have an Anaconda or Miniconda environment, you can install them as:
+
+* conda install -c conda-forge ffmpeg
+* conda install -c conda-forge libsndfile
+
+```bash
+
+### 2.1 Enhancement
+
+The objective of the enhancement stage is takes a song and optimise it to a listener hearing characteristics
+knowing metadata information about the car noise scenario (you won't have access to noise signal), head
+rotation of the listener and the SNR of the enhanced music and the noise at the hearing aid microphones.
+
+In the baseline, we simply attenuate the song according to the average hearing loss and save it in 16-bit PCM WAV format.
+This attenuation prevents some clipping in the hearing aid output signal.
+
+To run the baseline enhancement system first, make sure that `paths.root` in `config.yaml` points to
+where you have installed the Cadenza data foer the task2. This parameter defaults to one level above the recipe
+for the demo data. You can also define your own `path.exp_folder` to store enhanced and evaluated signal 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=/full/path/to/my/cadenza_data
+```
+
+To get a full list of the parameters, run:
+
+```bash
+python enhance.py --help
+```
+
+The folder `enhanced_signals` will appear in the `exp` folder.
+
+### 2.2 Evaluation
+
+The `evaluate.py` module takes the enhanced signals and adds the room impulses and the car noise using
+the expected SNR. It then passes that signal through a fixed hearing aid. The hearing aid output and
+the reference song are used to compute the HAAQI [2] score.
+
+To run the evaluation stage, make sure that `path.root` is set in the `config.yaml` file and then run
+
+```bash
+python evaluate.py
+```
+
+A csv file containing the HAAQI scores for each ear and the average of both will be generated in
+the `path.exp_folder`.
+
+To check the HAAQI code, see [here](../../../../clarity/evaluator/haaqi).
+
+Please note: you will not get identical HAAQI scores for the same signals if the random seed is not defined
+(in the given recipe, the random seed for each signal is set as the last eight digits of the song md5).
+As there are random noises generated within HAAQI, but the differences should be sufficiently small.
+
+**Baseline performance on the validation set will be updated soon.**
+
+## 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. [doi:10.1097/00003446-198608000-00007](https://doi.org/10.1097/00003446-198608000-00007)
+* [2] Kates J M, Arehart K H. "The Hearing-Aid Audio Quality Index (HAAQI)". IEEE/ACM transactions on audio, speech, and language processing, 24(2), 354–365. [doi:10.1109/TASLP.2015.2507858](https://doi.org/10.1109%2FTASLP.2015.2507858)
+* [3] Defferrard, M., Benzi, K., Vandergheynst, P., & Bresson, X. (2016). "FMA: A dataset for music analysis". arXiv preprint arXiv:1612.01840. [doi:10.48550/arXiv.1612.01840](https://doi.org/10.48550/arXiv.1612.01840)
+* [4] Bogdanov, D., Won, M., Tovstogan, P., Porter, A., & Serra, X. (2019). The MTG-Jamendo dataset for automatic music tagging. ICML.
diff --git a/recipes/cad1/task2/baseline/__init__.py b/recipes/cad1/task2/baseline/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/recipes/cad1/task2/baseline/audio_manager.py b/recipes/cad1/task2/baseline/audio_manager.py
new file mode 100644
index 000000000..650a2f86c
--- /dev/null
+++ b/recipes/cad1/task2/baseline/audio_manager.py
@@ -0,0 +1,119 @@
+"""A utility class for managing audio files."""
+
+import logging
+import warnings
+from pathlib import Path
+from typing import Dict, Tuple, Union
+
+import numpy as np
+import pyloudnorm as pyln
+from scipy.io import wavfile
+
+logger = logging.getLogger(__name__)
+
+
+class AudioManager:
+    """A utility class for managing audio files."""
+
+    def __init__(
+        self,
+        sample_rate: int = 44100,
+        output_audio_path: Union[str, Path] = "",
+        soft_clip: bool = False,
+    ):
+        """Initialize the AudioManager instance."""
+        self.audios_to_save: Dict[str, np.adarray] = {}
+        self.sample_rate = sample_rate
+        self.soft_clip = soft_clip
+        self.output_audio_path = Path(output_audio_path)
+        self.output_audio_path.mkdir(exist_ok=True, parents=True)
+        self.level_meter = pyln.Meter(self.sample_rate)
+
+    def add_audios_to_save(self, file_name: str, waveform: np.ndarray) -> None:
+        """Add a waveform to the list of audios to save.
+
+        Args:
+            file_name (str): The name of the track.
+            waveform (np.ndarray): The track to save.
+        """
+        self.audios_to_save[file_name] = waveform.copy()
+
+    def save_audios(self) -> None:
+        """Save the audios to the given path.
+
+        Args:
+            output_audio_path (str): The path to save the audios to.
+        """
+        for file_name, waveform in self.audios_to_save.items():
+            self._save_audio(file_name, waveform)
+
+    def _save_audio(self, file_name: str, waveform: np.ndarray) -> None:
+        """Save the audio to the given path.
+        It always save in Int16 format.
+
+        Args:
+            file_name (str): The name of the track.
+            waveform (np.ndarray): The track to save.
+            output_audio_path (str): The path to save the audio to.
+            sample_rate (int): The sample rate of the audio.
+        """
+        waveform = waveform.T if waveform.shape[0] == 2 else waveform
+
+        n_clipped, waveform = self.clip_audio(waveform)
+        if n_clipped > 0:
+            logger.warning(
+                f"Writing {self.output_audio_path / file_name}: {n_clipped} "
+                "samples clipped"
+            )
+
+        waveform = (32768.0 * waveform).astype(np.int16)
+
+        wavfile.write(
+            self.output_audio_path / f"{file_name}.wav",
+            self.sample_rate,
+            waveform,
+        )
+
+    def clip_audio(
+        self, signal: np.ndarray, min_val: float = -1, max_val: float = 1
+    ) -> Tuple[int, np.ndarray]:
+        """Clip a WAV file to the given range.
+
+        Args:
+            signal (np.ndarray): The WAV file to clip.
+            min_val (float): The minimum value to clip to. Defaults to -1.
+            max_val (float): The maximum value to clip to. Defaults to 1.
+
+        Returns:
+            Tuple[int, np.ndarray]: Number of samples clipped and the clipped signal.
+        """
+        if self.soft_clip:
+            signal = np.tanh(signal)
+        n_clipped = np.sum(np.abs(signal) > 1.0)
+        return int(n_clipped), np.clip(signal, min_val, max_val)
+
+    def get_lufs_level(self, signal: np.ndarray) -> float:
+        """Get the LUFS level of the signal.
+
+        Args:
+            signal (np.ndarray): The signal to get the LUFS level of.
+
+        Returns:
+            float: The LUFS level of the signal.
+        """
+        return self.level_meter.integrated_loudness(signal)
+
+    def scale_to_lufs(self, signal: np.ndarray, target_lufs: float) -> np.ndarray:
+        """Scale the signal to the given LUFS level.
+
+        Args:
+            signal (np.ndarray): The signal to scale.
+            target_lufs (float): The target LUFS level.
+
+        Returns:
+            np.ndarray: The scaled signal.
+        """
+        current_lufs = self.get_lufs_level(signal)
+        with warnings.catch_warnings(record=True):
+            scaled_signal = pyln.normalize.loudness(signal, current_lufs, target_lufs).T
+        return scaled_signal
diff --git a/recipes/cad1/task2/baseline/baseline_utils.py b/recipes/cad1/task2/baseline/baseline_utils.py
new file mode 100644
index 000000000..c5b4e293f
--- /dev/null
+++ b/recipes/cad1/task2/baseline/baseline_utils.py
@@ -0,0 +1,123 @@
+"""Utility functions for the baseline model."""
+# pylint: disable=import-error
+
+import json
+import logging
+import warnings
+from pathlib import Path
+from typing import Optional, Tuple, Union
+
+import librosa
+import numpy as np
+import pandas as pd
+from omegaconf import DictConfig
+
+logger = logging.getLogger(__name__)
+
+
+def read_mp3(
+    file_path: Union[str, Path], sample_rate: Optional[int] = None
+) -> Tuple[np.ndarray, Optional[int]]:
+    """Read a MP3 file and return its signal.
+
+    Args:
+        file_path (str, Path): The path to the mp3 file.
+        sample_rate (int): The sampling frequency of the mp3 file.
+
+    Returns:
+        signal (np.ndarray): The signal of the mp3 file.
+        sample_rate (int): The sampling frequency of the mp3 file.
+    """
+
+    try:
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore")
+            signal, sample_rate = librosa.load(
+                str(file_path),
+                sr=sample_rate,
+                mono=False,
+                res_type="kaiser_best",
+                dtype=np.float32,
+            )
+    except Exception as error:
+        raise ValueError from error
+
+    if signal.ndim == 1:
+        # If mono, duplicate to stereo
+        signal = np.stack([signal, signal], axis=0)
+
+    # Peak Normalization for cases when signal has
+    # absolute values greater than 1
+    if np.max(np.abs(signal)) > 1:
+        signal = signal / np.max(np.abs(signal))
+
+    return signal, sample_rate
+
+
+def load_hrtf(config: DictConfig) -> dict:
+    """Load the HRTF file.
+
+    Args:
+        config (DictConfig): A dictionary-like object containing various configuration
+            parameters for the evaluation. This includes the path to the HRTF files.
+
+    Returns:
+        hrtf_data (dict): A dictionary containing the HRTF data for the dataset.
+
+    """
+    with open(config.path.hrtf_file, "r", encoding="utf-8") as fp:
+        hrtf_data = json.load(fp)
+    return hrtf_data[config.evaluate.split]
+
+
+def load_listeners_and_scenes(config: DictConfig) -> Tuple[dict, dict, dict]:
+    """Load listener and scene data
+
+    Args:
+        config (DictConfig): A dictionary-like object containing various configuration
+            parameters for the evaluation. This includes the path to the scenes file,
+            the path to the listeners train file, and the path to the listeners valid
+            file.
+
+    Returns:
+        Tuple[dict, dict, dict]: A tuple containing the scene data, the listener data
+            and the pair scenes-listeners.
+
+    """
+    # Load listener data
+    with open(config.path.scenes_file, "r", encoding="utf-8") as fp:
+        df_scenes = pd.read_json(fp, orient="index")
+
+    # Load audiograms and scence data for the corresponding split
+    if config.evaluate.split == "train":
+        with open(config.path.listeners_train_file, "r", encoding="utf-8") as fp:
+            listener_audiograms = json.load(fp)
+        scenes = df_scenes[df_scenes["split"] == "train"].to_dict("index")
+
+    elif config.evaluate.split == "valid":
+        with open(config.path.listeners_valid_file, "r", encoding="utf-8") as fp:
+            listener_audiograms = json.load(fp)
+        scenes = df_scenes[df_scenes["split"] == "valid"].to_dict("index")
+
+    with open(config.path.scenes_listeners_file, "r", encoding="utf-8") as fp:
+        scenes_listeners = json.load(fp)
+        scenes_listeners = {
+            k: v for k, v in scenes_listeners.items() if k in scenes.keys()
+        }
+
+    return scenes, listener_audiograms, scenes_listeners
+
+
+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
diff --git a/recipes/cad1/task2/baseline/car_scene_acoustics.py b/recipes/cad1/task2/baseline/car_scene_acoustics.py
new file mode 100644
index 000000000..fd6f6a3e3
--- /dev/null
+++ b/recipes/cad1/task2/baseline/car_scene_acoustics.py
@@ -0,0 +1,401 @@
+"""A class for the car acoustic environment."""
+# pylint: disable=import-error
+# pylint: disable=too-many-instance-attributes
+
+import logging
+import warnings
+from pathlib import Path
+from typing import Optional
+
+import numpy as np
+import pyloudnorm as pyln
+from omegaconf import DictConfig
+from scipy.io import wavfile
+from scipy.signal import lfilter
+
+from clarity.enhancer.compressor import Compressor
+from clarity.enhancer.nalr import NALR
+from clarity.utils.car_noise_simulator.carnoise_signal_generator import (
+    CarNoiseSignalGenerator,
+)
+from recipes.cad1.task2.baseline.audio_manager import AudioManager
+
+logger = logging.getLogger(__name__)
+
+
+class CarSceneAcoustics:
+    """
+    A class for the car acoustic environment.
+
+    Constants:
+        ANECHOIC_HRTF_FOR_NOISE (dict): A dictionary containing the names of the
+            anechoic BRIRs for the following directions:
+                0 degrees: front
+                    - 000_left: The left channel of the BRIR for 0 degrees.
+                    - 000_right: The right channel of the BRIR for 0 degrees.
+                -90 degrees: left
+                    - m90_left: The left channel of the BRIR for -90 degrees.
+                    - m90_right: The right channel of the BRIR for -90 degrees.
+                90 degrees: right
+                    - p90_left: The left channel of the BRIR for 90 degrees.
+                    - p90_right: The right channel of the BRIR for 90 degrees.
+    """
+
+    ANECHOIC_HRTF_FOR_NOISE = {
+        "000_left": "HR36_E02_CH1_Left.wav",
+        "000_right": "HR36_E02_CH1_Right.wav",
+        "m90_left": "HR0_E02_CH1_Left.wav",
+        "m90_right": "HR0_E02_CH1_Right.wav",
+        "p90_left": "HR72_E02_CH1_Left.wav",
+        "p90_right": "HR72_E02_CH1_Right.wav",
+    }
+
+    def __init__(
+        self,
+        track_duration: int,
+        sample_rate: int,
+        hrtf_dir: str,
+        config_nalr: dict,
+        config_compressor: dict,
+        extend_noise: float = 0.2,
+    ):
+        """
+        Initializes the CarSceneAcoustics object.
+
+        Args:
+        track_duration (int): The duration of the audio track in seconds..
+        sample_rate (int): The sample rate of the audio in Hz.
+        hrtf_dir (str): The path to the directory containing the BRIR files.
+        config_nalr (dict): The configuration for the NALR enhancer.
+        config_compressor (dict): The configuration for the compressor.
+        extend_noise (float): The factor by which to extend the duration of the car
+            noise generated by the CarNoiseGenerator. Defaults to 0.2.
+            This is to prevent the car noise from being shorter than the audio track.
+        """
+
+        self.track_duration = track_duration * (1 + extend_noise)
+        self.sample_rate = sample_rate
+        self.hrtf_dir = hrtf_dir
+
+        self.preload_anechoic_hrtf(self.hrtf_dir)
+        self.enhancer = NALR(**config_nalr)
+        self.compressor = Compressor(**config_compressor)
+
+        self.carnoise = CarNoiseSignalGenerator(
+            duration_secs=self.track_duration,
+            sample_rate=self.sample_rate,
+        )
+        self.loudness_meter = pyln.Meter(self.sample_rate)
+
+    def preload_anechoic_hrtf(self, hrtf_dir: str) -> None:
+        """
+        Loads the Anechoic BRIRs from the eBrird database for the given directions.
+        Using the following directions:
+            0 degrees: front
+            -90 degrees: left
+            90 degrees: right
+
+        Args:
+            brird_dir (str): The path to the directory containing the BRIR files.
+        """
+        self.hrir_for_noise = {}
+        anechoic_hrtf_dir = Path(hrtf_dir) / "Anechoic" / "audio"
+
+        for key, item in self.ANECHOIC_HRTF_FOR_NOISE.items():
+            self.hrir_for_noise[key] = wavfile.read(anechoic_hrtf_dir / item)[1]
+
+    def apply_hearing_aid(
+        self, signal: np.ndarray, audiogram: np.ndarray, center_frequencies: np.ndarray
+    ) -> np.ndarray:
+        """
+        Applies the hearing aid:
+        It consists in NALR prescription and Compressor
+
+        Args:
+            signal (np.ndarray): The audio signal to be enhanced.
+            audiogram (np.ndarray): An audiogram used to configure the NALR object.
+            center_frequencies (np.ndarray): An array of center frequencies
+                used to configure the NALR object.
+
+        Returns:
+            np.ndarray: The enhanced audio signal.
+        """
+        nalr_fir, _ = self.enhancer.build(audiogram, center_frequencies)
+        signal = self.enhancer.apply(nalr_fir, signal)
+        signal, _, _ = self.compressor.process(signal)
+        return signal
+
+    def add_anechoic_hrtf_to_noise(self, noise_signal: np.ndarray) -> np.ndarray:
+        """
+        Adds the Anechoic HRTF to the noise signal.
+        Args:
+            noise_signal: A numpy array representing the different components
+                of the car noise signal.
+
+        Returns:
+            np.ndarray: The noise signal with the Anechoic HRTF applied.
+
+        """
+        # Apply Anechoic HRTF to the noise signal
+        # Engine first
+        out_left = lfilter(self.hrir_for_noise["000_left"], 1, noise_signal[0, :])
+        our_right = lfilter(self.hrir_for_noise["000_right"], 1, noise_signal[0, :])
+
+        # noise processing hardwired for 2 noises
+        out_left += lfilter(self.hrir_for_noise["m90_left"], 1, noise_signal[1, :])
+        our_right += lfilter(self.hrir_for_noise["m90_right"], 1, noise_signal[1, :])
+
+        # swap HRIR so this noise is on the other side
+        out_left += lfilter(self.hrir_for_noise["p90_left"], 1, noise_signal[2, :])
+        our_right += lfilter(self.hrir_for_noise["p90_right"], 1, noise_signal[2, :])
+
+        return np.stack([out_left, our_right], axis=0)
+
+    def get_car_noise(
+        self,
+        car_noise_params: dict,
+    ) -> np.ndarray:
+        """
+        Generates car noise.
+
+        Args:
+            car_noise_params (dict): Car Noise Parameters as generated by
+                Class CarNoiseParameterGenerator
+
+        Returns:
+            numpy.ndarray: A numpy array representing the different components
+                of the car noise signal
+
+        """
+        return self.carnoise.generate_car_noise(
+            noise_parameters=car_noise_params,
+            number_noise_sources=2,
+            commonness_factor=0,
+        )
+
+    def add_hrtf_to_stereo_signal(
+        self, signal: np.ndarray, hrir: dict, hrtf_type: str
+    ) -> np.ndarray:
+        """Add a head rotation transfer function using binaural room impulse
+            response (BRIR) from eBrird.
+
+        Args:
+            signal (np.ndarray): a numpy array of shape (2, n_samples) containing the
+                stereo audio signal.
+            hrir: a dictionary containing the HRIR (head-related impulse response)
+                filenames.
+            hrtf_type: the type of HRTF to use. Can be either "Anechoic" or "Car".
+
+        Returns:
+            A numpy array of shape (2, n_samples) containing the stereo audio signal
+                with the BRIR added.
+
+        """
+        car_hrtf_path = Path(self.hrtf_dir) / hrtf_type / "audio"
+
+        # HRTF from left speaker
+        hr_ls03_ch1_left = wavfile.read(
+            car_hrtf_path / f"{hrir['left_speaker']['left_side']}.wav"
+        )[1]
+        hr_ls03_ch1_right = wavfile.read(
+            car_hrtf_path / f"{hrir['left_speaker']['right_side']}.wav"
+        )[1]
+
+        # HRTF from right speaker
+        hr_ls04_ch1_left = wavfile.read(
+            car_hrtf_path / f"{hrir['right_speaker']['left_side']}.wav"
+        )[1]
+        hr_ls04_ch1_right = wavfile.read(
+            car_hrtf_path / f"{hrir['right_speaker']['right_side']}.wav"
+        )[1]
+
+        # add the BRIRs to the signal
+        # Left Speaker (LS03)
+        out_left = lfilter(hr_ls03_ch1_left, 1, signal[0, :])
+        out_right = lfilter(hr_ls03_ch1_right, 1, signal[0, :])
+        # Right Speaker (LS04)
+        out_left += lfilter(hr_ls04_ch1_left, 1, signal[1, :])
+        out_right += lfilter(hr_ls04_ch1_right, 1, signal[1, :])
+
+        return np.stack([out_left, out_right], axis=0)
+
+    def scale_signal_to_snr(
+        self,
+        signal: np.ndarray,
+        reference_signal: np.ndarray = None,
+        snr: Optional[float] = 0,
+    ) -> np.ndarray:
+        """
+        Scales the target signal to the desired SNR.
+        We transpose channel because pylodnorm operates
+        on arrays with shape [n_samples, n_channels].
+
+        Args:
+            target_signal (np.ndarray): The target signal to scale.
+            reference_signal (np.ndarray): The reference signal.
+                If None, the reference signal is set to 0 LUFS.
+            snr (float): The desired SNR gain in dB.
+                If None, the target signal is scaled to the reference signal.
+
+        Returns:
+            np.ndarray: The scaled target signal.
+        """
+        # Ensure channels are in the correct dimension
+        if reference_signal.shape[0] < reference_signal.shape[1]:
+            reference_signal = reference_signal.T
+        if signal.shape[0] < signal.shape[1]:
+            signal = signal.T
+
+        ref_signal_lufs = (
+            0.0
+            if reference_signal is None
+            else self.loudness_meter.integrated_loudness(reference_signal)
+        )
+
+        signal_lufs = self.loudness_meter.integrated_loudness(signal)
+        target_lufs = ref_signal_lufs - snr
+
+        with warnings.catch_warnings(record=True):
+            normalised_signal = pyln.normalize.loudness(
+                signal, signal_lufs, target_lufs
+            )
+
+        # return to original shape
+        return normalised_signal.T
+
+    def equalise_level(
+        self, signal: np.ndarray, reference_signal: np.ndarray, max_level: float = 20
+    ) -> np.ndarray:
+        """
+        Equalises the level of the target signal to the reference signal.
+
+        Args:
+            signal (np.ndarray): The target signal to equalise.
+            reference_signal (np.ndarray): The reference signal.
+            max_level (float): The maximum level of the target signal.
+                This to prevent clipping.
+
+        Returns:
+            np.ndarray: The equalised target signal.
+        """
+        signal_lufs = self.loudness_meter.integrated_loudness(signal.T)
+        target_lufs = self.loudness_meter.integrated_loudness(reference_signal.T)
+        with warnings.catch_warnings(record=True):
+            return pyln.normalize.loudness(
+                signal, signal_lufs, min(target_lufs, max_level)
+            )
+
+    @staticmethod
+    def add_two_signals(signal1: np.ndarray, signal2: np.ndarray) -> np.ndarray:
+        """
+        Adds two signals together.
+
+        Args:
+            signal1 (np.ndarray): The first signal.
+            signal2 (np.ndarray): The second signal.
+
+        Returns:
+            np.ndarray: The sum of the two signals.
+        """
+        min_length = min(signal1.shape[1], signal2.shape[1])
+        return signal1[:, :min_length] + signal2[:, :min_length]
+
+    def apply_car_acoustics_to_signal(
+        self,
+        enh_signal: np.ndarray,
+        scene: dict,
+        listener: dict,
+        hrtf: dict,
+        audio_manager: AudioManager,
+        config: DictConfig,
+    ) -> np.ndarray:
+        """
+        Applies the car acoustics to the enhanced signal.
+
+        Args:
+            enh_signal (np.ndarray): The enhanced signal to apply the car acoustics to.
+            scene (dict): The scene dictionary with the acoustics parameters.
+            listener (dict): The listener dictionary with the audiograms.
+            hrtf (dict): A dictionary containing the head-related transfer functions
+                (HRTFs) for the listener being evaluated. This includes the left and
+                right HRTFs for the car and the anechoic room.
+            audio_manager (AudioManager): The audio manager object.
+            config (DictConfig): The config object.
+
+        Returns:
+            np.ndarray: The enhanced signal with the car acoustics applied.
+            np.ndarray: The reference signal normalised to enhanced level.
+        """
+
+        # 1. Generates car noise and adds anechoic HRTFs to the car noise
+        # car_noise_anechoic = car_noise + anechoic HRTF
+
+        car_noise = self.get_car_noise(scene["car_noise_parameters"])
+        car_noise_anechoic = self.add_anechoic_hrtf_to_noise(car_noise)
+
+        if config.evaluate.save_intermediate_wavs:
+            audio_manager.add_audios_to_save("car_noise_anechoic", car_noise_anechoic)
+
+        # 2. Add HRTFs to enhanced signal
+        # processed_signal = enh_signal + car HRTF
+
+        processed_signal = self.add_hrtf_to_stereo_signal(
+            enh_signal, hrtf["car"], "Car"
+        )
+
+        if config.evaluate.save_intermediate_wavs:
+            audio_manager.add_audios_to_save("enh_signal_hrtf", processed_signal)
+
+        # 3. Scale noise to target SNR
+        # car_noise_anechoic = car_noise_anechoic * scale_factor
+
+        car_noise_anechoic = self.scale_signal_to_snr(
+            signal=car_noise_anechoic,
+            reference_signal=processed_signal,
+            snr=float(scene["snr"]),
+        )
+
+        if config.evaluate.save_intermediate_wavs:
+            audio_manager.add_audios_to_save(
+                "car_noise_anechoic_scaled", car_noise_anechoic
+            )
+
+        # 4. Add the scaled anechoic car noise to the enhanced signal
+        # processed_signal = (enh_signal * car HRTF)
+        #   + (car_noise * Anechoic HRTF) * scale_factor
+        processed_signal = self.add_two_signals(processed_signal, car_noise_anechoic)
+
+        if config.evaluate.save_intermediate_wavs:
+            audio_manager.add_audios_to_save(
+                "enh_signal_hrtf_plus_car_noise_anechoic", processed_signal
+            )
+
+        # 5. Apply Hearing Aid to Left and Right channels and join them
+        processed_signal_left = self.apply_hearing_aid(
+            processed_signal[0, :],
+            np.array(listener["audiogram_levels_l"]),
+            np.array(listener["audiogram_cfs"]),
+        )
+
+        processed_signal_right = self.apply_hearing_aid(
+            processed_signal[1, :],
+            np.array(listener["audiogram_levels_r"]),
+            np.array(listener["audiogram_cfs"]),
+        )
+
+        processed_signal = np.stack(
+            [processed_signal_left, processed_signal_right], axis=0
+        )
+
+        # processed_signal = np.clip(processed_signal, -1.0, 1.0)
+        n_clipped, processed_signal = audio_manager.clip_audio(processed_signal)
+
+        if n_clipped > 0:
+            logger.warning(
+                f"Scene {scene['scene']}: {n_clipped}"
+                " samples clipped in evaluation signal."
+            )
+
+        audio_manager.add_audios_to_save("ha_processed_signal", processed_signal)
+
+        return processed_signal
diff --git a/recipes/cad1/task2/baseline/config.yaml b/recipes/cad1/task2/baseline/config.yaml
new file mode 100644
index 000000000..36a5ceb2f
--- /dev/null
+++ b/recipes/cad1/task2/baseline/config.yaml
@@ -0,0 +1,44 @@
+path:
+  root: ../../cadenza_task2_data_demo/cad1/task2
+  audio_dir: ${path.root}/audio
+  metadata_dir: ${path.root}/metadata
+  music_dir: ${path.audio_dir}/music
+  hrtf_dir: ${path.audio_dir}/eBrird
+  listeners_train_file: ${path.metadata_dir}/listeners.train.json
+  listeners_valid_file: ${path.metadata_dir}/listeners.valid.json
+  scenes_file: ${path.metadata_dir}/scenes.json
+  scenes_listeners_file: ${path.metadata_dir}/scenes_listeners.json
+  hrtf_file: ${path.metadata_dir}/eBrird_BRIR.json
+  exp_folder: ./exp # folder to store enhanced signals and final results
+
+sample_rate: 44100
+
+nalr:
+  nfir: 220
+  fs: ${sample_rate}
+
+compressor:
+  threshold: 0.7
+  attenuation: 0.1
+  attack: 5
+  release: 20
+  rms_buffer_size: 0.064
+
+soft_clip: False
+
+enhance:
+  average_level: -14   # Average level according Spotify's levels
+  min_level: -19
+
+evaluate:
+  set_random_seed: True
+  small_test: False
+  save_intermediate_wavs: False
+  split: valid # train, valid
+  batch_size: 1   # Number of batches
+  batch: 0      # Batch number to evaluate
+
+# hydra config
+hydra:
+  run:
+    dir: ${path.exp_folder}
\ No newline at end of file
diff --git a/recipes/cad1/task2/baseline/enhance.py b/recipes/cad1/task2/baseline/enhance.py
new file mode 100644
index 000000000..9e8139604
--- /dev/null
+++ b/recipes/cad1/task2/baseline/enhance.py
@@ -0,0 +1,152 @@
+""" Run the dummy enhancement. """
+# pylint: disable=too-many-locals
+# pylint: disable=import-error
+
+import logging
+import warnings
+from pathlib import Path
+from typing import Tuple
+
+import hydra
+import numpy as np
+import pyloudnorm as pyln
+from omegaconf import DictConfig
+from scipy.io import wavfile
+from tqdm import tqdm
+
+from recipes.cad1.task2.baseline.baseline_utils import (
+    make_scene_listener_list,
+    read_mp3,
+)
+from recipes.cad1.task2.baseline.evaluate import load_listeners_and_scenes
+
+logger = logging.getLogger(__name__)
+
+
+def compute_average_hearing_loss(listener: dict) -> float:
+    """
+    Compute the average hearing loss of a listener.
+
+    Args:
+        listener (dict): The audiogram of the listener.
+
+    Returns:
+        average_hearing_loss (float): The average hearing loss of the listener.
+
+    """
+    cfs = [500, 1000, 2000, 4000]
+    left_loss = [
+        listener["audiogram_levels_l"][i]
+        for i in range(len(listener["audiogram_cfs"]))
+        if listener["audiogram_cfs"][i] in cfs
+    ]
+    right_loss = [
+        listener["audiogram_levels_r"][i]
+        for i in range(len(listener["audiogram_cfs"]))
+        if listener["audiogram_cfs"][i] in cfs
+    ]
+    return max(float(np.mean(left_loss)), float(np.mean(right_loss)))
+
+
+def enhance_song(
+    waveform: np.ndarray,
+    listener_audiograms: dict,
+    config: DictConfig,
+) -> Tuple[np.ndarray, np.ndarray]:
+    """
+    Enhance a single song for a listener.
+
+    Baseline enhancement returns the signal with a loudness
+    of -14 LUFS if the average hearing loss is below 50 dB HL,
+    and -11 LUFS otherwise.
+
+    Args:
+        waveform (np.ndarray): The waveform of the song.
+        listener_audiograms (dict): The audiograms of the listener.
+        config (dict): Dictionary of configuration options for enhancing music.
+
+    Returns:
+        out_left (np.ndarray): The enhanced left channel.
+        out_right (np.ndarray): The enhanced right channel.
+
+    """
+    meter = pyln.Meter(config.sample_rate)
+    original_loudness = meter.integrated_loudness(waveform.T)
+
+    average_hearing_loss = compute_average_hearing_loss(listener_audiograms)
+
+    extra_reduction = (
+        (average_hearing_loss - 50) / 5 if (average_hearing_loss - 50) / 5 > 0 else 0
+    )
+    target_level = (
+        config.enhance.min_level - extra_reduction
+        if average_hearing_loss >= 50
+        else config.enhance.average_level
+    )
+
+    with warnings.catch_warnings(record=True):
+        if original_loudness > target_level:
+            waveform = pyln.normalize.loudness(
+                waveform.T, original_loudness, target_level
+            ).T
+
+    out_left = waveform[0, :]
+    out_right = waveform[1, :]
+
+    return out_left, out_right
+
+
+@hydra.main(config_path="", config_name="config")
+def enhance(config: DictConfig) -> None:
+    """
+    Run the music enhancement.
+    The baseline system is a dummy processor that returns the input signal.
+
+    Args:
+        config (dict): Dictionary of configuration options for enhancing music.
+    """
+    enhanced_folder = Path("enhanced_signals") / config.evaluate.split
+
+    # Load scenes and listeners depending on config.evaluate.split
+    scenes, listener_audiograms, scenes_listeners = load_listeners_and_scenes(config)
+    scene_listener_pairs = make_scene_listener_list(
+        scenes_listeners, config.evaluate.small_test
+    )
+    scene_listener_pairs = scene_listener_pairs[
+        config.evaluate.batch :: config.evaluate.batch_size
+    ]
+
+    for scene_id, listener_id in tqdm(scene_listener_pairs):
+        current_scene = scenes[scene_id]
+        listener = listener_audiograms[listener_id]
+
+        song_path = Path(config.path.music_dir) / f"{current_scene['song_path']}"
+
+        # Read song
+        song_waveform, _ = read_mp3(song_path, config.sample_rate)
+        out_l, out_r = enhance_song(
+            waveform=song_waveform, listener_audiograms=listener, config=config
+        )
+
+        enhanced = np.stack([out_l, out_r], axis=1)
+        filename = f"{scene_id}_{listener['name']}_{current_scene['song']}.wav"
+
+        enhanced_folder_listener = enhanced_folder / f"{listener['name']}"
+        enhanced_folder_listener.mkdir(parents=True, exist_ok=True)
+
+        # Clip and save
+        if config.soft_clip:
+            enhanced = np.tanh(enhanced)
+        n_clipped = np.sum(np.abs(enhanced) > 1.0)
+        if n_clipped > 0:
+            logger.warning(f"Writing {filename}: {n_clipped} samples clipped")
+        np.clip(enhanced, -1.0, 1.0, out=enhanced)
+        signal_16 = (32768.0 * enhanced).astype(np.int16)
+        wavfile.write(
+            enhanced_folder_listener / filename, config.sample_rate, signal_16
+        )
+
+
+# pylint: disable = no-value-for-parameter
+if __name__ == "__main__":
+    enhance()
diff --git a/recipes/cad1/task2/baseline/evaluate.py b/recipes/cad1/task2/baseline/evaluate.py
new file mode 100644
index 000000000..bca2f3c55
--- /dev/null
+++ b/recipes/cad1/task2/baseline/evaluate.py
@@ -0,0 +1,298 @@
+"""Evaluate the enhanced signals using the HAAQI metric."""
+# pylint: disable=too-many-locals
+# pylint: disable=import-error
+
+import csv
+import hashlib
+import logging
+from pathlib import Path
+from typing import Tuple
+
+import hydra
+import numpy as np
+from omegaconf import DictConfig
+from scipy.io import wavfile
+from tqdm import tqdm
+
+from clarity.evaluator.haaqi import compute_haaqi
+from recipes.cad1.task2.baseline.audio_manager import AudioManager
+from recipes.cad1.task2.baseline.baseline_utils import (
+    load_hrtf,
+    load_listeners_and_scenes,
+    make_scene_listener_list,
+    read_mp3,
+)
+from recipes.cad1.task2.baseline.car_scene_acoustics import CarSceneAcoustics
+
+logger = logging.getLogger(__name__)
+
+
+class ResultsFile:
+    """A utility class for writing results to a CSV file.
+
+    Attributes:
+        file_name (str): The name of the file to write results to.
+    """
+
+    def __init__(self, file_name):
+        """Initialize the ResultsFile instance.
+
+        Args:
+            file_name (str): The name of the file to write results to.
+        """
+        self.file_name = file_name
+
+    def write_header(self):
+        """Write the header row to the CSV file."""
+        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(
+                [
+                    "song",
+                    "listener",
+                    "score",
+                    "haaqi_left",
+                    "haaqi_right",
+                ]
+            )
+
+    def add_result(
+        self,
+        scene: str,
+        song: str,
+        listener: str,
+        score: float,
+        haaqi_left: float,
+        haaqi_right: float,
+    ):
+        """Add a result to the CSV file.
+
+        Args:
+            scene (str): The name of the scene that the result is for.
+            song (str): The name of the song that the result is for.
+            listener (str): The name of the listener who submitted the result.
+            score (float): The combined score for the result.
+            haaqi_left (float): The HAAQI score for the left channel.
+            haaqi_right (float): The HAAQI score for the right channel.
+        """
+
+        logger.info(f"The combined score for scene {scene}: {score:.4f}")
+
+        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,
+                    song,
+                    listener,
+                    str(score),
+                    str(haaqi_left),
+                    str(haaqi_right),
+                ]
+            )
+
+
+def set_scene_seed(scene: str):
+    """Set a seed that is unique for the given song
+    based on the last 8 characters of the 'md5'
+    `.hexdigest` of the scene itself.
+    """
+    scene_encoded = hashlib.md5(scene.encode("utf-8")).hexdigest()
+    scene_md5 = int(scene_encoded, 16) % (10**8)
+    np.random.seed(scene_md5)
+
+
+# pylint: disable=too-many-arguments
+def evaluate_scene(
+    ref_signal: np.ndarray,
+    enh_signal: np.ndarray,
+    sample_rate: int,
+    scene_id: str,
+    current_scene: dict,
+    listener_audiogram: dict,
+    car_scene_acoustic: CarSceneAcoustics,
+    hrtf: dict,
+    config: DictConfig,
+) -> Tuple[float, float]:
+    """Evaluate a single scene and return HAAQI scores for left and right ears
+
+    Args:
+        ref_signal (np.ndarray): A numpy array of shape (2, n_samples)
+            containing the reference signal.
+        enh_signal (np.ndarray): A numpy array of shape (2, n_samples)
+            containing the enhanced signal.
+        sample_rate (int): The sampling frequency of the reference and enhanced signals.
+        scene_id (str): A string identifier for the scene being evaluated.
+        current_scene (dict): A dictionary containing information about the scene being
+            evaluated, including the song ID, the listener ID, the car noise type, and
+            the split.
+        listener_audiogram (dict): A dictionary containing the listener's audiogram
+            data, including the center frequencies (cfs) and the hearing levels for both
+            ears (audiogram_levels_l and audiogram_levels_r).
+        car_scene_acoustic (CarSceneAcoustics): An instance of the CarSceneAcoustics
+            class, which is used to generate car noise and add binaural room impulse
+            responses (BRIRs) to the enhanced signal.
+        hrtf (dict): A dictionary containing the head-related transfer functions (HRTFs)
+            for the listener being evaluated. This includes the left and right HRTFs for
+            the car and the anechoic room.
+        config (DictConfig): A dictionary-like object containing various configuration
+            parameters for the evaluation. This includes the path to the enhanced signal
+            folder,the path to the music directory, and a flag indicating whether to set
+            a random seed.
+
+    Returns:
+        Tuple[float, float]: A tuple containing HAAQI scores for left and right ears.
+
+    """
+    audio_manager = AudioManager(
+        output_audio_path=Path("evaluation_signals")
+        / f"{listener_audiogram['name']} / {current_scene['song']}",
+        sample_rate=sample_rate,
+        soft_clip=config.soft_clip,
+    )
+
+    if config.evaluate.set_random_seed:
+        set_scene_seed(scene_id)
+
+    # Applies the Car Acoustics to the enhanced signal, i.e., the speakers output
+
+    processed_signal = car_scene_acoustic.apply_car_acoustics_to_signal(
+        enh_signal,
+        current_scene,
+        listener_audiogram,
+        hrtf,
+        audio_manager,
+        config,
+    )
+
+    # Normalise reference signal level to ha_processed_signal level
+    # ref_signal = ref_signal * scale_factor
+    # Following Spotify standard, Max level is -11 LUFS to avoid clipping
+    # https://artists.spotify.com/en/help/article/loudness-normalization
+    ref_signal = car_scene_acoustic.add_hrtf_to_stereo_signal(
+        ref_signal, hrtf["anechoic"], "Anechoic"
+    )
+    if config.evaluate.save_intermediate_wavs:
+        audio_manager.add_audios_to_save("ref_signal_anechoic", ref_signal)
+
+    ref_signal = car_scene_acoustic.equalise_level(
+        signal=ref_signal, reference_signal=processed_signal, max_level=-14
+    )
+    audio_manager.add_audios_to_save("ref_signal_for_eval", ref_signal)
+
+    audio_manager.save_audios()
+
+    # Compute HAAQI scores
+    aq_score_l = compute_haaqi(
+        processed_signal[0, :],
+        ref_signal[0, :],
+        np.array(listener_audiogram["audiogram_levels_l"]),
+        np.array(listener_audiogram["audiogram_cfs"]),
+        sample_rate,
+    )
+    aq_score_r = compute_haaqi(
+        processed_signal[1, :],
+        ref_signal[1, :],
+        np.array(listener_audiogram["audiogram_levels_r"]),
+        np.array(listener_audiogram["audiogram_cfs"]),
+        sample_rate,
+    )
+    return aq_score_l, aq_score_r
+
+
+@hydra.main(config_path="", config_name="config")
+def run_calculate_audio_quality(config: DictConfig) -> None:
+    """Evaluate the enhanced signals using the HAAQI metric."""
+
+    # Load scenes and listeners depending on config.evaluate.split
+    scenes, listener_audiograms, scenes_listeners = load_listeners_and_scenes(config)
+    scene_listener_pairs = make_scene_listener_list(
+        scenes_listeners, config.evaluate.small_test
+    )
+    scene_listener_pairs = scene_listener_pairs[
+        config.evaluate.batch :: config.evaluate.batch_size
+    ]
+
+    hrtfs = load_hrtf(config)
+
+    enhanced_folder = Path("enhanced_signals")
+    logger.info(f"Evaluating from {enhanced_folder} directory")
+
+    results_file = ResultsFile(
+        f"scores_{config.evaluate.batch}-{config.evaluate.batch_size}.csv"
+    )
+    results_file.write_header()
+
+    # Initialize acoustic scene model
+    car_scene_acoustic = CarSceneAcoustics(
+        track_duration=30,
+        sample_rate=config.sample_rate,
+        hrtf_dir=config.path.hrtf_dir,
+        config_nalr=config.nalr,
+        config_compressor=config.compressor,
+        extend_noise=0.2,
+    )
+
+    # Iterate over scenes
+    for scene_id, listener_id in tqdm(scene_listener_pairs):
+        current_scene = scenes[scene_id]
+
+        # Retrieve audiograms
+        listener = listener_audiograms[listener_id]
+
+        # Retrieve HRTF according to the listener's head orientation
+        hrtf_scene = hrtfs[str(current_scene["hr"])]
+
+        # Load reference signal
+        reference_song_path = (
+            Path(config.path.music_dir) / f"{current_scene['song_path']}"
+        )
+        # Read MP3 reference signal using librosa
+        reference_signal, _ = read_mp3(
+            reference_song_path.as_posix(), sample_rate=config.sample_rate
+        )
+
+        # Load enhanced signal
+        enhanced_folder = Path("enhanced_signals") / config.evaluate.split
+        enhanced_song_id = f"{scene_id}_{listener['name']}_{current_scene['song']}"
+        enhanced_song_path = (
+            enhanced_folder / f"{listener['name']}" / f"{enhanced_song_id}.wav"
+        )
+
+        # Read WAV enhanced signal using scipy.io.wavfile
+        enhanced_sample_rate, enhanced_signal = wavfile.read(enhanced_song_path)
+        enhanced_signal = enhanced_signal / 32768.0
+        assert enhanced_sample_rate == config.sample_rate
+
+        # Evaluate scene
+        aq_score_l, aq_score_r = evaluate_scene(
+            reference_signal,
+            enhanced_signal.T,
+            config.sample_rate,
+            scene_id,
+            current_scene,
+            listener,
+            car_scene_acoustic,
+            hrtf_scene,
+            config,
+        )
+
+        # Compute combined score and save
+        score = np.mean([aq_score_r, aq_score_l])
+        results_file.add_result(
+            scene_id,
+            current_scene["song"],
+            listener_id,
+            score=float(score),
+            haaqi_left=aq_score_l,
+            haaqi_right=aq_score_r,
+        )
+
+
+# pylint: disable=no-value-for-parameter
+if __name__ == "__main__":
+    run_calculate_audio_quality()
diff --git a/recipes/cad1/task2/baseline/merge_batches_results.py b/recipes/cad1/task2/baseline/merge_batches_results.py
new file mode 100644
index 000000000..d5803f7ef
--- /dev/null
+++ b/recipes/cad1/task2/baseline/merge_batches_results.py
@@ -0,0 +1,25 @@
+"""Join batches scores into a single file."""
+
+import hydra
+import pandas as pd
+from omegaconf import DictConfig
+
+
+@hydra.main(config_path="", config_name="config")
+def join_batches(config: DictConfig) -> None:
+    """
+    Join batches scores into a single file.
+
+    """
+    batches_results = []
+    for batch in range(config.evaluate.batch_size):
+        batches_results.append(
+            pd.read_csv(f"scores_{batch}-{config.evaluate.batch_size}.csv")
+        )
+    df_res = pd.concat(batches_results, ignore_index=True)
+    df_res.to_csv("scores.csv", index=False)
+
+
+# pylint: disable=no-value-for-parameter
+if __name__ == "__main__":
+    join_batches()
diff --git a/recipes/cad1/task2/data_preparation/__init__.py b/recipes/cad1/task2/data_preparation/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/recipes/cad1/task2/data_preparation/build_scene_metadata.py b/recipes/cad1/task2/data_preparation/build_scene_metadata.py
new file mode 100644
index 000000000..3e7db24cc
--- /dev/null
+++ b/recipes/cad1/task2/data_preparation/build_scene_metadata.py
@@ -0,0 +1,184 @@
+"""Module to Generate the metadata for the scenes in the CAD-1 Task-2 challenge."""
+# pylint: disable=import-error
+
+import json
+import logging
+from typing import Any, Dict
+
+import hydra
+import numpy as np
+from omegaconf import DictConfig
+
+from clarity.utils.car_noise_simulator.carnoise_parameters_generator import (
+    CarNoiseParametersGenerator,
+)
+
+logger = logging.getLogger(__name__)
+
+
+def set_seed(seed: int) -> None:
+    """Set the seed for the random number generator."""
+    np.random.seed(seed)
+
+
+def get_random_head_rotation(input_dict: dict) -> Any:
+    """
+    Selects a random head rotation degree.
+
+    Args:
+        input_dict (dict): A dictionary where keys are bird IDs and values are bird
+            data.
+
+    Returns:
+        A random item from the input dictionary.
+    """
+    return float(np.random.choice(list(input_dict.keys()), size=1, replace=False)[0])
+
+
+def get_random_car_params(min_speed: int = 50, max_speed: int = 120) -> Dict:
+    """
+    Returns a dictionary with the parameters for a car noise.
+    These parameters are generated randomly, based on the car speed.
+    The parameters are to be used by the CarNoiseSignalGeneration class
+
+    Parameters:
+        - min_speed (int): The minimum speed that can be returned (default 50).
+        - max_speed (int): The maximum speed that can be returned (default 120).
+
+    Returns:
+        A dictionary containing parameters needed by CarNoiseSignalGeneration class.
+    """
+    speed = np.random.randint(min_speed, max_speed)
+    car_params = CarNoiseParametersGenerator().gen_parameters(speed)
+    return car_params
+
+
+def read_json(path_file) -> Dict:
+    """Function the read a json file and return the data as a dictionary
+    or only the keys if ```return_keys``` is True.
+
+    Args:
+        path_file (str): Path to the json file.
+    """
+
+    with open(path_file, "r", encoding="utf-8") as file:
+        json_data = json.load(file)
+    return json_data
+
+
+def get_random_snr(min_snr, max_snr, round_to=4) -> float:
+    """Function to get a random SNR value.
+
+    Args:
+        min_snr (float): The minimum SNR value.
+        max_snr (float): The maximum SNR value.
+        round_to (int): The number of decimals to round the SNR value to.
+
+    Returns:
+        float: A random SNR value.
+    """
+
+    return float(np.round(np.random.uniform(min_snr, max_snr, 1), round_to))
+
+
+@hydra.main(config_path=".", config_name="config")
+def run(cfg: DictConfig) -> None:
+    """Main function to generate metadata for the scenes in the CAD-1 Task-2 challenge.
+
+    This function relies on a random seed to generate the metadata.
+    The seed is set to 2023 by default and it should always be present.
+    This ensures all participant have access to the same initial scenes.
+    However, participants can augment the number of scenes by adding more seeds
+    in the config file. Note the first seed must always be 2023 and cannot be change.
+
+    Args:
+        cfg (DictConfig): The configuration object.
+    """
+
+    logger.info("Generating metadata")
+
+    assert (
+        cfg.seed[0] == 2023
+    ), f"First training seed must be 2023, {cfg.valid_seed} was provided"
+
+    assert (
+        cfg.valid_seed == 2023
+    ), f"Validation seed should be 2023, {cfg.valid_seed} was provided"
+
+    # read metadata in json format
+    train_songs = read_json(cfg.path.train_music_file)
+    valid_songs = read_json(cfg.path.valid_music_file)
+    train_listeners = read_json(cfg.path.listeners_train_file)
+    valid_listeners = read_json(cfg.path.listeners_valid_file)
+    brir = read_json(cfg.path.brir_file)
+
+    # Start generating scenes for training
+    train_scenes: Dict = {}
+    valid_scenes: Dict = {}
+    scene_listener: Dict[str, list] = {}
+
+    logger.info("... training metadata")
+    scene_id = 100000
+    for seed in cfg.seed:
+        logger.info(f"...... seed {seed}")
+        set_seed(seed)
+        songs_shuffled = np.random.permutation(list(train_songs.keys()))
+        for song in songs_shuffled:
+            train_scenes[f"S{scene_id:06d}"] = {
+                "scene": f"S{scene_id:06d}",
+                "song": song,
+                "song_path": train_songs[song]["path"],
+                "hr": get_random_head_rotation(brir["train"]),
+                "car_noise_parameters": get_random_car_params(
+                    min_speed=50, max_speed=120
+                ),
+                "snr": get_random_snr(0, 15.0),
+                "split": "train",
+            }
+            scene_id += 1
+
+    # Start generating scenes for validation
+    logger.info("... validation metadata with seed 2023")
+
+    seed = cfg.valid_seed
+    set_seed(seed)
+    songs_shuffled = np.random.permutation(list(valid_songs.keys()))
+    scene_id = 500000
+
+    for song in songs_shuffled:
+        valid_scenes[f"S{scene_id:06d}"] = {
+            "scene": f"S{scene_id:06d}",
+            "song": song,
+            "song_path": valid_songs[song]["path"],
+            "hr": get_random_head_rotation(brir["valid"]),
+            "car_noise_parameters": get_random_car_params(min_speed=50, max_speed=120),
+            "snr": get_random_snr(0, 15.0),
+            "split": "valid",
+        }
+        scene_id += 1
+
+    for scene_id in train_scenes:
+        listener = np.random.choice(list(train_listeners.keys()), 1, replace=False)[0]
+        if f"{scene_id}" not in scene_listener:
+            scene_listener[f"{scene_id}"] = []
+        scene_listener[f"{scene_id}"].append(listener)
+
+    for scene_id in valid_scenes:
+        listener = np.random.choice(list(valid_listeners.keys()), 1, replace=False)[0]
+        if f"{scene_id}" not in scene_listener:
+            scene_listener[f"{scene_id}"] = []
+        scene_listener[f"{scene_id}"].append(listener)
+
+    logger.info(f"Saving scenes metadata in {cfg.path.metadata_dir}")
+    with open(cfg.path.scenes_file, "w", encoding="utf-8") as file:
+        json.dump({**train_scenes, **valid_scenes}, file, indent=4)
+
+    with open(cfg.path.scenes_listeners_file, "w", encoding="utf-8") as file:
+        json.dump(scene_listener, file, indent=4)
+
+    logger.info("Done")
+
+
+# pylint: disable=no-value-for-parameter
+if __name__ == "__main__":
+    run()
diff --git a/recipes/cad1/task2/data_preparation/config.yaml b/recipes/cad1/task2/data_preparation/config.yaml
new file mode 100644
index 000000000..b32302149
--- /dev/null
+++ b/recipes/cad1/task2/data_preparation/config.yaml
@@ -0,0 +1,21 @@
+path:
+  root: ./
+  metadata_dir: ${path.root}/metadata
+  train_music_file: ${path.metadata_dir}/music.train.json
+  valid_music_file: ${path.metadata_dir}/music.valid.json
+  listeners_train_file: ${path.metadata_dir}/listeners.train.json
+  listeners_valid_file: ${path.metadata_dir}/listeners.valid.json
+  brir_file: ${path.metadata_dir}/eBrird_BRIR.json
+  scenes_file: ${path.metadata_dir}/scenes.json
+  scenes_listeners_file: ${path.metadata_dir}/scenes_listeners.json
+
+# Every seed value generates 8000 new samples.
+# Seed 2023 must never be removed.
+# If you want to add more seeds, add them to the list.
+# eg., seed: [2023, 2024, 2025]
+seed: [2023]
+valid_seed: 2023
+
+hydra:
+  run:
+    dir: .
diff --git a/recipes/cec1/baseline/enhance.py b/recipes/cec1/baseline/enhance.py
index e49c71d5b..5ae0e339c 100644
--- a/recipes/cec1/baseline/enhance.py
+++ b/recipes/cec1/baseline/enhance.py
@@ -14,8 +14,10 @@
 def enhance(cfg: DictConfig) -> None:
     enhanced_folder = os.path.join(cfg["path"]["exp_folder"], "enhanced_signals")
     os.makedirs(enhanced_folder, exist_ok=True)
-    scenes_listeners = json.load(open(cfg["path"]["scenes_listeners_file"]))
-    listener_audiograms = json.load(open(cfg["path"]["listeners_file"]))
+    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 = GHAHearingAid(**cfg["GHAHearingAid"])
 
@@ -44,5 +46,6 @@ def enhance(cfg: DictConfig) -> None:
             )
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     enhance()
diff --git a/recipes/cec1/baseline/evaluate.py b/recipes/cec1/baseline/evaluate.py
index c058e18d1..2394fe522 100644
--- a/recipes/cec1/baseline/evaluate.py
+++ b/recipes/cec1/baseline/evaluate.py
@@ -41,8 +41,10 @@ def listen(ear, signal, audiogram_l, audiogram_r):
 def run_HL_processing(cfg: DictConfig) -> None:
     output_path = os.path.join(cfg["path"]["exp_folder"], "eval_signals")
     os.makedirs(output_path, exist_ok=True)
-    scenes_listeners = json.load(open(cfg["path"]["scenes_listeners_file"]))
-    listener_audiograms = json.load(open(cfg["path"]["listeners_file"]))
+    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)
     enhanced_folder = cfg["path"]["enhanced_signals"]
 
     # initialize ear
@@ -69,7 +71,7 @@ def run_HL_processing(cfg: DictConfig) -> None:
                 f"{outfile_stem}_HL-mixoutput.wav",
             ]
             # if all signals to write exist, pass
-            if all([os.path.isfile(f) for f in signal_files_to_write]):
+            if all(os.path.isfile(f) for f in signal_files_to_write):
                 continue
 
             signal = read_signal(signal_file)
@@ -102,10 +104,11 @@ def run_HL_processing(cfg: DictConfig) -> None:
                 listen(ear, ddf_signal, left_audiogram, right_audiogram),
                 listen(ear, mixture_signal, left_audiogram, right_audiogram),
             ]
-            for i in range(len(signals_to_write)):
+
+            for signal, signal_file in zip(signals_to_write, signal_files_to_write):
                 write_signal(
-                    signal_files_to_write[i],
-                    signals_to_write[i],
+                    signal_file,
+                    signal,
                     MSBG_FS,
                     floating_point=True,
                 )
@@ -113,7 +116,8 @@ def run_HL_processing(cfg: DictConfig) -> None:
 
 @hydra.main(config_path=".", config_name="config")
 def run_calculate_SI(cfg: DictConfig) -> None:
-    scenes_listeners = json.load(open(cfg["path"]["scenes_listeners_file"]))
+    with open(cfg["path"]["scenes_listeners_file"], "r", encoding="utf-8") as fp:
+        scenes_listeners = json.load(fp)
     proc_folder = os.path.join(cfg["path"]["exp_folder"], "eval_signals")
     sii_file = os.path.join(cfg["path"]["exp_folder"], "sii.csv")
     csv_lines = [["scene", "listener", "sii"]]
@@ -133,7 +137,8 @@ def run_calculate_SI(cfg: DictConfig) -> None:
                 os.path.join(proc_folder, f"{scene}_{listener}_HLddf-output.wav")
             )
 
-            # Calculate channel-specific unit impulse delay due to HL model and audiograms
+            # Calculate channel-specific unit impulse delay due to HL model
+            # and audiograms
             delay = find_delay_impulse(ddf, initial_value=int(MSBG_FS / 2))
             if delay[0] != delay[1]:
                 logging.info(f"Difference in delay of {delay[0] - delay[1]}.")
@@ -164,7 +169,7 @@ def run_calculate_SI(cfg: DictConfig) -> None:
             )
             csv_lines.append([scene, listener, sii])  # type: ignore
 
-    with open(sii_file, "w") as csv_f:
+    with open(sii_file, "w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
@@ -172,6 +177,7 @@ def run_calculate_SI(cfg: DictConfig) -> None:
             csv_writer.writerow(line)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run_HL_processing()
     run_calculate_SI()
diff --git a/recipes/cec1/data_preparation/prepare_cec1_data.py b/recipes/cec1/data_preparation/prepare_cec1_data.py
index cfd495f81..3fe6522bf 100644
--- a/recipes/cec1/data_preparation/prepare_cec1_data.py
+++ b/recipes/cec1/data_preparation/prepare_cec1_data.py
@@ -18,7 +18,7 @@ def prepare_data(root_path, metafile_path, scene_folder, num_channels):
         scene_folder: folder containing generated scenes
         num_channels: number of channels
     """
-    with open(metafile_path, "r") as f:
+    with open(metafile_path, "r", encoding="utf-8") as f:
         scenes = json.load(f)
 
     os.makedirs(scene_folder, exist_ok=True)
@@ -53,5 +53,6 @@ def run(cfg: DictConfig) -> None:
         )
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cec1/e009_sheffield/train.py b/recipes/cec1/e009_sheffield/train.py
index ecc879c13..8734ff4da 100644
--- a/recipes/cec1/e009_sheffield/train.py
+++ b/recipes/cec1/e009_sheffield/train.py
@@ -22,7 +22,14 @@
 
 
 class DenModule(System):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.ear_idx = None
+        self.down_sample = None
+
     def common_step(self, batch, batch_nb, train=True):
+        if self.down_sample is None:
+            raise RuntimeError("Hearing model not loaded")
         proc, ref = batch
         ref = ref[:, self.ear_idx, :]
         if self.config.downsample_factor != 1:
@@ -34,7 +41,24 @@ def common_step(self, batch, batch_nb, train=True):
 
 
 class AmpModule(System):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.hl_ear = None
+        self.nh_ear = None
+        self.down_sample = None
+        self.up_sample = None
+        self.ear_idx = None
+        self.den_model = None
+
     def common_step(self, batch, batch_nb, train=True):
+        if (
+            self.hl_ear is None
+            or self.nh_ear is None
+            or self.down_sample is None
+            or self.up_sample is None
+            or self.den_model is None
+        ):
+            raise RuntimeError("Hearing model not loaded")
         proc, ref = batch
         ref = ref[:, self.ear_idx, :]
         if self.config.downsample_factor != 1:
@@ -107,8 +131,8 @@ def train_den(cfg, ear):
     trainer.fit(den_module)
 
     best_k = {k: v.item() for k, v in checkpoint.best_k_models.items()}
-    with open(os.path.join(exp_dir, "best_k_models.json"), "w") as f:
-        json.dump(best_k, f, indent=0)
+    with open(os.path.join(exp_dir, "best_k_models.json"), "w", encoding="utf-8") as fp:
+        json.dump(best_k, fp, indent=0)
     state_dict = torch.load(checkpoint.best_model_path)
     den_module.load_state_dict(state_dict=state_dict["state_dict"])
     den_module.cpu()
@@ -163,8 +187,8 @@ def train_amp(cfg, ear):
         )
 
     # build normal hearing and hearing loss ears
-    with open(cfg.listener.metafile, "r") as f:
-        listeners_file = json.load(f)
+    with open(cfg.listener.metafile, "r", encoding="utf-8") as fp:
+        listeners_file = json.load(fp)
         audiogram_cfs = listeners_file[cfg.listener.id]["audiogram_cfs"]
         audiogram_lvl_l = listeners_file[cfg.listener.id]["audiogram_levels_l"]
         audiogram_lvl_r = listeners_file[cfg.listener.id]["audiogram_levels_r"]
@@ -199,8 +223,8 @@ def train_amp(cfg, ear):
     trainer.fit(amp_module)
 
     best_k = {k: v.item() for k, v in checkpoint.best_k_models.items()}
-    with open(os.path.join(exp_dir, "best_k_models.json"), "w") as f:
-        json.dump(best_k, f, indent=0)
+    with open(os.path.join(exp_dir, "best_k_models.json"), "w", encoding="utf-8") as fp:
+        json.dump(best_k, fp, indent=0)
     state_dict = torch.load(checkpoint.best_model_path)
     amp_module.load_state_dict(state_dict=state_dict["state_dict"])
     amp_module.cpu()
@@ -219,5 +243,6 @@ def run(cfg: DictConfig) -> None:
     train_amp(cfg, ear="right")
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cec2/baseline/data_generation/build_additional_scenes.py b/recipes/cec2/baseline/data_generation/build_additional_scenes.py
index 74750729f..ba8793560 100644
--- a/recipes/cec2/baseline/data_generation/build_additional_scenes.py
+++ b/recipes/cec2/baseline/data_generation/build_additional_scenes.py
@@ -38,5 +38,6 @@ def run(cfg: DictConfig) -> None:
     instantiate_scenes(cfg)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cec2/baseline/data_generation/render_additional_scenes.py b/recipes/cec2/baseline/data_generation/render_additional_scenes.py
index 2e3f7a249..b409141fa 100644
--- a/recipes/cec2/baseline/data_generation/render_additional_scenes.py
+++ b/recipes/cec2/baseline/data_generation/render_additional_scenes.py
@@ -12,8 +12,9 @@
 def render_scenes(cfg):
     for dataset in cfg.scene_renderer:
         logger.info(f"Beginning scene generation for {dataset} set...")
-        with open(cfg.scene_renderer[dataset].metadata.scene_definitions, "r") as f:
-            scenes = json.load(f)
+        file_path = cfg.scene_renderer[dataset].metadata.scene_definitions
+        with open(file_path, "r", encoding="utf-8") as fp:
+            scenes = json.load(fp)
 
         starting_scene = (
             cfg.scene_renderer[dataset].chunk_size * cfg.render_starting_chunk
@@ -37,5 +38,6 @@ def run(cfg: DictConfig) -> None:
     render_scenes(cfg)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cec2/baseline/enhance.py b/recipes/cec2/baseline/enhance.py
index 3713b9081..36da45bfc 100644
--- a/recipes/cec2/baseline/enhance.py
+++ b/recipes/cec2/baseline/enhance.py
@@ -18,8 +18,10 @@
 def enhance(cfg: DictConfig) -> None:
     enhanced_folder = os.path.join(cfg.path.exp_folder, "enhanced_signals")
     os.makedirs(enhanced_folder, exist_ok=True)
-    scenes_listeners = json.load(open(cfg.path.scenes_listeners_file))
-    listener_audiograms = json.load(open(cfg.path.listeners_file))
+    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)
@@ -61,5 +63,6 @@ def enhance(cfg: DictConfig) -> None:
             wavfile.write(os.path.join(enhanced_folder, filename), fs, signal_16)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     enhance()
diff --git a/recipes/cec2/baseline/evaluate.py b/recipes/cec2/baseline/evaluate.py
index cd9cc1423..e22a9df7b 100644
--- a/recipes/cec2/baseline/evaluate.py
+++ b/recipes/cec2/baseline/evaluate.py
@@ -17,7 +17,7 @@
 
 def read_csv_scores(file):
     score_dict = {}
-    with open(file, "r") as f:
+    with open(file, "r", encoding="utf-8") as f:
         reader = csv.reader(f)
         _ = next(reader)
         for row in reader:
@@ -27,8 +27,10 @@ def read_csv_scores(file):
 
 @hydra.main(config_path=".", config_name="config")
 def run_calculate_SI(cfg: DictConfig) -> None:
-    scenes_listeners = json.load(open(cfg.path.scenes_listeners_file))
-    listener_audiograms = json.load(open(cfg.path.listeners_file))
+    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)
     os.makedirs(cfg.path.exp_folder, exist_ok=True)
 
     enhanced_folder = os.path.join(cfg.path.exp_folder, "enhanced_signals")
@@ -40,9 +42,9 @@ def run_calculate_SI(cfg: DictConfig) -> None:
             score_dict = read_csv_scores(unproc_si_file)
             ave_score = np.mean(list(score_dict.values()))
             logger.info(
-                "si_unproc.csv exists, and the average HASPI scores for unprocessed scenes is {:.4f}".format(
-                    ave_score
-                )
+                "si_unproc.csv exists, and the average HASPI scores for unprocessed "
+                "scenes is %.4f",
+                ave_score,
             )
 
     si_file = os.path.join(cfg.path.exp_folder, "si.csv")
@@ -50,9 +52,7 @@ def run_calculate_SI(cfg: DictConfig) -> None:
     if os.path.exists(si_file):
         score_dict = read_csv_scores(si_file)
         ave_score = np.mean(list(score_dict.values()))
-        logger.info(
-            "si.csv exists, and the average HASPI scores is {:.4f}".format(ave_score)
-        )
+        logger.info("si.csv exists, and the average HASPI scores is %4f", ave_score)
         return
 
     for scene in tqdm(scenes_listeners):
@@ -116,7 +116,7 @@ def run_calculate_SI(cfg: DictConfig) -> None:
                     ) % (10**8)
                     np.random.seed(scene_md5)
 
-                fs_unproc, unproc = wavfile.read(
+                _fs_unproc, unproc = wavfile.read(
                     os.path.join(cfg.path.scenes_folder, f"{scene}_mix_CH1.wav")
                 )
                 unproc = unproc / 32768.0
@@ -133,7 +133,7 @@ def run_calculate_SI(cfg: DictConfig) -> None:
                 logger.info(f"The unprocessed signal HASPI score is {si_unproc}")
                 unproc_csv_lines.append([scene, listener, str(si_unproc)])
 
-    with open(si_file, "w") as csv_f:
+    with open(si_file, "w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
@@ -141,12 +141,10 @@ def run_calculate_SI(cfg: DictConfig) -> None:
             csv_writer.writerow(line)
     score_dict = read_csv_scores(si_file)
     ave_score = np.mean(list(score_dict.values()))
-    logger.info(
-        "si.csv exists, and the average HASPI scores is {:.4f}".format(ave_score)
-    )
+    logger.info("si.csv exists, and the average HASPI scores is %.4f", ave_score)
 
     if cfg.evaluate.cal_unprocessed_si:
-        with open(unproc_si_file, "w") as csv_f:
+        with open(unproc_si_file, "w", encoding="utf-8") as csv_f:
             csv_writer = csv.writer(
                 csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
             )
@@ -155,11 +153,12 @@ def run_calculate_SI(cfg: DictConfig) -> None:
         score_dict = read_csv_scores(unproc_si_file)
         ave_score = np.mean(list(score_dict.values()))
         logger.info(
-            "si_unproc.csv exists, and the average HASPI scores for unprocessed scenes is {:.4f}".format(
-                ave_score
-            )
+            "si_unproc.csv exists, and the average HASPI scores "
+            "for unprocessed scenes is %.4f",
+            ave_score,
         )
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run_calculate_SI()
diff --git a/recipes/cec2/data_preparation/build_scenes.py b/recipes/cec2/data_preparation/build_scenes.py
index e0742d5db..43559ed7e 100644
--- a/recipes/cec2/data_preparation/build_scenes.py
+++ b/recipes/cec2/data_preparation/build_scenes.py
@@ -52,5 +52,6 @@ def run(cfg: DictConfig) -> None:
     instantiate_scenes(cfg)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cec2/data_preparation/render_scenes.py b/recipes/cec2/data_preparation/render_scenes.py
index f23b64d1f..b213a5401 100644
--- a/recipes/cec2/data_preparation/render_scenes.py
+++ b/recipes/cec2/data_preparation/render_scenes.py
@@ -12,8 +12,12 @@
 def render_scenes(cfg):
     for dataset in cfg.scene_renderer:
         logger.info(f"Beginning scene generation for {dataset} set...")
-        with open(cfg.scene_renderer[dataset].metadata.scene_definitions, "r") as f:
-            scenes = json.load(f)
+        with open(
+            cfg.scene_renderer[dataset].metadata.scene_definitions,
+            "r",
+            encoding="utf-8",
+        ) as fp:
+            scenes = json.load(fp)
 
         starting_scene = (
             cfg.scene_renderer[dataset].chunk_size * cfg.render_starting_chunk
@@ -37,5 +41,6 @@ def run(cfg: DictConfig) -> None:
     render_scenes(cfg)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cpc1/baseline/compute_scores.py b/recipes/cpc1/baseline/compute_scores.py
index a4b2bbe83..8bf76b630 100644
--- a/recipes/cpc1/baseline/compute_scores.py
+++ b/recipes/cpc1/baseline/compute_scores.py
@@ -31,8 +31,8 @@ def std_err(x, y):
 class Model:
     """Class to represent the mapping from mbstoi parameters to intelligibility scores.
     The mapping uses a simple logistic function scaled between 0 and 100.
-    The mapping parameters need to fit first using mbstoi, intelligibility score pairs, using fit().
-    Once the fit has been made predictions can be made by calling predict()
+    The mapping parameters need to fit first using mbstoi, intelligibility score pairs,
+    using fit(). Once the fit has been made predictions can be made by calling predict()
     """
 
     params = None  # The model params
@@ -49,7 +49,7 @@ def _logistic_mapping(self, x, x0, k):
     def fit(self, pred, intel):
         """Fit a mapping betweeen mbstoi scores and intelligibility scores."""
         initial_guess = [0.5, 1.0]  # Initial guess for parameter values
-        self.params, pcov = curve_fit(
+        self.params, *_remaining_returns = curve_fit(
             self._logistic_mapping, pred, intel, initial_guess
         )
 
@@ -73,7 +73,9 @@ def read_data(pred_csv, label_json):
     df_pred = pd.read_csv(pred_csv).rename(
         columns={"signal_ID": "signal", "intelligibility_score": "prediction"}
     )
-    df_label = pd.read_json(label_json).rename(columns={"correctness": "label"})
+    df_label = pd.read_json(label_json).rename(  # pylint: disable=no-member
+        columns={"correctness": "label"}
+    )
     data = df_pred.merge(df_label[["signal", "label"]])
     data["prediction"] = data["prediction"].apply(lambda x: x * 100)
     return data
@@ -111,15 +113,16 @@ def run(cfg: DictConfig) -> None:
     fit_pred = model.predict(data_eval["prediction"].to_numpy())
     open_set_scores = compute_scores(fit_pred, data_eval["label"].to_numpy())
 
-    with open("results.json", "w") as f:
+    with open("results.json", "w", encoding="utf-8") as fp:
         json.dump(
             {
                 "closed_set scores:": closed_set_scores,
                 "open_set scores:": open_set_scores,
             },
-            f,
+            fp,
         )
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cpc1/baseline/run.py b/recipes/cpc1/baseline/run.py
index 2b472d5e3..1ff44edb2 100644
--- a/recipes/cpc1/baseline/run.py
+++ b/recipes/cpc1/baseline/run.py
@@ -42,8 +42,10 @@ def listen(ear, signal, audiogram_l, audiogram_r):
 def run_HL_processing(cfg, path):
     output_path = os.path.join(path.exp_folder, "eval_signals")
     os.makedirs(output_path, exist_ok=True)
-    scenes = json.load(open(path.scenes_file))
-    listener_audiograms = json.load(open(path.listeners_file))
+    with open(path.scenes_file, "r", encoding="utf-8") as fp:
+        scenes = json.load(fp)
+    with open(path.listeners_file, "r", encoding="utf-8") as fp:
+        listener_audiograms = json.load(fp)
     enhanced_folder = path.scenes_folder
 
     # initialize ear
@@ -64,7 +66,7 @@ def run_HL_processing(cfg, path):
             f"{outfile_stem}_HL-mixoutput.wav",
         ]
         # if all signals to write exist, pass
-        if all([os.path.isfile(f) for f in signal_files_to_write]):
+        if all(os.path.isfile(f) for f in signal_files_to_write):
             continue
 
         signal = read_signal(signal_file)
@@ -93,17 +95,18 @@ def run_HL_processing(cfg, path):
             listen(ear, ddf_signal, left_audiogram, right_audiogram),
             listen(ear, mixture_signal, left_audiogram, right_audiogram),
         ]
-        for i in range(len(signals_to_write)):
+        for signal, signal_file in zip(signals_to_write, signal_files_to_write):
             write_signal(
-                signal_files_to_write[i],
-                signals_to_write[i],
+                signal_file,
+                signal,
                 MSBG_FS,
                 floating_point=True,
             )
 
 
 def run_calculate_SI(cfg, path) -> None:
-    scenes = json.load(open(path.scenes_file))
+    with open(path.scenes_file, "r", encoding="utf-8") as fp:
+        scenes = json.load(fp)
     proc_folder = os.path.join(path.exp_folder, "eval_signals")
     sii_file = os.path.join(path.exp_folder, "sii.csv")
     csv_lines = [["signal_ID", "intelligibility_score"]]
@@ -153,7 +156,7 @@ def run_calculate_SI(cfg, path) -> None:
         )
         csv_lines.append([f"{scene}_{listener}_{system}", sii])  # type: ignore
 
-    with open(sii_file, "w") as csv_f:
+    with open(sii_file, "w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
@@ -180,5 +183,6 @@ def run(cfg: DictConfig) -> None:
     run_calculate_SI(cfg, cfg.test_indep_path)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cpc1/e029_sheffield/evaluate.py b/recipes/cpc1/e029_sheffield/evaluate.py
index 16b42837e..abb3323c7 100644
--- a/recipes/cpc1/e029_sheffield/evaluate.py
+++ b/recipes/cpc1/e029_sheffield/evaluate.py
@@ -30,8 +30,8 @@ def std_err(x, y):
 class Model:
     """Class to represent the mapping from mbstoi parameters to intelligibility scores.
     The mapping uses a simple logistic function scaled between 0 and 100.
-    The mapping parameters need to fit first using mbstoi, intelligibility score pairs, using fit().
-    Once the fit has been made predictions can be made by calling predict()
+    The mapping parameters need to fit first using mbstoi, intelligibility score pairs,
+    using fit(). Once the fit has been made predictions can be made by calling predict()
     """
 
     params = None  # The model params
@@ -48,7 +48,7 @@ def _logistic_mapping(self, x, x0, k):
     def fit(self, pred, intel):
         """Fit a mapping betweeen mbstoi scores and intelligibility scores."""
         initial_guess = [0.5, 1.0]  # Initial guess for parameter values
-        self.params, pcov = curve_fit(
+        self.params, *_remaining_returns = curve_fit(
             self._logistic_mapping, pred, intel, initial_guess
         )
 
@@ -74,11 +74,13 @@ def read_data(pred_json, label_json):
 
     # read label_json to dict
     label_dict = {}
-    label_json = json.load(open(label_json))
+    with open(label_json, "r", encoding="utf-8") as fp:
+        label_json = json.load(fp)
     for item in label_json:
         label_dict[item["signal"]] = item["correctness"] / 100.0
 
-    pred_dict = json.load(open(pred_json))
+    with open(pred_json, "r", encoding="utf-8") as fp:
+        pred_dict = json.load(fp)
     for signal, pred in pred_dict.items():
         prediction.append(pred * 100.0)
         label.append(label_dict[signal])
@@ -127,15 +129,18 @@ def run(cfg: DictConfig) -> None:
     fit_pred = model.predict(prediction_test)
     negent_scores = compute_scores(fit_pred * 100, label_test * 100)
 
-    with open(os.path.join(cfg.path.exp_folder, "results.json"), "w") as f:
+    with open(
+        os.path.join(cfg.path.exp_folder, "results.json"), "w", encoding="utf-8"
+    ) as fp:
         json.dump(
             {
                 "confidence_results": conf_scores,
                 "negative_entropy_results": negent_scores,
             },
-            f,
+            fp,
         )
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cpc1/e029_sheffield/infer.py b/recipes/cpc1/e029_sheffield/infer.py
index 6201dcf18..5832be503 100644
--- a/recipes/cpc1/e029_sheffield/infer.py
+++ b/recipes/cpc1/e029_sheffield/infer.py
@@ -10,14 +10,27 @@
 from omegaconf import DictConfig
 from speechbrain.utils.distributed import run_on_main
 from tqdm import tqdm
-from transformer_cpc1_ensemble_decoder import S2STransformerBeamSearch
+from transformer_cpc1_ensemble_decoder import (  # pylint: disable=E0401
+    S2STransformerBeamSearch,
+)
 
 logger = logging.getLogger(__name__)
 
 
 class ASR(sb.core.Brain):
+    # pylint: disable=abstract-method
+    # Note, no implementation of compute_forward() or compute_objectives() provided
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.asr_ensemble = None
+        self.test_search = None
+
     def compute_uncertainty(self, wavs, wav_lens, tokens_bos):
-        """Forward computations from the waveform batches to the output probabilities."""
+        """Forward computations from waveform batches to the output probabilities."""
+        if self.asr_ensemble is None or self.test_search is None:
+            raise RuntimeError("ASR model not loaded")
+
         # batch = batch.to(self.device)
         wavs, wav_lens, tokens_bos = (
             wavs.to(self.device),
@@ -40,7 +53,7 @@ def compute_uncertainty(self, wavs, wav_lens, tokens_bos):
 
     def init_ensembles(self, n_ensemble):
         ensembles = []
-        for j in range(n_ensemble):
+        for _j in range(n_ensemble):
             ensembles.append(copy.deepcopy(self.hparams.model))
         return ensembles
 
@@ -81,8 +94,8 @@ def init_evaluation(self, max_key=None, min_key=None):
 
 def init_asr(asr_config):
     hparams_file, run_opts, overrides = sb.parse_arguments([asr_config])
-    with open(hparams_file) as fin:
-        hparams = load_hyperpyyaml(fin, overrides)
+    with open(hparams_file, "r", encoding="utf-8") as fp:
+        hparams = load_hyperpyyaml(fp, overrides)
 
     tokenizer = hparams["tokenizer"]
     bos_index = hparams["bos_index"]
@@ -104,7 +117,7 @@ def init_asr(asr_config):
     return asr_brain, tokenizer, bos_index
 
 
-def compute_uncertainty(left_proc_path, asr_model, bos_index, tokenizer):
+def compute_uncertainty(left_proc_path, asr_model, bos_index, _tokenizer):
     wav_len = torch.tensor([1], dtype=torch.float32)
     tokens_bos = torch.LongTensor([bos_index]).view(1, -1)
 
@@ -156,10 +169,14 @@ def run(cfg: DictConfig) -> None:
         dev_conf[wav_id] = uncertainty[0].tolist()
         dev_negent[wav_id] = uncertainty[1].tolist()
 
-        with open(os.path.join(cfg.path.exp_folder, "dev_conf.json"), "w") as f:
-            json.dump(dev_conf, f)
-        with open(os.path.join(cfg.path.exp_folder, "dev_negent.json"), "w") as f:
-            json.dump(dev_negent, f)
+        with open(
+            os.path.join(cfg.path.exp_folder, "dev_conf.json"), "w", encoding="utf-8"
+        ) as fp:
+            json.dump(dev_conf, fp)
+        with open(
+            os.path.join(cfg.path.exp_folder, "dev_negent.json"), "w", encoding="utf-8"
+        ) as fp:
+            json.dump(dev_negent, fp)
 
     # test set similarity
     test_conf = {}
@@ -172,11 +189,17 @@ def run(cfg: DictConfig) -> None:
         test_conf[wav_id] = uncertainty[0].tolist()
         test_negent[wav_id] = uncertainty[1].tolist()
 
-        with open(os.path.join(cfg.path.exp_folder, "test_conf.json"), "w") as f:
-            json.dump(test_conf, f)
-        with open(os.path.join(cfg.path.exp_folder, "test_negent.json"), "w") as f:
-            json.dump(test_negent, f)
+        with open(
+            os.path.join(cfg.path.exp_folder, "test_conf.json"), "w", encoding="utf-8"
+        ) as fp:
+            json.dump(test_conf, fp)
+
+        with open(
+            os.path.join(cfg.path.exp_folder, "test_negent.json"), "w", encoding="utf-8"
+        ) as fp:
+            json.dump(test_negent, fp)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cpc1/e029_sheffield/prepare_data.py b/recipes/cpc1/e029_sheffield/prepare_data.py
index a09be3a82..34e5db155 100644
--- a/recipes/cpc1/e029_sheffield/prepare_data.py
+++ b/recipes/cpc1/e029_sheffield/prepare_data.py
@@ -33,15 +33,13 @@ def run_data_split(cfg, track):
         logger.info("Train set and dev set lists exist...")
         return
 
-    scenes_dict = json.load(
-        open(
-            os.path.join(
-                cfg.path.root,
-                "clarity_CPC1_data_train/metadata",
-                f"CPC1.{'train'+track}.json",
-            )
-        )
+    file_path = os.path.join(
+        cfg.path.root,
+        "clarity_CPC1_data_train/metadata",
+        f"CPC1.{'train'+track}.json",
     )
+    with open(file_path, "r", encoding="utf-8") as fp:
+        scenes_dict = json.load(fp)
     scene_list = []
     for item in scenes_dict:
         scene_list.append(item["scene"])
@@ -51,10 +49,10 @@ def run_data_split(cfg, track):
     )
     scene_train_list = list(set(scene_list) - set(scene_dev_list))
 
-    with open(scene_train_json, "w") as f:
-        json.dump(scene_train_list, f)
-    with open(scene_dev_json, "w") as f:
-        json.dump(scene_dev_list, f)
+    with open(scene_train_json, "w", encoding="utf-8") as fp:
+        json.dump(scene_train_list, fp)
+    with open(scene_dev_json, "w", encoding="utf-8") as fp:
+        json.dump(scene_dev_list, fp)
 
 
 def listen(ear, signal, audiogram_l, audiogram_r):
@@ -83,23 +81,21 @@ def run_msbg_simulation(cfg, track):
         dataset = split + track
         dataset_folder = os.path.join(cfg.path.root, "clarity_CPC1_data_" + split)
         output_path = os.path.join(dataset_folder, "clarity_data/HA_outputs", dataset)
-        scenes = json.load(
-            open(os.path.join(dataset_folder, "metadata", f"CPC1.{dataset}.json"))
-        )
+        file_path = os.path.join(dataset_folder, "metadata", f"CPC1.{dataset}.json")
+        with open(file_path, "r", encoding="utf-8") as fp:
+            scenes = json.load(fp)
         if split == "train":
-            listener_audiograms = json.load(
-                open(
-                    os.path.join(
-                        dataset_folder, "metadata", "listeners.CPC1_train.json"
-                    )
-                )
+            file_path = os.path.join(
+                dataset_folder, "metadata", "listeners.CPC1_train.json"
             )
+            with open(file_path, "r", encoding="utf-8") as fp:
+                listener_audiograms = json.load(fp)
         else:
-            listener_audiograms = json.load(
-                open(
-                    os.path.join(dataset_folder, "metadata", "listeners.CPC1_all.json")
-                )
+            file_path = os.path.join(
+                dataset_folder, "metadata", "listeners.CPC1_all.json"
             )
+            with open(file_path, "r", encoding="utf-8") as fp:
+                listener_audiograms = json.load(fp)
 
         # initialize ear
         ear = Ear(**cfg["MSBGEar"])
@@ -116,7 +112,7 @@ def run_msbg_simulation(cfg, track):
                 f"{outfile_stem}_HL-output.wav",
             ]
             # if all signals to write exist, pass
-            if all([os.path.isfile(f) for f in signal_files_to_write]):
+            if all(os.path.isfile(f) for f in signal_files_to_write):
                 continue
             signal = read_signal(signal_file)
 
@@ -134,10 +130,11 @@ def run_msbg_simulation(cfg, track):
             signals_to_write = [
                 listen(ear, signal, left_audiogram, right_audiogram),
             ]
-            for i in range(len(signals_to_write)):
+
+            for signal, signal_file in zip(signals_to_write, signal_files_to_write):
                 write_signal(
-                    signal_files_to_write[i],
-                    signals_to_write[i],
+                    signal_file,
+                    signal,
                     MSBG_FS,
                     floating_point=True,
                 )
@@ -152,8 +149,8 @@ def generate_data_split(
     if_msbg=False,
     if_ref=False,
 ):
-    with open(orig_data_json, "r") as f:
-        all_data_list = json.load(f)
+    with open(orig_data_json, "r", encoding="utf-8") as fp:
+        all_data_list = json.load(fp)
 
     left_tgt_signal_folder = os.path.join(
         target_data_folder, orig_signal_folder.split("/")[-2] + "_left"
@@ -212,7 +209,9 @@ def generate_data_split(
             wrds = item["prompt"].upper()
 
             utt, orig_fs = sf.read(wav_file)
-            utt_16k = resample(np.array(utt).transpose(), orig_fs, targ_fs).transpose()
+            utt_16k = resample(
+                np.array(utt).transpose(), orig_sr=orig_fs, target_sr=targ_fs
+            ).transpose()
             duration = (
                 len(utt_16k[2 * targ_fs :, 0]) / targ_fs
             )  # Get rid of the first two seconds, as there is no speech
@@ -230,21 +229,21 @@ def generate_data_split(
                 ["right_" + snt_id, str(duration), wav_file_right, spk_id, wrds]
             )
 
-    with open(left_csvfile, mode="w") as csv_f:
+    with open(left_csvfile, mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
         for line in csv_lines_left:
             csv_writer.writerow(line)
 
-    with open(right_csvfile, mode="w") as csv_f:
+    with open(right_csvfile, mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
         for line in csv_lines_right:
             csv_writer.writerow(line)
 
-    with open(binaural_csvfile, mode="w") as csv_f:
+    with open(binaural_csvfile, mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
@@ -268,17 +267,16 @@ def run_signal_generation_train(cfg, track):
     ]
 
     datasets_to_generate = []
-    for i in range(len(lists_to_generate)):
-        with open(lists_to_generate[i], "r") as f:
-            datasets_to_generate.append(json.load(f))
-            f.close()
+    for list_to_generate in lists_to_generate:
+        with open(list_to_generate, "r", encoding="utf-8") as fp:
+            datasets_to_generate.append(json.load(fp))
 
-    for i in range(len(lists_to_generate)):
+    for list_to_generate, dataset in zip(lists_to_generate, datasets_to_generate):
         # generate_data_split(
         #     train_json_path,
         #     train_signal_folder,
         #     target_folder,
-        #     os.path.basename(lists_to_generate[i]).split("_")[1],
+        #     os.path.basename(list_to_generate).split("_")[1],
         #     datasets_to_generate[i],
         # )
 
@@ -286,8 +284,8 @@ def run_signal_generation_train(cfg, track):
             train_json_path,
             train_signal_folder,
             target_folder,
-            os.path.basename(lists_to_generate[i]).split("_")[1],
-            datasets_to_generate[i],
+            os.path.basename(list_to_generate).split("_")[1],
+            dataset,
             if_msbg=True,
         )
 
@@ -295,8 +293,8 @@ def run_signal_generation_train(cfg, track):
             train_json_path,
             train_signal_folder,
             target_folder,
-            os.path.basename(lists_to_generate[i]).split("_")[1],
-            datasets_to_generate[i],
+            os.path.basename(list_to_generate).split("_")[1],
+            dataset,
             if_ref=True,
         )
 
@@ -342,5 +340,6 @@ def run(cfg: DictConfig) -> None:
     run_signal_generation_test(cfg, track)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cpc1/e029_sheffield/train_asr.py b/recipes/cpc1/e029_sheffield/train_asr.py
index c001ee163..0f99a2e8d 100644
--- a/recipes/cpc1/e029_sheffield/train_asr.py
+++ b/recipes/cpc1/e029_sheffield/train_asr.py
@@ -1,10 +1,12 @@
 #!/usr/bin/env python3
 """
-Recipe for training a Transformer ASR system with librispeech, from the SpeechBrain LibriSpeech/ASR recipe.
-The SpeechBrain version used in this work is:
+Recipe for training a Transformer ASR system with librispeech, from the SpeechBrain
+LibriSpeech/ASR recipe. The SpeechBrain version used in this work is:
 https://github.com/speechbrain/speechbrain/tree/1eddf66eea01866d3cf9dfe61b00bb48d2062236
 """
 
+# pylint: disable=W0201  # code requires attributes defined outside __init__
+
 import logging
 import os
 import sys
@@ -17,11 +19,13 @@
 
 logger = logging.getLogger(__name__)
 
+tokenizer = None
+
 
 # Define training procedure
 class ASR(sb.core.Brain):
     def compute_forward(self, batch, stage):
-        """Forward computations from the waveform batches to the output probabilities."""
+        """Forward computations from waveform batches to the output probabilities."""
         batch = batch.to(self.device)
         wavs, wav_lens = batch.sig
         tokens_bos, _ = batch.tokens_bos
@@ -57,8 +61,8 @@ def compute_forward(self, batch, stage):
             hyps = None
             current_epoch = self.hparams.epoch_counter.current
             if current_epoch % self.hparams.valid_search_interval == 0:
-                # for the sake of efficiency, we only perform beamsearch with limited capacity
-                # and no LM to give user some idea of how the AM is doing
+                # for the sake of efficiency, we only perform beamsearch with limited
+                # capacity and no LM to give user some idea of how the AM is doing
                 hyps, _ = self.hparams.valid_search(enc_out.detach(), wav_lens)
         elif stage == sb.Stage.TEST:
             hyps, _ = self.hparams.test_search(enc_out.detach(), wav_lens)
@@ -127,13 +131,13 @@ def evaluate_batch(self, batch, stage):
             loss = self.compute_objectives(predictions, batch, stage=stage)
         return loss.detach()
 
-    def on_stage_start(self, stage, epoch):
+    def on_stage_start(self, stage, epoch=None):
         """Gets called at the beginning of each epoch"""
         if stage != sb.Stage.TRAIN:
             self.acc_metric = self.hparams.acc_computer()
             self.wer_metric = self.hparams.error_rate_computer()
 
-    def on_stage_end(self, stage, stage_loss, epoch):
+    def on_stage_end(self, stage, stage_loss, epoch=None):
         """Gets called at the end of a epoch."""
         # Compute/store important stats
         stage_stats = {"loss": stage_loss}
@@ -181,8 +185,8 @@ def on_stage_end(self, stage, stage_loss, epoch):
                 stats_meta={"Epoch loaded": self.hparams.epoch_counter.current},
                 test_stats=stage_stats,
             )
-            with open(self.hparams.wer_file, "w") as w:
-                self.wer_metric.write_stats(w)
+            with open(self.hparams.wer_file, "w", encoding="utf-8") as fp:
+                self.wer_metric.write_stats(fp)
 
             # save the averaged checkpoint at the end of the evaluation stage
             # delete the rest of the intermediate checkpoints
@@ -223,7 +227,8 @@ 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
+                # do not reload the weights if training is interrupted right before
+                # stage 2
                 group = current_optimizer.param_groups[0]
                 if "momentum" not in group:
                     return
@@ -270,7 +275,8 @@ def dataio_prepare(hparams):
 
     # We get the tokenizer as we need it to encode the labels when creating
     # mini-batches.
-    tokenizer = hparams["tokenizer"]
+    # (Note, tokenizer is also defined in global space. TODO: fix the design)
+    tokenizer = hparams["tokenizer"]  # pylint: disable=redefined-outer-name
 
     # 2. Define audio pipeline:
     @sb.utils.data_pipeline.takes("wav")
@@ -306,11 +312,11 @@ def text_pipeline(wrd):
     return train_data, valid_data, test_datasets, tokenizer
 
 
-if __name__ == "__main__":
+def main():
     # CLI:
     hparams_file, run_opts, overrides = sb.parse_arguments(sys.argv[1:])
-    with open(hparams_file) as fin:
-        hparams = load_hyperpyyaml(fin, overrides)
+    with open(hparams_file, "r", encoding="utf-8") as fp:
+        hparams = load_hyperpyyaml(fp, overrides)
 
     # If distributed_launch=True then
     # create ddp_group with the right communication protocol
@@ -324,7 +330,7 @@ def text_pipeline(wrd):
     )
 
     # here we create the datasets objects as well as tokenization and encoding
-    train_data, valid_data, test_datasets, tokenizer = dataio_prepare(hparams)
+    train_data, valid_data, test_datasets, _tokenizer = dataio_prepare(hparams)
 
     # We download the pretrained LM from HuggingFace (or elsewhere depending on
     # the path given in the YAML file). The tokenizer is loaded at the same time.
@@ -353,12 +359,16 @@ def text_pipeline(wrd):
     )
 
     # Testing
-    for k in test_datasets.keys():  # keys are test_clean, test_other etc
+    for dataset_key, test_dataset in test_datasets.items():
         asr_brain.hparams.wer_file = os.path.join(
-            hparams["output_folder"], "wer_{}.txt".format(k)
+            hparams["output_folder"], f"wer_{dataset_key}.txt"
         )
         asr_brain.evaluate(
-            test_datasets[k],
+            test_dataset,
             max_key="ACC",
             test_loader_kwargs=hparams["test_dataloader_opts"],
         )
+
+
+if __name__ == "__main__":
+    main()
diff --git a/recipes/cpc1/e029_sheffield/transformer_cpc1_ensemble_decoder.py b/recipes/cpc1/e029_sheffield/transformer_cpc1_ensemble_decoder.py
index 64c6a1db6..001235453 100644
--- a/recipes/cpc1/e029_sheffield/transformer_cpc1_ensemble_decoder.py
+++ b/recipes/cpc1/e029_sheffield/transformer_cpc1_ensemble_decoder.py
@@ -40,7 +40,7 @@ class S2SBaseSearcher(torch.nn.Module):
     """
 
     def __init__(self, bos_index, eos_index, min_decode_ratio, max_decode_ratio):
-        super(S2SBaseSearcher, self).__init__()
+        super().__init__()
         self.bos_index = bos_index
         self.eos_index = eos_index
         self.min_decode_ratio = min_decode_ratio
@@ -59,7 +59,7 @@ def forward(self, enc_states, wav_len):
         """
         raise NotImplementedError
 
-    def forward_step(self, inp_tokens, memory, enc_states, enc_lens):
+    def forward_step(self, inp_tokens, memory, enc_states, enc_lens, index=0):
         """This method should implement one step of
         forwarding operation in the autoregressive model.
 
@@ -74,6 +74,8 @@ def forward_step(self, inp_tokens, memory, enc_states, enc_lens):
             The encoder states to be attended.
         enc_lens : torch.Tensor
             The actual length of each enc_states sequence.
+        index : int
+            The current timestep index.
 
         Returns
         -------
@@ -153,6 +155,9 @@ class S2SGreedySearcher(S2SBaseSearcher):
     greedy decoding approach. See also S2SBaseSearcher().
     """
 
+    # pylint: disable=W0223
+    # This class is intentionally abstract so disable these warnings
+
     def forward(self, enc_states, wav_len):
         enc_lens = torch.round(enc_states.shape[1] * wav_len).int()
         device = enc_states.device
@@ -166,7 +171,7 @@ def forward(self, enc_states, wav_len):
         log_probs_lst = []
         max_decode_steps = int(enc_states.shape[1] * self.max_decode_ratio)
 
-        for t in range(max_decode_steps):
+        for _t in range(max_decode_steps):
             log_probs, memory, _ = self.forward_step(
                 inp_tokens, memory, enc_states, enc_lens
             )
@@ -219,8 +224,11 @@ class S2SRNNGreedySearcher(S2SGreedySearcher):
     >>> hyps, scores = searcher(enc, wav_len)
     """
 
+    # pylint: disable=W0223
+    # Doesn't implement lm_forward_step() or reset_lm_mem()
+
     def __init__(self, embedding, decoder, linear, **kwargs):
-        super(S2SRNNGreedySearcher, self).__init__(**kwargs)
+        super().__init__(**kwargs)
         self.emb = embedding
         self.dec = decoder
         self.fc = linear
@@ -235,7 +243,8 @@ def reset_mem(self, batch_size, device):
         c = torch.zeros(batch_size, self.dec.attn_dim, device=device)
         return hs, c
 
-    def forward_step(self, inp_tokens, memory, enc_states, enc_lens):
+    def forward_step(self, inp_tokens, memory, enc_states, enc_lens, _index=0):
+        # Note, _index is not used here, but it is defined in base class method
         hs, c = memory
         e = self.emb(inp_tokens)
         dec_out, hs, c, w = self.dec.forward_step(e, hs, c, enc_states, enc_lens)
@@ -276,7 +285,8 @@ class S2SBeamSearcher(S2SBaseSearcher):
     coverage_penalty: float
         The coefficient of coverage penalty (η).
         log P(y|x) + λ log P_LM(y) + γ*len(y) + η*coverage(x,y). (default: 0.0)
-        Reference: https://arxiv.org/pdf/1612.02695.pdf, https://arxiv.org/pdf/1808.10792.pdf
+        Reference: https://arxiv.org/pdf/1612.02695.pdf,
+            https://arxiv.org/pdf/1808.10792.pdf
     lm_weight : float
         The weight of LM when performing beam search (λ).
         log P(y|x) + λ log P_LM(y). (default: 0.0)
@@ -290,8 +300,8 @@ class S2SBeamSearcher(S2SBaseSearcher):
         CTC prefix scoring on "partial" token or "full: token.
     ctc_window_size: int
         Default: 0
-        Compute the ctc scores over the time frames using windowing based on attention peaks.
-        If 0, no windowing applied.
+        Compute the ctc scores over the time frames using windowing based on
+        attention peaks. If 0, no windowing applied.
     using_max_attn_shift: bool
         Whether using the max_attn_shift constraint. (default: False)
     max_attn_shift: int
@@ -329,10 +339,9 @@ def __init__(
         max_attn_shift=60,
         minus_inf=-1e20,
     ):
-        super(S2SBeamSearcher, self).__init__(
-            bos_index, eos_index, min_decode_ratio, max_decode_ratio
-        )
+        super().__init__(bos_index, eos_index, min_decode_ratio, max_decode_ratio)
         self.beam_size = beam_size
+        self.beam_offset = None
         self.topk = topk
         self.return_log_probs = return_log_probs
         self.length_normalization = length_normalization
@@ -365,7 +374,8 @@ def __init__(
         if self.ctc_weight > 0.0:
             if len({self.bos_index, self.eos_index, self.blank_index}) < 3:
                 raise ValueError(
-                    "To perform joint ATT/CTC decoding, set blank, eos and bos to different indexes."
+                    "To perform joint ATT/CTC decoding, set blank, eos and bos to "
+                    " different indexes."
                 )
 
         # ctc already initialized
@@ -380,7 +390,8 @@ def _check_full_beams(self, hyps, beam_size):
         ---------
         hyps : List
             This list contains batch_size number.
-            Each inside list contains a list stores all the hypothesis for this sentence.
+            Each inside list contains a list storing all the hypothesis for
+                this sentence.
         beam_size : int
             The number of beam_size.
 
@@ -390,11 +401,8 @@ def _check_full_beams(self, hyps, beam_size):
             Whether the hyps has been full.
         """
         hyps_len = [len(lst) for lst in hyps]
-        beam_size = [self.beam_size for _ in range(len(hyps_len))]
-        if hyps_len == beam_size:
-            return True
-        else:
-            return False
+        beam_size = [self.beam_size] * len(hyps_len)
+        return hyps_len == beam_size
 
     def _check_attn_shift(self, attn, prev_attn_peak):
         """This method checks whether attention shift is more than attn_shift.
@@ -490,15 +498,17 @@ def _update_hyp_and_scores(
                 hyps_and_scores[batch_id].append((hyp, log_probs, final_scores))
         return is_eos
 
-    def _get_top_score_prediction(self, hyps_and_scores, topk):
-        """This method sorts the scores and return corresponding hypothesis and log probs.
+    # pylint: disable=W0613
+    def _get_top_score_prediction(self, all_hyps_and_scores, topk):
+        """Sort the scores and return corresponding hypothesis and log probs.
 
         Arguments
         ---------
-        hyps_and_scores : list
+        all_hyps_and_scores : list
             To store generated hypotheses and scores.
         topk : int
             Number of hypothesis to return.
+            Deprecated, use self.topk instead.
 
         Returns
         -------
@@ -512,11 +522,11 @@ def _get_top_score_prediction(self, hyps_and_scores, topk):
             The log probabilities of each hypotheses.
         """
         top_hyps, top_log_probs, top_scores, top_lengths = [], [], [], []
-        batch_size = len(hyps_and_scores)
+        batch_size = len(all_hyps_and_scores)
 
         # Collect hypotheses
-        for i in range(len(hyps_and_scores)):
-            hyps, log_probs, scores = zip(*hyps_and_scores[i])
+        for hyps_and_scores in all_hyps_and_scores:
+            hyps, log_probs, scores = zip(*hyps_and_scores)
             top_hyps += hyps
             top_scores += scores
             top_log_probs += log_probs
@@ -540,8 +550,9 @@ def _get_top_score_prediction(self, hyps_and_scores, topk):
 
         return topk_hyps, topk_scores, topk_lengths, topk_log_probs
 
-    # def forward(self, enc_states, wav_len):  # noqa: C901
+    # Extra parameters have been added to overriden function
     def forward(self, src, tokens_bos, wav_lens, pad_idx):  # noqa: C901
+        # pylint: disable=arguments-differ
         all_enc_states = []
         all_enc_lens = []
         all_memory = []
@@ -609,7 +620,7 @@ def forward(self, src, tokens_bos, wav_lens, pad_idx):  # noqa: C901
         # keep only the first to make sure no redundancy.
         sequence_scores.index_fill_(0, self.beam_offset, 0.0)
 
-        # keep the hypothesis that reaches eos and their corresponding score and log_probs.
+        # keep the hypothesis that reaches eos and corresponding score and log_probs.
         hyps_and_scores = [[] for _ in range(batch_size)]
 
         # keep the sequences that still not reaches eos.
@@ -724,7 +735,7 @@ def forward(self, src, tokens_bos, wav_lens, pad_idx):  # noqa: C901
             if self.length_normalization:
                 sequence_scores = sequence_scores * (t + 1)
 
-            # The index of which beam the current top-K output came from in (t-1) timesteps.
+            # Index of which beam the current top-K output came from in (t-1) timesteps.
             predecessors = (
                 torch.div(candidates, vocab_size, rounding_mode="floor")
                 + self.beam_offset.unsqueeze(1).expand_as(candidates)
@@ -741,7 +752,7 @@ def forward(self, src, tokens_bos, wav_lens, pad_idx):  # noqa: C901
                         all_ctc_memory[j], candidates
                     )
 
-            # If using_max_attn_shift, then the previous attn peak has to be permuted too.
+            # If using_max_attn_shift, the previous attn peak has to be permuted too.
             if self.using_max_attn_shift:
                 prev_attn_peak = torch.index_select(
                     prev_attn_peak, dim=0, index=predecessors
@@ -756,9 +767,10 @@ def forward(self, src, tokens_bos, wav_lens, pad_idx):  # noqa: C901
                     # Init coverage
                     self.coverage = cur_attn
 
-                # the attn of transformer is [batch_size*beam_size, current_step, source_len]
+                # the attn of transformer is
+                #   [batch_size*beam_size, current_step, source_len]
                 if len(cur_attn.size()) > 2:
-                    self.converage = torch.sum(cur_attn, dim=1)
+                    self.coverage = torch.sum(cur_attn, dim=1)
                 else:
                     # Update coverage
                     self.coverage = torch.index_select(
@@ -843,8 +855,8 @@ def forward(self, src, tokens_bos, wav_lens, pad_idx):  # noqa: C901
                 # for entropy prediction
                 beam_logprobs = log_probs[j * self.topk : (j + 1) * self.topk]
                 beam_seq_probs = []
-                for m in range(len(beam_logprobs)):
-                    seq_prob = torch.sum(beam_logprobs[m])
+                for beam_logprob in beam_logprobs:
+                    seq_prob = torch.sum(beam_logprob)
                     beam_seq_probs.append(seq_prob)
                 beam_seq_probs = torch.stack(beam_seq_probs, dim=0)
                 beam_seq_probs_softmax = torch.nn.functional.softmax(beam_seq_probs)
@@ -862,8 +874,8 @@ def forward(self, src, tokens_bos, wav_lens, pad_idx):  # noqa: C901
                 uncertainty.append({"confidence": confidence, "entropy": entropy})
 
             return predictions, topk_scores, uncertainty
-        else:
-            return predictions, topk_scores
+
+        return predictions, topk_scores
 
     def ctc_forward_step(self, x, model_idx):
         logits = self.ctc_fc[model_idx](x)
@@ -951,10 +963,13 @@ class S2SRNNBeamSearcher(S2SBeamSearcher):
     >>> hyps, scores = searcher(enc, wav_len)
     """
 
+    # pylint: disable=W0223
+    # Doesn't implement lm_forward_step(), permute_lm_mem() or reset_lm_mem()
+
     def __init__(
         self, embedding, decoder, linear, ctc_linear=None, temperature=1.0, **kwargs
     ):
-        super(S2SRNNBeamSearcher, self).__init__(**kwargs)
+        super().__init__(**kwargs)
         self.emb = embedding
         self.dec = decoder
         self.fc = linear
@@ -971,7 +986,8 @@ def reset_mem(self, batch_size, device):
         c = torch.zeros(batch_size, self.dec.attn_dim, device=device)
         return hs, c
 
-    def forward_step(self, inp_tokens, memory, enc_states, enc_lens):
+    def forward_step(self, inp_tokens, memory, enc_states, enc_lens, _index=0):
+        # Note, _index is not used here, but it is defined in base class method
         with torch.no_grad():
             hs, c = memory
             e = self.emb(inp_tokens)
@@ -1052,7 +1068,7 @@ class S2SRNNBeamSearchLM(S2SRNNBeamSearcher):
     def __init__(
         self, embedding, decoder, linear, language_model, temperature_lm=1.0, **kwargs
     ):
-        super(S2SRNNBeamSearchLM, self).__init__(embedding, decoder, linear, **kwargs)
+        super().__init__(embedding, decoder, linear, **kwargs)
 
         self.lm = language_model
         self.lm.eval()
@@ -1138,9 +1154,7 @@ class S2SRNNBeamSearchTransformerLM(S2SRNNBeamSearcher):
     def __init__(
         self, embedding, decoder, linear, language_model, temperature_lm=1.0, **kwargs
     ):
-        super(S2SRNNBeamSearchTransformerLM, self).__init__(
-            embedding, decoder, linear, **kwargs
-        )
+        super().__init__(embedding, decoder, linear, **kwargs)
 
         self.lm = language_model
         self.lm.eval()
@@ -1196,7 +1210,7 @@ def inflate_tensor(tensor, times, dim):
 
 
 def mask_by_condition(tensor, cond, fill_value):
-    """This function will mask some element in the tensor with fill_value, if condition=False.
+    """Mask some element in the tensor with fill_value, if condition=False.
 
     Arguments
     ---------
@@ -1262,7 +1276,7 @@ class S2STransformerBeamSearch(S2SBeamSearcher):
     """
 
     def __init__(self, modules, temperature=1.0, temperature_lm=1.0, **kwargs):
-        super(S2STransformerBeamSearch, self).__init__(**kwargs)
+        super().__init__(**kwargs)
 
         # self.model = modules[0]
         # self.fc = modules[1]
@@ -1293,10 +1307,10 @@ def permute_lm_mem(self, memory, index):
         memory = torch.index_select(memory, dim=0, index=index)
         return memory
 
-    def forward_step(self, inp_tokens, memory, enc_states, enc_lens, model_idx):
+    def forward_step(self, inp_tokens, memory, enc_states, enc_lens, index=0):
         memory = _update_mem(inp_tokens, memory)
-        pred, attn = self.model[model_idx].decode(memory, enc_states)
-        prob_dist = self.softmax(self.fc[model_idx](pred) / self.temperature)
+        pred, attn = self.model[index].decode(memory, enc_states)
+        prob_dist = self.softmax(self.fc[index](pred) / self.temperature)
         return prob_dist[:, -1, :], memory, attn
 
     def lm_forward_step(self, inp_tokens, memory):
diff --git a/recipes/cpc1/e032_sheffield/evaluate.py b/recipes/cpc1/e032_sheffield/evaluate.py
index 93851639c..ff66b150e 100644
--- a/recipes/cpc1/e032_sheffield/evaluate.py
+++ b/recipes/cpc1/e032_sheffield/evaluate.py
@@ -30,7 +30,8 @@ def std_err(x, y):
 class Model:
     """Class to represent the mapping from mbstoi parameters to intelligibility scores.
     The mapping uses a simple logistic function scaled between 0 and 100.
-    The mapping parameters need to fit first using mbstoi, intelligibility score pairs, using fit().
+    The mapping parameters need to fit first using mbstoi, intelligibility score
+    pairs, using fit().
     Once the fit has been made predictions can be made by calling predict()
     """
 
@@ -48,7 +49,7 @@ def _logistic_mapping(self, x, x0, k):
     def fit(self, pred, intel):
         """Fit a mapping betweeen mbstoi scores and intelligibility scores."""
         initial_guess = [50.0, 1.0]  # Initial guess for parameter values
-        self.params, pcov = curve_fit(
+        self.params, *_remaining_returns = curve_fit(
             self._logistic_mapping, pred, intel, initial_guess
         )
 
@@ -74,11 +75,13 @@ def read_data(pred_json, label_json):
 
     # read label_json to dict
     label_dict = {}
-    label_json = json.load(open(label_json))
+    with open(label_json, "r", encoding="utf-8") as fp:
+        label_json = json.load(fp)
     for item in label_json:
         label_dict[item["signal"]] = item["correctness"]
 
-    pred_dict = json.load(open(pred_json))
+    with open(pred_json, "r", encoding="utf-8") as fp:
+        pred_dict = json.load(fp)
     for signal, pred in pred_dict.items():
         prediction.append(pred * 100.0)
         label.append(label_dict[signal])
@@ -127,9 +130,12 @@ def run(cfg: DictConfig) -> None:
     fit_pred = model.predict(prediction_test)
     dec_scores = compute_scores(fit_pred, label_test)
 
-    with open(os.path.join(cfg.path.exp_folder, "results.json"), "w") as f:
+    with open(
+        os.path.join(cfg.path.exp_folder, "results.json"), "w", encoding="utf-8"
+    ) as f:
         json.dump({"enc_results": enc_scores, "dec_results": dec_scores}, f)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cpc1/e032_sheffield/infer.py b/recipes/cpc1/e032_sheffield/infer.py
index fa930e9b7..56d0edca8 100644
--- a/recipes/cpc1/e032_sheffield/infer.py
+++ b/recipes/cpc1/e032_sheffield/infer.py
@@ -11,15 +11,25 @@
 from scipy.spatial.distance import cosine
 from speechbrain.utils.distributed import run_on_main
 from tqdm import tqdm
-from transfromer_cpc1_decoder import S2STransformerBeamSearch
+from transformer_cpc1_decoder import S2STransformerBeamSearch  # pylint: disable=E0401
 
 logger = logging.getLogger(__name__)
 
 
 class ASR(sb.core.Brain):
+    # pylint: disable=abstract-method
+    # Note, no implementation of compute_forward() or compute_objectives() provided
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.test_search = None
+
     def generate_feats(self, wavs, wav_lens, tokens_bos):
-        """Forward computations from the waveform batches to the output probabilities."""
+        """Forward computations from the waveform batches to the output probs."""
         # batch = batch.to(self.device)
+        if self.test_search is None:
+            raise ValueError("test_search is not initialized")
+
         wavs, wav_lens, tokens_bos = (
             wavs.to(self.device),
             wav_lens.to(self.device),
@@ -74,8 +84,8 @@ def init_evaluation(self, max_key=None, min_key=None):
 
 def init_asr(asr_config):
     hparams_file, run_opts, overrides = sb.parse_arguments([asr_config])
-    with open(hparams_file) as fin:
-        hparams = load_hyperpyyaml(fin, overrides)
+    with open(hparams_file, "r", encoding="utf-8") as fp:
+        hparams = load_hyperpyyaml(fp, overrides)
 
     tokenizer = hparams["tokenizer"]
     bos_index = hparams["bos_index"]
@@ -103,9 +113,9 @@ def dtw_similarity(x, y):
     )[1]
 
     x_, y_ = [], []
-    for step in range(len(path)):
-        x_.append(x[:, path[step][0], :])
-        y_.append(y[:, path[step][1], :])
+    for step in path:
+        x_.append(x[:, step[0], :])
+        y_.append(y[:, step[1], :])
     x_ = torch.stack(x_, dim=1)
     y_ = torch.stack(y_, dim=1)
     return torch.nn.functional.cosine_similarity(x_, y_, dim=-1)
@@ -131,7 +141,6 @@ def feat2similarity(
             ),
             dim=-1,
         )[0]
-        return sim
     else:
         max_length = torch.max(
             torch.LongTensor(
@@ -173,7 +182,8 @@ def feat2similarity(
             padded_ref_feats_right, padded_proc_feats_right, dim=-1
         )
         sim = torch.stack([ll_sim, lr_sim, rl_sim, rr_sim], dim=-1).max(dim=-1)[0]
-        return torch.mean(sim, dim=-1)
+        sim = torch.mean(sim, dim=-1)
+    return sim
 
 
 def compute_similarity(left_proc_path, wrd, asr_model, bos_index, tokenizer):
@@ -240,13 +250,17 @@ def run(cfg: DictConfig) -> None:
         dev_dec_similarity[wav_id] = similarity[1].tolist()
 
         with open(
-            os.path.join(cfg.path.exp_folder, "dev_enc_similarity.json"), "w"
-        ) as f:
-            json.dump(dev_enc_similarity, f)
+            os.path.join(cfg.path.exp_folder, "dev_enc_similarity.json"),
+            "w",
+            encoding="utf-8",
+        ) as fp:
+            json.dump(dev_enc_similarity, fp)
         with open(
-            os.path.join(cfg.path.exp_folder, "dev_dec_similarity.json"), "w"
-        ) as f:
-            json.dump(dev_dec_similarity, f)
+            os.path.join(cfg.path.exp_folder, "dev_dec_similarity.json"),
+            "w",
+            encoding="utf-8",
+        ) as fp:
+            json.dump(dev_dec_similarity, fp)
 
     # test set similarity
     test_enc_similarity = {}
@@ -261,14 +275,20 @@ def run(cfg: DictConfig) -> None:
         test_dec_similarity[wav_id] = similarity[1].tolist()
 
         with open(
-            os.path.join(cfg.path.exp_folder, "test_enc_similarity.json"), "w"
-        ) as f:
-            json.dump(test_enc_similarity, f)
+            os.path.join(cfg.path.exp_folder, "test_enc_similarity.json"),
+            "w",
+            encoding="utf-8",
+        ) as fp:
+            json.dump(test_enc_similarity, fp)
+
         with open(
-            os.path.join(cfg.path.exp_folder, "test_dec_similarity.json"), "w"
-        ) as f:
-            json.dump(test_dec_similarity, f)
+            os.path.join(cfg.path.exp_folder, "test_dec_similarity.json"),
+            "w",
+            encoding="utf-8",
+        ) as fp:
+            json.dump(test_dec_similarity, fp)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cpc1/e032_sheffield/prepare_data.py b/recipes/cpc1/e032_sheffield/prepare_data.py
index d9467a1e1..6bf2fabb7 100644
--- a/recipes/cpc1/e032_sheffield/prepare_data.py
+++ b/recipes/cpc1/e032_sheffield/prepare_data.py
@@ -33,15 +33,13 @@ def run_data_split(cfg, track):
         logger.info("Train set and dev set lists exist...")
         return
 
-    scenes_dict = json.load(
-        open(
-            os.path.join(
-                cfg.path.root,
-                "clarity_CPC1_data_train/metadata",
-                f"CPC1.{'train'+track}.json",
-            )
-        )
+    filepath = os.path.join(
+        cfg.path.root,
+        "clarity_CPC1_data_train/metadata",
+        f"CPC1.{'train'+track}.json",
     )
+    with open(filepath, "r", encoding="utf-8") as fp:
+        scenes_dict = json.load(fp)
     scene_list = []
     for item in scenes_dict:
         scene_list.append(item["scene"])
@@ -51,10 +49,10 @@ def run_data_split(cfg, track):
     )
     scene_train_list = list(set(scene_list) - set(scene_dev_list))
 
-    with open(scene_train_json, "w") as f:
-        json.dump(scene_train_list, f)
-    with open(scene_dev_json, "w") as f:
-        json.dump(scene_dev_list, f)
+    with open(scene_train_json, "w", encoding="utf-8") as fp:
+        json.dump(scene_train_list, fp)
+    with open(scene_dev_json, "w", encoding="utf-8") as fp:
+        json.dump(scene_dev_list, fp)
 
 
 def listen(ear, signal, audiogram_l, audiogram_r):
@@ -83,23 +81,21 @@ def run_msbg_simulation(cfg, track):
         dataset = split + track
         dataset_folder = os.path.join(cfg.path.root, "clarity_CPC1_data_" + split)
         output_path = os.path.join(dataset_folder, "clarity_data/HA_outputs", dataset)
-        scenes = json.load(
-            open(os.path.join(dataset_folder, "metadata", f"CPC1.{dataset}.json"))
-        )
+        file_path = os.path.join(dataset_folder, "metadata", f"CPC1.{dataset}.json")
+        with open(file_path, "r", encoding="utf-8") as fp:
+            scenes = json.load(fp)
         if split == "train":
-            listener_audiograms = json.load(
-                open(
-                    os.path.join(
-                        dataset_folder, "metadata", "listeners.CPC1_train.json"
-                    )
-                )
+            file_path = os.path.join(
+                dataset_folder, "metadata", "listeners.CPC1_train.json"
             )
+            with open(file_path, "r", encoding="utf-8") as fp:
+                listener_audiograms = json.load(fp)
         else:
-            listener_audiograms = json.load(
-                open(
-                    os.path.join(dataset_folder, "metadata", "listeners.CPC1_all.json")
-                )
+            file_path = os.path.join(
+                dataset_folder, "metadata", "listeners.CPC1_all.json"
             )
+            with open(file_path, "r", encoding="utf-8") as fp:
+                listener_audiograms = json.load(fp)
 
         # initialize ear
         ear = Ear(**cfg["MSBGEar"])
@@ -116,7 +112,7 @@ def run_msbg_simulation(cfg, track):
                 f"{outfile_stem}_HL-output.wav",
             ]
             # if all signals to write exist, pass
-            if all([os.path.isfile(f) for f in signal_files_to_write]):
+            if all(os.path.isfile(f) for f in signal_files_to_write):
                 continue
             signal = read_signal(signal_file)
 
@@ -134,10 +130,11 @@ def run_msbg_simulation(cfg, track):
             signals_to_write = [
                 listen(ear, signal, left_audiogram, right_audiogram),
             ]
-            for i in range(len(signals_to_write)):
+
+            for signal, signal_file in zip(signals_to_write, signal_files_to_write):
                 write_signal(
-                    signal_files_to_write[i],
-                    signals_to_write[i],
+                    signal_file,
+                    signal,
                     MSBG_FS,
                     floating_point=True,
                 )
@@ -152,8 +149,8 @@ def generate_data_split(
     if_msbg=False,
     if_ref=False,
 ):
-    with open(orig_data_json, "r") as f:
-        all_data_list = json.load(f)
+    with open(orig_data_json, "r", encoding="utf-8") as fp:
+        all_data_list = json.load(fp)
 
     left_tgt_signal_folder = os.path.join(
         target_data_folder, orig_signal_folder.split("/")[-2] + "_left"
@@ -212,7 +209,9 @@ def generate_data_split(
             wrds = item["prompt"].upper()
 
             utt, orig_fs = sf.read(wav_file)
-            utt_16k = resample(np.array(utt).transpose(), orig_fs, targ_fs).transpose()
+            utt_16k = resample(
+                np.array(utt).transpose(), orig_sr=orig_fs, target_sr=targ_fs
+            ).transpose()
             duration = (
                 len(utt_16k[2 * targ_fs :, 0]) / targ_fs
             )  # Get rid of the first two seconds, as there is no speech
@@ -230,21 +229,21 @@ def generate_data_split(
                 ["right_" + snt_id, str(duration), wav_file_right, spk_id, wrds]
             )
 
-    with open(left_csvfile, mode="w") as csv_f:
+    with open(left_csvfile, mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
         for line in csv_lines_left:
             csv_writer.writerow(line)
 
-    with open(right_csvfile, mode="w") as csv_f:
+    with open(right_csvfile, mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
         for line in csv_lines_right:
             csv_writer.writerow(line)
 
-    with open(binaural_csvfile, mode="w") as csv_f:
+    with open(binaural_csvfile, mode="w", encoding="utf-8") as csv_f:
         csv_writer = csv.writer(
             csv_f, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
         )
@@ -268,17 +267,16 @@ def run_signal_generation_train(cfg, track):
     ]
 
     datasets_to_generate = []
-    for i in range(len(lists_to_generate)):
-        with open(lists_to_generate[i], "r") as f:
-            datasets_to_generate.append(json.load(f))
-            f.close()
+    for list_to_generate in lists_to_generate:
+        with open(list_to_generate, "r", encoding="utf-8") as fp:
+            datasets_to_generate.append(json.load(fp))
 
-    for i in range(len(lists_to_generate)):
+    for list_to_generate, dataset in zip(lists_to_generate, datasets_to_generate):
         # generate_data_split(
         #     train_json_path,
         #     train_signal_folder,
         #     target_folder,
-        #     os.path.basename(lists_to_generate[i]).split("_")[1],
+        #     os.path.basename(list_to_generate).split("_")[1],
         #     datasets_to_generate[i],
         # )
 
@@ -286,8 +284,8 @@ def run_signal_generation_train(cfg, track):
             train_json_path,
             train_signal_folder,
             target_folder,
-            os.path.basename(lists_to_generate[i]).split("_")[1],
-            datasets_to_generate[i],
+            os.path.basename(list_to_generate).split("_")[1],
+            dataset,
             if_msbg=True,
         )
 
@@ -295,8 +293,8 @@ def run_signal_generation_train(cfg, track):
             train_json_path,
             train_signal_folder,
             target_folder,
-            os.path.basename(lists_to_generate[i]).split("_")[1],
-            datasets_to_generate[i],
+            os.path.basename(list_to_generate).split("_")[1],
+            dataset,
             if_ref=True,
         )
 
@@ -342,5 +340,6 @@ def run(cfg: DictConfig) -> None:
     run_signal_generation_test(cfg, track)
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/recipes/cpc1/e032_sheffield/train_asr.py b/recipes/cpc1/e032_sheffield/train_asr.py
index 896b02df7..eac380b43 100644
--- a/recipes/cpc1/e032_sheffield/train_asr.py
+++ b/recipes/cpc1/e032_sheffield/train_asr.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 """
-Recipe for training a Transformer ASR system with librispeech, from the SpeechBrain LibriSpeech/ASR recipe.
-The SpeechBrain version used in this work is:
+Recipe for training a Transformer ASR system with librispeech, from the SpeechBrain
+LibriSpeech/ASR recipe. The SpeechBrain version used in this work is:
 https://github.com/speechbrain/speechbrain/tree/1eddf66eea01866d3cf9dfe61b00bb48d2062236
 """
 
@@ -17,11 +17,22 @@
 
 logger = logging.getLogger(__name__)
 
+tokenizer = None
+
 
 # Define training procedure
 class ASR(sb.core.Brain):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.acc_metric = None
+        self.wer_metric = None
+        self.train_stats = None
+        self.switched = None
+        self.optimizer = None
+        self.tokenizer = None
+
     def compute_forward(self, batch, stage):
-        """Forward computations from the waveform batches to the output probabilities."""
+        """Forward computations from waveform batches to output probabilities."""
         batch = batch.to(self.device)
         wavs, wav_lens = batch.sig
         tokens_bos, _ = batch.tokens_bos
@@ -57,8 +68,8 @@ def compute_forward(self, batch, stage):
             hyps = None
             current_epoch = self.hparams.epoch_counter.current
             if current_epoch % self.hparams.valid_search_interval == 0:
-                # for the sake of efficiency, we only perform beamsearch with limited capacity
-                # and no LM to give user some idea of how the AM is doing
+                # for the sake of efficiency, we only perform beamsearch with limited
+                # capacity and no LM to give user some idea of how the AM is doing
                 hyps, _ = self.hparams.valid_search(enc_out.detach(), wav_lens)
         elif stage == sb.Stage.TEST:
             hyps, _ = self.hparams.test_search(enc_out.detach(), wav_lens)
@@ -67,7 +78,8 @@ def compute_forward(self, batch, stage):
 
     def compute_objectives(self, predictions, batch, stage):
         """Computes the loss (CTC+NLL) given predictions and targets."""
-
+        if self.wer_metric is None or self.acc_metric is None:
+            raise ValueError("wer_metric or acc_metric is None")
         (p_ctc, p_seq, wav_lens, hyps) = predictions
 
         ids = batch.id
@@ -100,6 +112,9 @@ def fit_batch(self, batch):
         """Train the parameters given a single batch in input"""
         # check if we need to switch optimizer
         # if so change the optimizer from Adam to SGD
+        if self.optimizer is None:
+            raise ValueError("optimizer is None")
+
         self.check_and_reset_optimizer()
 
         predictions = self.compute_forward(batch, sb.Stage.TRAIN)
@@ -127,14 +142,16 @@ def evaluate_batch(self, batch, stage):
             loss = self.compute_objectives(predictions, batch, stage=stage)
         return loss.detach()
 
-    def on_stage_start(self, stage, epoch):
+    def on_stage_start(self, stage, epoch=None):
         """Gets called at the beginning of each epoch"""
         if stage != sb.Stage.TRAIN:
             self.acc_metric = self.hparams.acc_computer()
             self.wer_metric = self.hparams.error_rate_computer()
 
-    def on_stage_end(self, stage, stage_loss, epoch):
+    def on_stage_end(self, stage, stage_loss, epoch=None):
         """Gets called at the end of a epoch."""
+        if self.wer_metric is None or self.acc_metric is None:
+            raise ValueError("wer_metric or acc_metric is None")
         # Compute/store important stats
         stage_stats = {"loss": stage_loss}
         if stage == sb.Stage.TRAIN:
@@ -181,8 +198,8 @@ def on_stage_end(self, stage, stage_loss, epoch):
                 stats_meta={"Epoch loaded": self.hparams.epoch_counter.current},
                 test_stats=stage_stats,
             )
-            with open(self.hparams.wer_file, "w") as w:
-                self.wer_metric.write_stats(w)
+            with open(self.hparams.wer_file, "w", encoding="utf-8") as fp:
+                self.wer_metric.write_stats(fp)
 
             # save the averaged checkpoint at the end of the evaluation stage
             # delete the rest of the intermediate checkpoints
@@ -225,7 +242,8 @@ 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
+                # do not reload the weights if training is interrupted right before
+                # stage 2
                 group = current_optimizer.param_groups[0]
                 if "momentum" not in group:
                     return
@@ -272,7 +290,8 @@ def dataio_prepare(hparams):
 
     # We get the tokenizer as we need it to encode the labels when creating
     # mini-batches.
-    tokenizer = hparams["tokenizer"]
+    # (Note, tokenizer is also defined in global space. TODO: fix the design)
+    tokenizer = hparams["tokenizer"]  # pylint: disable=redefined-outer-name
 
     # 2. Define audio pipeline:
     @sb.utils.data_pipeline.takes("wav")
@@ -308,11 +327,11 @@ def text_pipeline(wrd):
     return train_data, valid_data, test_datasets, tokenizer
 
 
-if __name__ == "__main__":
+def main():
     # CLI:
     hparams_file, run_opts, overrides = sb.parse_arguments(sys.argv[1:])
-    with open(hparams_file) as fin:
-        hparams = load_hyperpyyaml(fin, overrides)
+    with open(hparams_file, "r", encoding="utf-8") as fp:
+        hparams = load_hyperpyyaml(fp, overrides)
 
     # If distributed_launch=True then
     # create ddp_group with the right communication protocol
@@ -326,7 +345,7 @@ def text_pipeline(wrd):
     )
 
     # here we create the datasets objects as well as tokenization and encoding
-    train_data, valid_data, test_datasets, tokenizer = dataio_prepare(hparams)
+    train_data, valid_data, test_datasets, _tokenizer = dataio_prepare(hparams)
 
     # We download the pretrained LM from HuggingFace (or elsewhere depending on
     # the path given in the YAML file). The tokenizer is loaded at the same time.
@@ -355,12 +374,17 @@ def text_pipeline(wrd):
     )
 
     # Testing
-    for k in test_datasets.keys():  # keys are test_clean, test_other etc
+    for dataset_key, test_dataset in test_datasets.items():
+        # dataset_keys are test_clean, test_other etc
         asr_brain.hparams.wer_file = os.path.join(
-            hparams["output_folder"], "wer_{}.txt".format(k)
+            hparams["output_folder"], f"wer_{dataset_key}.txt"
         )
         asr_brain.evaluate(
-            test_datasets[k],
+            test_dataset,
             max_key="ACC",
             test_loader_kwargs=hparams["test_dataloader_opts"],
         )
+
+
+if __name__ == "__main__":
+    main()
diff --git a/recipes/cpc1/e032_sheffield/transfromer_cpc1_decoder.py b/recipes/cpc1/e032_sheffield/transformer_cpc1_decoder.py
similarity index 95%
rename from recipes/cpc1/e032_sheffield/transfromer_cpc1_decoder.py
rename to recipes/cpc1/e032_sheffield/transformer_cpc1_decoder.py
index 1c3cc3863..1cd80dbae 100644
--- a/recipes/cpc1/e032_sheffield/transfromer_cpc1_decoder.py
+++ b/recipes/cpc1/e032_sheffield/transformer_cpc1_decoder.py
@@ -38,7 +38,7 @@ class S2SBaseSearcher(torch.nn.Module):
     """
 
     def __init__(self, bos_index, eos_index, min_decode_ratio, max_decode_ratio):
-        super(S2SBaseSearcher, self).__init__()
+        super().__init__()
         self.bos_index = bos_index
         self.eos_index = eos_index
         self.min_decode_ratio = min_decode_ratio
@@ -151,6 +151,9 @@ class S2SGreedySearcher(S2SBaseSearcher):
     greedy decoding approach. See also S2SBaseSearcher().
     """
 
+    # pylint: disable=W0223
+    # This class is intentionally abstract so disable these warnings
+
     def forward(self, enc_states, wav_len):
         enc_lens = torch.round(enc_states.shape[1] * wav_len).int()
         device = enc_states.device
@@ -164,7 +167,7 @@ def forward(self, enc_states, wav_len):
         log_probs_lst = []
         max_decode_steps = int(enc_states.shape[1] * self.max_decode_ratio)
 
-        for t in range(max_decode_steps):
+        for _t in range(max_decode_steps):
             log_probs, memory, _ = self.forward_step(
                 inp_tokens, memory, enc_states, enc_lens
             )
@@ -217,8 +220,11 @@ class S2SRNNGreedySearcher(S2SGreedySearcher):
     >>> hyps, scores = searcher(enc, wav_len)
     """
 
+    # pylint: disable=W0223
+    # Doesn't implement lm_forward_step() or reset_lm_mem()
+
     def __init__(self, embedding, decoder, linear, **kwargs):
-        super(S2SRNNGreedySearcher, self).__init__(**kwargs)
+        super().__init__(**kwargs)
         self.emb = embedding
         self.dec = decoder
         self.fc = linear
@@ -274,7 +280,8 @@ class S2SBeamSearcher(S2SBaseSearcher):
     coverage_penalty: float
         The coefficient of coverage penalty (η).
         log P(y|x) + λ log P_LM(y) + γ*len(y) + η*coverage(x,y). (default: 0.0)
-        Reference: https://arxiv.org/pdf/1612.02695.pdf, https://arxiv.org/pdf/1808.10792.pdf
+        Reference: https://arxiv.org/pdf/1612.02695.pdf
+            https://arxiv.org/pdf/1808.10792.pdf
     lm_weight : float
         The weight of LM when performing beam search (λ).
         log P(y|x) + λ log P_LM(y). (default: 0.0)
@@ -288,7 +295,8 @@ class S2SBeamSearcher(S2SBaseSearcher):
         CTC prefix scoring on "partial" token or "full: token.
     ctc_window_size: int
         Default: 0
-        Compute the ctc scores over the time frames using windowing based on attention peaks.
+        Compute the ctc scores over the time frames using windowing based on
+            attention peaks.
         If 0, no windowing applied.
     using_max_attn_shift: bool
         Whether using the max_attn_shift constraint. (default: False)
@@ -326,10 +334,9 @@ def __init__(
         max_attn_shift=60,
         minus_inf=-1e20,
     ):
-        super(S2SBeamSearcher, self).__init__(
-            bos_index, eos_index, min_decode_ratio, max_decode_ratio
-        )
+        super().__init__(bos_index, eos_index, min_decode_ratio, max_decode_ratio)
         self.beam_size = beam_size
+        self.beam_offset = None
         self.topk = topk
         self.return_log_probs = return_log_probs
         self.length_normalization = length_normalization
@@ -359,7 +366,8 @@ def __init__(
         if self.ctc_weight > 0.0:
             if len({self.bos_index, self.eos_index, self.blank_index}) < 3:
                 raise ValueError(
-                    "To perform joint ATT/CTC decoding, set blank, eos and bos to different indexes."
+                    "To perform joint ATT/CTC decoding, set blank, eos and bos to "
+                    "different indexes."
                 )
 
         # ctc already initialized
@@ -374,7 +382,8 @@ def _check_full_beams(self, hyps, beam_size):
         ---------
         hyps : List
             This list contains batch_size number.
-            Each inside list contains a list stores all the hypothesis for this sentence.
+            Each inside list contains a list storing all the hypothesis
+                for this sentence.
         beam_size : int
             The number of beam_size.
 
@@ -384,11 +393,8 @@ def _check_full_beams(self, hyps, beam_size):
             Whether the hyps has been full.
         """
         hyps_len = [len(lst) for lst in hyps]
-        beam_size = [self.beam_size for _ in range(len(hyps_len))]
-        if hyps_len == beam_size:
-            return True
-        else:
-            return False
+        beam_size = [self.beam_size] * len(hyps_len)
+        return hyps_len == beam_size
 
     def _check_attn_shift(self, attn, prev_attn_peak):
         """This method checks whether attention shift is more than attn_shift.
@@ -484,15 +490,17 @@ def _update_hyp_and_scores(
                 hyps_and_scores[batch_id].append((hyp, log_probs, final_scores))
         return is_eos
 
-    def _get_top_score_prediction(self, hyps_and_scores, topk):
-        """This method sorts the scores and return corresponding hypothesis and log probs.
+    # pylint: disable=W0613
+    def _get_top_score_prediction(self, all_hyps_and_scores, topk):
+        """Sort the scores and return corresponding hypothesis and log probs.
 
         Arguments
         ---------
-        hyps_and_scores : list
+        all_hyps_and_scores : list
             To store generated hypotheses and scores.
         topk : int
             Number of hypothesis to return.
+            Deprecated, use self.topk instead.
 
         Returns
         -------
@@ -505,12 +513,13 @@ def _get_top_score_prediction(self, hyps_and_scores, topk):
         topk_log_probs : list
             The log probabilities of each hypotheses.
         """
+
         top_hyps, top_log_probs, top_scores, top_lengths = [], [], [], []
-        batch_size = len(hyps_and_scores)
+        batch_size = len(all_hyps_and_scores)
 
         # Collect hypotheses
-        for i in range(len(hyps_and_scores)):
-            hyps, log_probs, scores = zip(*hyps_and_scores[i])
+        for hyps_and_scores in all_hyps_and_scores:
+            hyps, log_probs, scores = zip(*hyps_and_scores)
             top_hyps += hyps
             top_scores += scores
             top_log_probs += log_probs
@@ -579,7 +588,7 @@ def forward(self, enc_states, wav_len):  # noqa: C901
         # keep only the first to make sure no redundancy.
         sequence_scores.index_fill_(0, self.beam_offset, 0.0)
 
-        # keep the hypothesis that reaches eos and their corresponding score and log_probs.
+        # keep the hypothesis that reaches eos and corresponding score and log_probs.
         hyps_and_scores = [[] for _ in range(batch_size)]
 
         # keep the sequences that still not reaches eos.
@@ -673,7 +682,7 @@ def forward(self, enc_states, wav_len):  # noqa: C901
             if self.length_normalization:
                 sequence_scores = sequence_scores * (t + 1)
 
-            # The index of which beam the current top-K output came from in (t-1) timesteps.
+            # The index of beam the current top-K output came from in (t-1) timesteps.
             predecessors = (
                 torch.div(candidates, vocab_size, rounding_mode="floor")
                 + self.beam_offset.unsqueeze(1).expand_as(candidates)
@@ -687,7 +696,7 @@ def forward(self, enc_states, wav_len):  # noqa: C901
             if self.ctc_weight > 0:
                 ctc_memory = ctc_scorer.permute_mem(ctc_memory, candidates)
 
-            # If using_max_attn_shift, then the previous attn peak has to be permuted too.
+            # If using_max_attn_shift, then previous attn peak has to be permuted too.
             if self.using_max_attn_shift:
                 prev_attn_peak = torch.index_select(
                     prev_attn_peak, dim=0, index=predecessors
@@ -702,9 +711,10 @@ def forward(self, enc_states, wav_len):  # noqa: C901
                     # Init coverage
                     self.coverage = cur_attn
 
-                # the attn of transformer is [batch_size*beam_size, current_step, source_len]
+                # the attn of transformer is
+                #   [batch_size*beam_size, current_step, source_len]
                 if len(cur_attn.size()) > 2:
-                    self.converage = torch.sum(cur_attn, dim=1)
+                    self.coverage = torch.sum(cur_attn, dim=1)
                 else:
                     # Update coverage
                     self.coverage = torch.index_select(
@@ -773,7 +783,7 @@ def forward(self, enc_states, wav_len):  # noqa: C901
         (
             topk_hyps,
             topk_scores,
-            topk_lengths,
+            _topk_lengths,
             log_probs,
         ) = self._get_top_score_prediction(hyps_and_scores, topk=self.topk)
         # pick the best hyp
@@ -782,13 +792,13 @@ def forward(self, enc_states, wav_len):  # noqa: C901
 
         if self.return_log_probs:
             return predictions, topk_scores, log_probs
-        else:
-            return (
-                predictions,
-                topk_scores,
-                dec_outputs[0, :, :],
-                torch.exp(log_probs[0]),
-            )
+
+        return (
+            predictions,
+            topk_scores,
+            dec_outputs[0, :, :],
+            torch.exp(log_probs[0]),
+        )
 
     def ctc_forward_step(self, x):
         logits = self.ctc_fc(x)
@@ -876,10 +886,13 @@ class S2SRNNBeamSearcher(S2SBeamSearcher):
     >>> hyps, scores = searcher(enc, wav_len)
     """
 
+    # pylint: disable=W0223
+    # Doesn't implement lm_forward_step(), permute_lm_mem() or reset_lm_mem()
+
     def __init__(
         self, embedding, decoder, linear, ctc_linear=None, temperature=1.0, **kwargs
     ):
-        super(S2SRNNBeamSearcher, self).__init__(**kwargs)
+        super().__init__(**kwargs)
         self.emb = embedding
         self.dec = decoder
         self.fc = linear
@@ -977,7 +990,7 @@ class S2SRNNBeamSearchLM(S2SRNNBeamSearcher):
     def __init__(
         self, embedding, decoder, linear, language_model, temperature_lm=1.0, **kwargs
     ):
-        super(S2SRNNBeamSearchLM, self).__init__(embedding, decoder, linear, **kwargs)
+        super().__init__(embedding, decoder, linear, **kwargs)
 
         self.lm = language_model
         self.lm.eval()
@@ -1063,9 +1076,7 @@ class S2SRNNBeamSearchTransformerLM(S2SRNNBeamSearcher):
     def __init__(
         self, embedding, decoder, linear, language_model, temperature_lm=1.0, **kwargs
     ):
-        super(S2SRNNBeamSearchTransformerLM, self).__init__(
-            embedding, decoder, linear, **kwargs
-        )
+        super().__init__(embedding, decoder, linear, **kwargs)
 
         self.lm = language_model
         self.lm.eval()
@@ -1121,7 +1132,7 @@ def inflate_tensor(tensor, times, dim):
 
 
 def mask_by_condition(tensor, cond, fill_value):
-    """This function will mask some element in the tensor with fill_value, if condition=False.
+    """Mask some element in the tensor with fill_value, if condition=False.
 
     Arguments
     ---------
@@ -1187,7 +1198,7 @@ class S2STransformerBeamSearch(S2SBeamSearcher):
     """
 
     def __init__(self, modules, temperature=1.0, temperature_lm=1.0, **kwargs):
-        super(S2STransformerBeamSearch, self).__init__(**kwargs)
+        super().__init__(**kwargs)
 
         self.model = modules[0]
         self.fc = modules[1]
diff --git a/recipes/cpc2/baseline/predict.py b/recipes/cpc2/baseline/predict.py
index 462c9dbfe..5adff89a4 100644
--- a/recipes/cpc2/baseline/predict.py
+++ b/recipes/cpc2/baseline/predict.py
@@ -87,13 +87,13 @@ def predict(cfg: DictConfig):
     records_df = pd.DataFrame(records)
 
     # make predictions for each item in the data
-    for i, record in records_df.iterrows():
+    for i, _record in records_df.iterrows():
         test_df = records_df.iloc[[i]].copy()
 
-        # The prediction is made using a logistic mapping from HASPI scores to intelligibility
-        # It is important that this mapping is trained using a disjoint set of data
-        # i.e. we define a training data set that does not contain systems, listeners or signals
-        # that appear in the test data sample.
+        # The prediction is made using a logistic mapping from HASPI scores to
+        # intelligibility. It is important that this mapping is trained using a
+        # disjoint set of data, i.e. we define a training data set that does not
+        # contain systems, listeners or signals that appear in the test data sample.
 
         train_df = make_disjoint_train_set(records_df, test_df)
 
diff --git a/recipes/icassp_2023/baseline/enhance.py b/recipes/icassp_2023/baseline/enhance.py
index bd840cd5d..c8e62d3d5 100644
--- a/recipes/icassp_2023/baseline/enhance.py
+++ b/recipes/icassp_2023/baseline/enhance.py
@@ -5,7 +5,7 @@
 
 import hydra
 import numpy as np
-from evaluate import make_scene_listener_list
+from evaluate import make_scene_listener_list  # pylint: disable=E0401
 from omegaconf import DictConfig
 from scipy.io import wavfile
 from tqdm import tqdm
@@ -23,6 +23,7 @@ def enhance(cfg: DictConfig) -> None:
     with open(cfg.path.scenes_listeners_file, "r", encoding="utf-8") as fp:
         scenes_listeners = json.load(fp)
 
+    # pylint: disable=unused-variable
     with open(cfg.path.listeners_file, "r", encoding="utf-8") as fp:
         listener_audiograms = json.load(fp)  # noqa: F841
 
@@ -59,5 +60,6 @@ def enhance(cfg: DictConfig) -> None:
         )
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     enhance()
diff --git a/recipes/icassp_2023/baseline/evaluate.py b/recipes/icassp_2023/baseline/evaluate.py
index 745decb92..c99eca8ee 100644
--- a/recipes/icassp_2023/baseline/evaluate.py
+++ b/recipes/icassp_2023/baseline/evaluate.py
@@ -174,5 +174,6 @@ def run_calculate_si(cfg: DictConfig) -> None:
         )
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
-    run_calculate_si()  # noqa
+    run_calculate_si()
diff --git a/recipes/icassp_2023/baseline/report_score.py b/recipes/icassp_2023/baseline/report_score.py
index faace37ff..77c8e98ff 100644
--- a/recipes/icassp_2023/baseline/report_score.py
+++ b/recipes/icassp_2023/baseline/report_score.py
@@ -4,7 +4,7 @@
 
 import hydra
 import pandas as pd
-from evaluate import make_scene_listener_list
+from evaluate import make_scene_listener_list  # pylint: disable=E0401
 from omegaconf import DictConfig
 
 logger = logging.getLogger(__name__)
@@ -40,5 +40,6 @@ def report_score(cfg: DictConfig) -> None:
         print(selected_results_df[["haspi", "hasqi", "combined"]].mean(axis=0))
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     report_score()
diff --git a/setup.cfg b/setup.cfg
index 04a9269a0..991e8d987 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -34,6 +34,7 @@ packages = find:
 
 install_requires =
   audioread>=2.1.9
+  fastdtw
   gdown
   hydra-core==1.1.1
   hydra-submitit-launcher>=1.1.6
@@ -44,11 +45,14 @@ install_requires =
   omegaconf>=2.1.1
   pandas>=1.3.5
   pyloudnorm>=0.1.0
+  pytorch-lightning
   pystoi
+  resampy
   scikit-learn>=1.0.2
   scipy>=1.7.3
   SoundFile>=0.10.3.post1
-  torch
+  speechbrain
+  torch<2.0
   torchaudio
   tqdm>=4.62.3
   typing_extensions
@@ -86,6 +90,7 @@ dev =
   pre-commit
   pycodestyle
   pylint
+  pylint-pytest
   pytest
   pytest-cov
   pytest-regtest
diff --git a/setup.py b/setup.py
index 681075881..530a0572f 100644
--- a/setup.py
+++ b/setup.py
@@ -3,6 +3,6 @@
 from setuptools import setup
 
 setup(
-    version=versioneer.get_version(),
-    cmdclass=versioneer.get_cmdclass(),
+    version=versioneer.get_version(),  # pylint: disable=no-member
+    cmdclass=versioneer.get_cmdclass(),  # pylint: disable=no-member
 )


Test patch:
diff --git a/recipes/cec1/e009_sheffield/test.py b/recipes/cec1/e009_sheffield/test.py
index 59b47fccd..ce5f799b1 100644
--- a/recipes/cec1/e009_sheffield/test.py
+++ b/recipes/cec1/e009_sheffield/test.py
@@ -84,5 +84,6 @@ def run(cfg: DictConfig) -> None:
             )
 
 
+# pylint: disable=no-value-for-parameter
 if __name__ == "__main__":
     run()
diff --git a/tests/data/test_HOA_tools_cec2.py b/tests/data/test_HOA_tools_cec2.py
index 76d66bc1a..e1a5b6983 100644
--- a/tests/data/test_HOA_tools_cec2.py
+++ b/tests/data/test_HOA_tools_cec2.py
@@ -2,18 +2,20 @@
 import numpy as np
 import pytest
 
-from clarity.data.HOA_tools_cec2 import (  # HOARotator,; ambisonic_convolve,; binaural_mixdown,; compute_band_rotation,; compute_rotation_matrix,;  dot,; dot,
+# HOARotator,; ambisonic_convolve,; binaural_mixdown,;;;
+# compute_band_rotation,;; compute_rotation_matrix,;  dot,; dot,
+from clarity.data.HOA_tools_cec2 import (
     P,
     U,
     V,
     W,
     centred_element,
     compute_rms,
+    compute_rotation_vector,
     compute_UVW_coefficients,
     dB_to_gain,
     equalise_rms_levels,
     rotation_control_vector,
-    rotation_vector,
     smoothstep,
 )
 
@@ -39,7 +41,8 @@ def test_centred_element(
 
 
 def test_centred_element_index_error(random_matrix: np.ndarray) -> None:
-    """Test centered_element() raises an IndexError if centering is outside of matrix dimensions."""
+    """Test that centered_element() raises an IndexError if centering is outside
+    of matrix dimensions."""
     with pytest.raises(IndexError):
         centred_element(random_matrix, row=101, col=2)
     with pytest.raises(IndexError):
@@ -58,7 +61,6 @@ def test_P(
     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,
@@ -73,7 +75,6 @@ def test_P(
 
 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, order=1, rotation_matrices=[random_matrix])
     with pytest.raises(IndexError):
@@ -158,7 +159,8 @@ def test_W(
         (0, 2, 2, (0.5773502691896257, -0.28867513459481287, -0.0)),
         (1, 2, 2, (0.5, 0.3535533905932738, -0.0)),
         (-1, 2, 3, (1.2649110640673518, 0.7745966692414834, -0.31622776601683794)),
-        # (-1, 2, 1, (-0.0, np.nan, -0.0)),  # FixMe : This is probably going to cause problems how to capture?
+        # (-1, 2, 1, (-0.0, np.nan, -0.0)),
+        # TODO : This is probably going to cause problems how to capture?
     ],
 )
 def test_compute_UVW_coefficients(
@@ -174,24 +176,28 @@ def test_compute_UVW_coefficients_zero_division_error() -> None:
         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
+# TODO : Not yet working, I don't understand how to get `output` passed in correctly
 # @pytest.mark.parametrize(
 #     "el, output, expected",
 #     [
 #         (2, np.asarray([0, 0]), np.asarray([[1, 2], [3, 4]])),
 #     ],
 # )
-# def test_compute_band_rotation(el: int, output: np.ndarray, random_matrix: np.ndarray, expected: np.ndarray) -> None:
+# def test_compute_band_rotation(el: int, output: np.ndarray,
+#   random_matrix: np.ndarray, expected: np.ndarray) -> None:
 #     """Test for compute_band_rotation() function."""
-#     np.testing.assert_array_equal(compute_band_rotation(el, [random_matrix, random_matrix], output), expected)
+#     np.testing.assert_array_equal(compute_band_rotation(el,
+# [random_matrix, random_matrix], output), expected)
 
 
-# FixMe : Not working yet, results in a typing error???
+# TODO : Not working yet, results in a typing error???
 # @pytest.mark.parametrize(
 #     "A, B, expected",
 #     [
-#         (np.asarray([[1, 2], [3, 4]]), np.asarray([[4, 3], [2, 1]]), np.asarray([[1, 2], [3, 4]])),
-#         (np.asarray([[5, 6], [7, 8]]), np.asarray([[8, 7], [6, 5]]), np.asarray([[1, 2], [3, 4]])),
+#         (np.asarray([[1, 2], [3, 4]]), np.asarray([[4, 3], [2, 1]]),
+#               np.asarray([[1, 2], [3, 4]])),
+#         (np.asarray([[5, 6], [7, 8]]), np.asarray([[8, 7], [6, 5]]),
+#               np.asarray([[1, 2], [3, 4]])),
 #     ],
 # )
 # def test_dot(A: np.ndarray, B: np.ndarray, expected: np.ndarray) -> None:
@@ -204,7 +210,7 @@ def test_HOARotator() -> None:
     assert True
 
 
-# FixMe : need examples of hrir_metadata dictionary to be able to write a tests for this
+# TODO : 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
@@ -327,7 +333,8 @@ def test_rotation_control_vector(
 def test_rotation_control_vector_value_error(
     array_length: int, start_idx: int, end_idx: int
 ) -> None:
-    """Test rotation_control_vector() raises ValueError if start_idx > end_idx or end_idx > array_length."""
+    """Test rotation_control_vector() raises ValueError if start_idx > end_idx or
+    end_idx > array_length."""
     with pytest.raises(ValueError):
         rotation_control_vector(array_length, start_idx, end_idx)
 
@@ -363,7 +370,7 @@ def test_rotation_control_vector_value_error(
         ),
     ],
 )
-def test_rotation_vector(
+def test_compute_rotation_vector(
     start_angle: float,
     end_angle: float,
     signal_length: int,
@@ -373,14 +380,17 @@ def test_rotation_vector(
 ) -> None:
     """Test for rotation_vector() function."""
     np.testing.assert_array_equal(
-        rotation_vector(start_angle, end_angle, signal_length, start_idx, end_idx),
+        compute_rotation_vector(
+            start_angle, end_angle, signal_length, start_idx, end_idx
+        ),
         expected,
     )
 
 
-def test_rotation_vector_floating_point_error() -> None:
-    """Test rotation_vector() raises FloatingPointError if signal_length is zero function."""
+def test_compute_rotation_vector_floating_point_error() -> None:
+    """Test rotation_vector() raises FloatingPointError if signal_length is
+    zero function."""
     with pytest.raises(FloatingPointError):
-        rotation_vector(
+        compute_rotation_vector(
             start_angle=10, end_angle=20, signal_length=0, start_idx=2, end_idx=8
         )
diff --git a/tests/data/test_utils.py b/tests/data/test_utils.py
index 1fa629108..54da9597f 100644
--- a/tests/data/test_utils.py
+++ b/tests/data/test_utils.py
@@ -141,5 +141,5 @@ def test_pad(signal: np.ndarray, length: int, expected: np.ndarray) -> None:
 
 def test_pad_invalid_length_assertion_error() -> None:
     """Test pad() raises an exception when length < signal.shape[0]."""
-    with pytest.raises(AssertionError):
+    with pytest.raises(ValueError):
         pad(signal=np.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), length=2)
diff --git a/tests/recipes/cad1/task1/test_enhance.py b/tests/recipes/cad1/task1/test_enhance.py
index 80aa13ca9..15008a010 100644
--- a/tests/recipes/cad1/task1/test_enhance.py
+++ b/tests/recipes/cad1/task1/test_enhance.py
@@ -19,7 +19,7 @@
 )
 
 BASE_DIR = Path.cwd()
-RESOURCES = BASE_DIR / "tests" / "resources"
+RESOURCES = BASE_DIR / "tests" / "resources" / "recipes" / "cad1" / "task1"
 
 
 def test_map_to_dict():
@@ -64,8 +64,9 @@ def test_decompose_signal():
         RESOURCES / "test_enhance.test_decompose_signal.npy",
         allow_pickle=True,
     )[()]
-    for key in output.keys():
-        np.testing.assert_array_almost_equal(output[key], expected_results[key])
+
+    for key, item in output.items():
+        np.testing.assert_array_almost_equal(item, expected_results[key])
 
 
 def test_apply_baseline_ha():
@@ -110,7 +111,7 @@ def test_process_stems_for_listener():
         threshold=0.35, attenuation=0.1, attack=50, release=1000, rms_buffer_size=0.064
     )
 
-    # Call the process_stems_for_listener function and check that the output is as expected
+    # Call the process_stems_for_listener function and check output is as expected
     output = process_stems_for_listener(
         stems, enhancer, compressor, audiogram_left, audiogram_right, cfs
     )
@@ -118,8 +119,9 @@ def test_process_stems_for_listener():
         RESOURCES / "test_enhance.test_process_stems_for_listener.npy",
         allow_pickle=True,
     )[()]
-    for key in output.keys():
-        np.testing.assert_array_almost_equal(output[key], expected_results[key])
+
+    for key, item in output.items():
+        np.testing.assert_array_almost_equal(item, expected_results[key])
 
 
 def test_separate_sources():
diff --git a/tests/recipes/cad1/task1/test_evaluate.py b/tests/recipes/cad1/task1/test_evaluate.py
index f99b510a4..803cf506d 100644
--- a/tests/recipes/cad1/task1/test_evaluate.py
+++ b/tests/recipes/cad1/task1/test_evaluate.py
@@ -116,7 +116,7 @@ def test_make_song_listener_list():
 def test_evaluate_song_listener(
     song, listener, config, split_dir, listener_audiograms, expected_results, tmp_path
 ):
-    """Test the function _evaluate_song_listener returns the correct results given the input"""
+    """Test the function _evaluate_song_listener returns correct results given input"""
     np.random.seed(2023)
 
     # Generate reference and enhanced wav files
diff --git a/tests/recipes/cad1/task2/__init__.py b/tests/recipes/cad1/task2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/recipes/cad1/task2/test_audio_manager.py b/tests/recipes/cad1/task2/test_audio_manager.py
new file mode 100644
index 000000000..e78b4aa0f
--- /dev/null
+++ b/tests/recipes/cad1/task2/test_audio_manager.py
@@ -0,0 +1,33 @@
+"""Test for AudioManager module"""
+# pylint: disable=import-error
+
+from pathlib import Path
+
+import numpy as np
+from scipy.io import wavfile
+
+from recipes.cad1.task2.baseline.audio_manager import AudioManager
+
+
+def test_save_audios(tmp_path):
+    """Test save_audios method."""
+    np.random.seed(42)
+    # Initialize an audio manager with temporary directory as output audio path
+    audio_manager = AudioManager(output_audio_path=tmp_path.as_posix())
+
+    # Create sample audio data
+    audio_data = np.random.randn(2, 44100)
+
+    # Add audio data to audio manager
+    audio_manager.add_audios_to_save("test_audio", audio_data)
+
+    # Save audio
+    audio_manager.save_audios()
+
+    # Check if audio file was saved
+    audio_file = Path(tmp_path) / "test_audio.wav"
+    assert audio_file.is_file()
+
+    # Check if audio data was saved correctly
+    sample_rate, _ = wavfile.read(audio_file)
+    assert sample_rate == audio_manager.sample_rate
diff --git a/tests/recipes/cad1/task2/test_baseline_utils.py b/tests/recipes/cad1/task2/test_baseline_utils.py
new file mode 100644
index 000000000..2ac5220ee
--- /dev/null
+++ b/tests/recipes/cad1/task2/test_baseline_utils.py
@@ -0,0 +1,43 @@
+"""Test for baseline_utils.py"""
+# pylint: disable=import-error
+
+from pathlib import Path
+
+import librosa
+import numpy as np
+from omegaconf import OmegaConf
+
+from recipes.cad1.task2.baseline.baseline_utils import (
+    load_listeners_and_scenes,
+    read_mp3,
+)
+
+BASE_DIR = Path.cwd()
+RESOURCES = BASE_DIR / "tests" / "resources" / "recipes" / "cad1" / "task2"
+
+
+def test_read_mp3():
+    """Test read_mp3()"""
+    signal, sample_rate = read_mp3(librosa.example("brahms"))
+    assert isinstance(signal, np.ndarray)
+    assert isinstance(sample_rate, int)
+
+
+def test_load_listeners_and_scenes():
+    """Test load_listeners_and_scenes()"""
+    config = OmegaConf.create(
+        {
+            "path": {
+                "scenes_file": (RESOURCES / "scenes.json").as_posix(),
+                "listeners_train_file": (RESOURCES / "listeners.json").as_posix(),
+                "scenes_listeners_file": (
+                    RESOURCES / "scenes_listeners.json"
+                ).as_posix(),
+            },
+            "evaluate": {"split": "train"},
+        }
+    )
+    scenes, listener_audiograms, scene_listeners = load_listeners_and_scenes(config)
+    assert isinstance(scenes, dict)
+    assert isinstance(listener_audiograms, dict)
+    assert isinstance(scene_listeners, dict)
diff --git a/tests/recipes/cad1/task2/test_build_scene_metadata.py b/tests/recipes/cad1/task2/test_build_scene_metadata.py
new file mode 100644
index 000000000..578e74bba
--- /dev/null
+++ b/tests/recipes/cad1/task2/test_build_scene_metadata.py
@@ -0,0 +1,70 @@
+"""Tests for the build_scene_metadata.py script"""
+# pylint: disable=import-error
+
+from pathlib import Path
+
+import numpy as np
+import pytest
+
+from recipes.cad1.task2.data_preparation.build_scene_metadata import (
+    get_random_car_params,
+    get_random_head_rotation,
+    read_json,
+    set_seed,
+)
+
+BASE_DIR = Path.cwd()
+RESOURCES = BASE_DIR / "tests" / "resources" / "recipes" / "cad1" / "task2"
+
+
+def test_set_seed():
+    """Test that the seed is set correctly"""
+    seed = 123
+    set_seed(seed)
+    np.testing.assert_array_almost_equal(np.random.rand(), 0.6964691855978616)
+
+
+def test_get_random_head_rotation():
+    """Test that the function returns a random item from the dictionary"""
+    input_dict = {"1": "value1", "2": "value2", "3": "value3"}
+    np.random.seed(42)
+    random_item = get_random_head_rotation(input_dict)
+    assert random_item == 1
+
+
+def test_get_random_car_params():
+    """Test that the function returns a dictionary with the expected keys"""
+    np.random.seed(42)
+    car_params = get_random_car_params()
+    assert car_params == {
+        "bump": {"btype": "bandpass", "cutoff_hz": [94, 187], "order": 1},
+        "dip_high": {"btype": "highpass", "cutoff_hz": 128, "order": 2},
+        "dip_low": {"btype": "lowpass", "cutoff_hz": 60, "order": 2},
+        "engine_num_harmonics": 20,
+        "gear": 5,
+        "primary_filter": {"btype": "lowpass", "cutoff_hz": 16.507, "order": 1},
+        "reference_level_db": 31.4,
+        "rpm": 2060.4,
+        "secondary_filter": {
+            "btype": "lowpass",
+            "cutoff_hz": 305.51040000000006,
+            "order": 2,
+        },
+        "speed": 101.0,
+    }
+
+
+@pytest.mark.parametrize(
+    "json_file, expected",
+    [
+        (
+            RESOURCES / "test_build_scene_metadata.json_sample.json",
+            {"key1": "value1", "key2": "value2"},
+        ),
+    ],
+)
+def test_read_json(json_file, expected):
+    """Test that the function reads the json file correctly"""
+    output = read_json(json_file.as_posix())
+
+    assert output == expected
diff --git a/tests/recipes/cad1/task2/test_enhance.py b/tests/recipes/cad1/task2/test_enhance.py
new file mode 100644
index 000000000..17ef5f492
--- /dev/null
+++ b/tests/recipes/cad1/task2/test_enhance.py
@@ -0,0 +1,56 @@
+"""Test the enhance module."""
+# pylint: disable=import-error
+
+from pathlib import Path
+
+import numpy as np
+import pyloudnorm as pyln
+from omegaconf import DictConfig
+
+from recipes.cad1.task2.baseline.enhance import enhance_song
+
+BASE_DIR = Path.cwd()
+RESOURCES = BASE_DIR / "tests" / "resources" / "recipes" / "cad1" / "task2"
+
+
+def test_enhance_song():
+    """Test the enhance_song function."""
+    np.random.seed(42)
+
+    # Set the sample rate and gain
+    duration = 0.5
+
+    config = DictConfig(
+        {
+            "sample_rate": 16000,
+            "enhance": {"min_level": -11, "max_level": -19, "average_level": -14},
+        }
+    )
+    listener = {
+        "audiogram_levels_l": [20, 30, 35, 45, 50, 60, 65, 60],
+        "audiogram_levels_r": [20, 30, 35, 45, 50, 60, 65, 60],
+        "audiogram_cfs": [250, 500, 1000, 2000, 3000, 4000, 6000, 8000],
+    }
+
+    # Create a sample waveform
+    waveform = np.random.rand(2, int(config.sample_rate * duration))
+
+    # Call the function
+    out_left, out_right = enhance_song(waveform, listener, config)
+
+    expected_left = np.load(
+        RESOURCES / "test_enhance.enhance_song_left.npy", allow_pickle=True
+    )
+    expected_right = np.load(
+        RESOURCES / "test_enhance.enhance_song_right.npy", allow_pickle=True
+    )
+
+    # Check that the output is not equal to the input
+    np.testing.assert_array_almost_equal(out_left, expected_left)
+    np.testing.assert_array_almost_equal(out_right, expected_right)
+
+    # Check that the output has the correct loudness
+    meter = pyln.Meter(config.sample_rate)
+
+    out_loudness = meter.integrated_loudness(np.array([out_left, out_right]).T)
+    assert np.isclose(out_loudness, -14, atol=0.1)
diff --git a/tests/regression/test_data_HOA_tools_cec2.py b/tests/regression/test_data_HOA_tools_cec2.py
index 2870dc25e..c9a90f56d 100644
--- a/tests/regression/test_data_HOA_tools_cec2.py
+++ b/tests/regression/test_data_HOA_tools_cec2.py
@@ -1,5 +1,5 @@
 import numpy as np
-from numba.typed import List
+from numba.typed import List  # pylint: disable=no-name-in-module
 from scipy.spatial.transform import Rotation as R
 
 from clarity.data import HOA_tools_cec2 as hoa
@@ -85,8 +85,7 @@ def test_compute_band_rotation(regtest):
 
     sub_matrices = [np.eye(i * 2 + 1) for i in np.arange(n + 1)]
     sub_matrices[1] = foa_rotmat
-    typed_sub_matrices = List()
-    [typed_sub_matrices.append(x) for x in sub_matrices]
+    typed_sub_matrices = sub_matrices.copy()
 
     if n > 1:
         for i in np.arange(2, n + 1):
diff --git a/tests/regression/test_enhancers.py b/tests/regression/test_enhancers.py
index ac209788f..ad0071b3e 100644
--- a/tests/regression/test_enhancers.py
+++ b/tests/regression/test_enhancers.py
@@ -4,7 +4,7 @@
 import numpy as np
 import torch
 
-from clarity.enhancer.dsp import filter
+from clarity.enhancer.dsp import filter  # pylint: disable=redefined-builtin
 from clarity.enhancer.gha.audiogram import Audiogram
 from clarity.enhancer.gha.gha_interface import GHAHearingAid as gha
 from clarity.enhancer.gha.gha_utils import format_gaintable, get_gaintable
@@ -41,7 +41,8 @@ def test_gha_audiogram(regtest):
         levels_r = np.round(np.log10(np.random.rand(6) / 20) * (-i), 0)
         ag = Audiogram(levels_l, levels_r, cfs)
         regtest.write(
-            f"Audiogram original: \n{ag.cfs}\n{ag.levels_l}\n{ag.levels_r}\n{ag.severity}\n"
+            "Audiogram original: \n"
+            f"{ag.cfs}\n{ag.levels_l}\n{ag.levels_r}\n{ag.severity}\n"
         )
         ag = ag.select_subset_of_cfs(np.array([500, 1000, 2000]))
         regtest.write(
@@ -55,7 +56,7 @@ def test_GHA_inputs(regtest):
     infile_names = [
         f"tests/test_data/scenes/S06001_mixed_CH{ch}.wav" for ch in range(1, 4)
     ]
-    fd_merged, merged_filename = tempfile.mkstemp(
+    _fd_merged, merged_filename = tempfile.mkstemp(
         prefix="clarity-merged-", suffix=".wav"
     )
     enhancer.create_HA_inputs(infile_names, merged_filename)
@@ -67,7 +68,8 @@ def test_GHA_inputs(regtest):
 
 def test_GHA_config(regtest):
     enhancer = gha(**gha_params)
-    listeners = json.load(open("tests/test_data/metadata/listeners.json"))
+    with open("tests/test_data/metadata/listeners.json", "r", encoding="utf-8") as fp:
+        listeners = json.load(fp)
     listener = listeners["L0001"]
     cfs = np.array(listener["audiogram_cfs"], dtype="int")
     audiogram_left = np.array(listener["audiogram_levels_l"])
diff --git a/tests/regression/test_evaluators_msbg.py b/tests/regression/test_evaluators_msbg.py
index f200685ed..ef106061e 100644
--- a/tests/regression/test_evaluators_msbg.py
+++ b/tests/regression/test_evaluators_msbg.py
@@ -9,9 +9,13 @@
     pad,
 )
 
+# pylint: disable=W0613
+# pylint false positives due to fixtures. pylint-pytest does not seem to work :(
 
-@pytest.fixture
-def use_numpy():
+
+@pytest.fixture(name="use_numpy")
+def fixture_use_numpy():
+    """Set numpy seed and print options for each test"""
     np.random.seed(0)
     np_print_opts = np.get_printoptions()
     np.set_printoptions(precision=5, threshold=1000)
diff --git a/tests/regression/test_mc_conv_tasnet.py b/tests/regression/test_mc_conv_tasnet.py
index 54ef2dec9..5ffa9b9b6 100644
--- a/tests/regression/test_mc_conv_tasnet.py
+++ b/tests/regression/test_mc_conv_tasnet.py
@@ -56,7 +56,7 @@ def test_convtasnet(regtest):
     cfg["test_loader"] = {
         "batch_size": 1,
         "shuffle": False,
-        "num_workers": 0,  # Overhead of multiprocessing not worth it for tiny test dataset
+        "num_workers": 0,  # Overhead of multiprocessing not worth it for tiny dataset
     }
     cfg = OmegaConf.create(cfg)
     device = "cuda" if torch.cuda.is_available() else None
diff --git a/tests/resources/test_enhance.test_apply_baseline_ha.npy b/tests/resources/recipes/cad1/task1/test_enhance.test_apply_baseline_ha.npy
similarity index 100%
rename from tests/resources/test_enhance.test_apply_baseline_ha.npy
rename to tests/resources/recipes/cad1/task1/test_enhance.test_apply_baseline_ha.npy
diff --git a/tests/resources/test_enhance.test_decompose_signal.npy b/tests/resources/recipes/cad1/task1/test_enhance.test_decompose_signal.npy
similarity index 100%
rename from tests/resources/test_enhance.test_decompose_signal.npy
rename to tests/resources/recipes/cad1/task1/test_enhance.test_decompose_signal.npy
diff --git a/tests/resources/test_enhance.test_process_stems_for_listener.npy b/tests/resources/recipes/cad1/task1/test_enhance.test_process_stems_for_listener.npy
similarity index 100%
rename from tests/resources/test_enhance.test_process_stems_for_listener.npy
rename to tests/resources/recipes/cad1/task1/test_enhance.test_process_stems_for_listener.npy
diff --git a/tests/resources/test_enhance.test_separate_sources.npy b/tests/resources/recipes/cad1/task1/test_enhance.test_separate_sources.npy
similarity index 100%
rename from tests/resources/test_enhance.test_separate_sources.npy
rename to tests/resources/recipes/cad1/task1/test_enhance.test_separate_sources.npy
diff --git a/tests/resources/recipes/cad1/task2/listeners.json b/tests/resources/recipes/cad1/task2/listeners.json
new file mode 100644
index 000000000..53fbf2c88
--- /dev/null
+++ b/tests/resources/recipes/cad1/task2/listeners.json
@@ -0,0 +1,68 @@
+{
+  "L5071": {
+    "name": "L5071",
+    "audiogram_cfs": [
+      250,
+      500,
+      1000,
+      2000,
+      3000,
+      4000,
+      6000,
+      8000
+    ],
+    "audiogram_levels_l": [
+      15,
+      30,
+      40,
+      55,
+      55,
+      55,
+      70,
+      65
+    ],
+    "audiogram_levels_r": [
+      15,
+      20,
+      35,
+      45,
+      40,
+      35,
+      50,
+      65
+    ]
+  },
+  "L5042": {
+    "name": "L5042",
+    "audiogram_cfs": [
+      250,
+      500,
+      1000,
+      2000,
+      3000,
+      4000,
+      6000,
+      8000
+    ],
+    "audiogram_levels_l": [
+      25,
+      25,
+      40,
+      50,
+      50,
+      65,
+      80,
+      80
+    ],
+    "audiogram_levels_r": [
+      20,
+      20,
+      30,
+      45,
+      60,
+      70,
+      80,
+      80
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/resources/recipes/cad1/task2/scenes.json b/tests/resources/recipes/cad1/task2/scenes.json
new file mode 100644
index 000000000..000d51722
--- /dev/null
+++ b/tests/resources/recipes/cad1/task2/scenes.json
@@ -0,0 +1,8 @@
+{
+    "SCENE01": {
+        "split": "valid"
+    },
+    "SCENE02": {
+        "split": "valid"
+    }
+}
diff --git a/tests/resources/recipes/cad1/task2/scenes_listeners.json b/tests/resources/recipes/cad1/task2/scenes_listeners.json
new file mode 100644
index 000000000..d4c003069
--- /dev/null
+++ b/tests/resources/recipes/cad1/task2/scenes_listeners.json
@@ -0,0 +1,4 @@
+{
+    "SCENE1": ["L5071"],
+    "SCENE2": ["L5042"]
+}
\ No newline at end of file
diff --git a/tests/resources/recipes/cad1/task2/test_build_scene_metadata.json_sample.json b/tests/resources/recipes/cad1/task2/test_build_scene_metadata.json_sample.json
new file mode 100644
index 000000000..73d1c4824
--- /dev/null
+++ b/tests/resources/recipes/cad1/task2/test_build_scene_metadata.json_sample.json
@@ -0,0 +1,1 @@
+{"key1": "value1", "key2": "value2"}
\ No newline at end of file
diff --git a/tests/resources/recipes/cad1/task2/test_enhance.enhance_song_left.npy b/tests/resources/recipes/cad1/task2/test_enhance.enhance_song_left.npy
new file mode 100644
index 000000000..1e48a6cc4
Binary files /dev/null and b/tests/resources/recipes/cad1/task2/test_enhance.enhance_song_left.npy differ
diff --git a/tests/resources/recipes/cad1/task2/test_enhance.enhance_song_right.npy b/tests/resources/recipes/cad1/task2/test_enhance.enhance_song_right.npy
new file mode 100644
index 000000000..31c5e4d47
Binary files /dev/null and b/tests/resources/recipes/cad1/task2/test_enhance.enhance_song_right.npy differ
diff --git a/tests/resources/utils/test_carnoise.signal_generator.npy b/tests/resources/utils/test_carnoise.signal_generator.npy
new file mode 100644
index 000000000..35308b48f
Binary files /dev/null and b/tests/resources/utils/test_carnoise.signal_generator.npy differ
diff --git a/tests/test_compressor.py b/tests/test_compressor.py
index 939091889..1dbff37d7 100644
--- a/tests/test_compressor.py
+++ b/tests/test_compressor.py
@@ -22,7 +22,7 @@ def test_compressor_set_release():
 def test_compressor_process():
     c = Compressor()
     signal = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
-    output, rms, comp_ratios = c.process(signal)
+    output, rms, _comp_ratios = c.process(signal)
 
     assert len(output) == len(signal)
     assert np.all(rms >= 0.0)
diff --git a/tests/test_import.py b/tests/test_import.py
index 54afce011..f0ebf5719 100644
--- a/tests/test_import.py
+++ b/tests/test_import.py
@@ -1,5 +1,7 @@
+# pylint: disable=unused-import
 import clarity.enhancer  # noqa: F401
 
 
 def test_null():
-    assert 1 == 1
+    """Test that should always."""
+    assert True
diff --git a/tests/utils/car_noise_simulator/test_carnoise_parameter_generator.py b/tests/utils/car_noise_simulator/test_carnoise_parameter_generator.py
new file mode 100644
index 000000000..b9f020c9c
--- /dev/null
+++ b/tests/utils/car_noise_simulator/test_carnoise_parameter_generator.py
@@ -0,0 +1,103 @@
+"""Tests for the Car noise parameters generator"""
+# pylint: disable=import-error
+
+import numpy as np
+import pytest
+
+from clarity.utils.car_noise_simulator.carnoise_parameters_generator import (
+    CarNoiseParametersGenerator,
+)
+
+
+@pytest.mark.parametrize(
+    "random_flag,speed,expected_result",
+    [
+        (
+            False,
+            100,
+            {
+                "bump": {"btype": "bandpass", "cutoff_hz": [30, 60], "order": 1},
+                "dip_high": {"btype": "highpass", "cutoff_hz": 300, "order": 2},
+                "dip_low": {"btype": "lowpass", "cutoff_hz": 200, "order": 2},
+                "engine_num_harmonics": 25,
+                "gear": 6,
+                "primary_filter": {
+                    "btype": "lowpass",
+                    "cutoff_hz": 16.860000000000003,
+                    "order": 1,
+                },
+                "reference_level_db": 30,
+                "rpm": 1680.0000000000002,
+                "secondary_filter": {
+                    "btype": "lowpass",
+                    "cutoff_hz": 280.0,
+                    "order": 2,
+                },
+                "speed": 100.0,
+            },
+        ),
+        (
+            True,
+            100,
+            {
+                "bump": {"btype": "bandpass", "cutoff_hz": [80, 116], "order": 2},
+                "dip_high": {"btype": "highpass", "cutoff_hz": 319, "order": 1},
+                "dip_low": {"btype": "lowpass", "cutoff_hz": 130, "order": 1},
+                "engine_num_harmonics": 38,
+                "gear": 5,
+                "primary_filter": {
+                    "btype": "lowpass",
+                    "cutoff_hz": 15.410040000000004,
+                    "order": 1,
+                },
+                "reference_level_db": 31.9,
+                "rpm": 2040.0,
+                "secondary_filter": {
+                    "btype": "lowpass",
+                    "cutoff_hz": 281.68000000000006,
+                    "order": 2,
+                },
+                "speed": 100.0,
+            },
+        ),
+        (
+            True,
+            70,
+            {
+                "bump": {"btype": "bandpass", "cutoff_hz": [91, 168], "order": 1},
+                "dip_high": {"btype": "highpass", "cutoff_hz": 220, "order": 1},
+                "dip_low": {"btype": "lowpass", "cutoff_hz": 90, "order": 1},
+                "engine_num_harmonics": 29,
+                "gear": 4,
+                "primary_filter": {
+                    "btype": "lowpass",
+                    "cutoff_hz": 12.558720000000001,
+                    "order": 1,
+                },
+                "reference_level_db": 30.6,
+                "rpm": 1890.0,
+                "secondary_filter": {
+                    "btype": "lowpass",
+                    "cutoff_hz": 233.984,
+                    "order": 2,
+                },
+                "speed": 70.0,
+            },
+        ),
+    ],
+)
+def test_gen_parameters(random_flag, speed, expected_result):
+    """Test the generation of parameters with and without randomisation."""
+    np.random.seed(42)
+    car_noise_parameters = CarNoiseParametersGenerator(random_flag=random_flag)
+    parameters = car_noise_parameters.gen_parameters(speed_kph=speed)
+
+    assert parameters == expected_result
+
+
+@pytest.mark.parametrize("speed", [40, 130])
+def test_gen_parameters_invalid_speed(speed):
+    """Test speed values out of the boundaries."""
+    car_noise_parameters = CarNoiseParametersGenerator(random_flag=True)
+    with pytest.raises(ValueError):
+        car_noise_parameters.gen_parameters(speed)
diff --git a/tests/utils/car_noise_simulator/test_carnoise_signal_generator.py b/tests/utils/car_noise_simulator/test_carnoise_signal_generator.py
new file mode 100644
index 000000000..17a79a303
--- /dev/null
+++ b/tests/utils/car_noise_simulator/test_carnoise_signal_generator.py
@@ -0,0 +1,49 @@
+"""Tests for the Car noise signal generator"""
+# pylint: disable=import-error
+
+from pathlib import Path
+
+import numpy as np
+
+from clarity.utils.car_noise_simulator.carnoise_signal_generator import (
+    CarNoiseSignalGenerator,
+)
+
+BASE_DIR = Path.cwd()
+RESOURCES = BASE_DIR / "tests" / "resources" / "utils"
+
+
+def test_car_noise_generation():
+    """Test that the car noise generator returns the expected signal"""
+    np.random.seed(42)
+    carnoise_params = {
+        "bump": {"btype": "bandpass", "cutoff_hz": [30, 60], "order": 1},
+        "dip_high": {"btype": "highpass", "cutoff_hz": 300, "order": 2},
+        "dip_low": {"btype": "lowpass", "cutoff_hz": 200, "order": 2},
+        "engine_num_harmonics": 25,
+        "gear": 6,
+        "primary_filter": {
+            "btype": "lowpass",
+            "cutoff_hz": 16.860000000000003,
+            "order": 1,
+        },
+        "reference_level_db": 30,
+        "rpm": 1680.0000000000002,
+        "secondary_filter": {
+            "btype": "lowpass",
+            "cutoff_hz": 280.0,
+            "order": 2,
+        },
+        "speed": 100.0,
+    }
+
+    car_noise = CarNoiseSignalGenerator(
+        sample_rate=16000, duration_secs=1, random_flag=True
+    )
+    car_noise_signal = car_noise.generate_car_noise(carnoise_params, 3, 0.5)
+
+    assert car_noise_signal.shape == (4, 16000)
+    expected = np.load(
+        RESOURCES / "test_carnoise.signal_generator.npy", allow_pickle=True
+    )
+    np.testing.assert_array_almost_equal(car_noise_signal, expected)