Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Tonality IS/TS 20065 #239

Merged
merged 5 commits into from
Mar 3, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/239.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feat: Tonality IS/TS 20065
1 change: 1 addition & 0 deletions doc/source/api/psychoacoustics.rst
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ Psychoacoustics
ToneToNoiseRatio
ToneToNoiseRatioForOrdersOverTime
TonalityDIN45681
TonalityISOTS20065
TonalityECMA418_2
TonalityISO1996_2
TonalityISO1996_2_OverTime
2 changes: 2 additions & 0 deletions src/ansys/sound/core/psychoacoustics/__init__.py
Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@
from .tonality_ecma_418_2 import TonalityECMA418_2
from .tonality_iso_1996_2 import TonalityISO1996_2
from .tonality_iso_1996_2_over_time import TonalityISO1996_2_OverTime
from .tonality_iso_ts_20065 import TonalityISOTS20065
from .tone_to_noise_ratio import ToneToNoiseRatio
from .tone_to_noise_ratio_for_orders_over_time import ToneToNoiseRatioForOrdersOverTime

@@ -60,6 +61,7 @@
"Roughness",
"FluctuationStrength",
"TonalityDIN45681",
"TonalityISOTS20065",
"TonalityISO1996_2_OverTime",
"TonalityAures",
"SpectralCentroid",
133 changes: 70 additions & 63 deletions src/ansys/sound/core/psychoacoustics/tonality_din_45681.py
Original file line number Diff line number Diff line change
@@ -23,13 +23,16 @@
"""Computes DIN 45681 tonality."""
import warnings

from ansys.dpf.core import Field, GenericDataContainersCollection, Operator
from ansys.dpf.core import Field, GenericDataContainersCollection, Operator, types
import matplotlib.pyplot as plt
import numpy as np

from . import PsychoacousticsParent
from .._pyansys_sound import PyAnsysSoundException, PyAnsysSoundWarning

# Name of the DPF Sound operator used in this module.
ID_COMPUTE_TONALITY_DIN_45681 = "compute_tonality_din45681"

TONE_TYPES = ("", "FG")


@@ -46,7 +49,7 @@ def __init__(self, signal: Field = None, window_length: float = 3.0, overlap: fl
Parameters
----------
signal: Field, default: None
Signal in Pa on which to calculate the tonality, as a DPF field.
Signal in Pa on which to calculate the tonality.
window_length: float, default: 3.0
Length, in s, of each slice of the signal used to calculate an average spectrum.
overlap: float, default: 0.0
@@ -58,24 +61,24 @@ def __init__(self, signal: Field = None, window_length: float = 3.0, overlap: fl
self.signal = signal
self.window_length = window_length
self.overlap = overlap
self.__operator = Operator("compute_tonality_din45681")
self.__operator = Operator(ID_COMPUTE_TONALITY_DIN_45681)

def __str__(self):
"""Overloads the __str__ method."""
return (
f"{__class__.__name__} object.\n"
"Data\n"
f'Signal name: "{self.signal.name}"\n'
f"Window length: {self.window_length} s\n"
f"Overlap: {self.overlap} %\n"
f"Mean difference DL: "
f'\tSignal name: "{self.signal.name}"\n'
f"\tWindow length: {self.window_length} s\n"
f"\tOverlap: {self.overlap} %\n"
f"Mean tonality (difference DL): "
f"{self.get_mean_difference():.1f} (+/-{self.get_uncertainty():.1f}) dB\n"
f"Tonal adjustment Kt: {self.get_tonal_adjustment():.0f} dB\n"
f"Tonal adjustment Kt: {self.get_tonal_adjustment():.0f} dB"
)

@property
def signal(self) -> Field:
"""Signal in Pa, as a DPF field. Default is None."""
"""Signal in Pa. Default is None."""
return self.__signal

@signal.setter
@@ -121,7 +124,7 @@ def process(self):
"""
if self.signal == None:
raise PyAnsysSoundException(
f"No input signal defined. Use ``{__class__.__name__}.signal``."
f"No input signal defined. Use `{__class__.__name__}.signal`."
)

# Connect the operator input(s).
@@ -134,13 +137,13 @@ def process(self):

# Store the operator outputs in a tuple.
self._output = (
self.__operator.get_output(0, "double"),
self.__operator.get_output(1, "double"),
self.__operator.get_output(2, "double"),
self.__operator.get_output(3, "field"),
self.__operator.get_output(4, "field"),
self.__operator.get_output(5, "field"),
self.__operator.get_output(6, "field"),
self.__operator.get_output(0, types.double),
self.__operator.get_output(1, types.double),
self.__operator.get_output(2, types.double),
self.__operator.get_output(3, types.field),
self.__operator.get_output(4, types.field),
self.__operator.get_output(5, types.field),
self.__operator.get_output(6, types.field),
self.__operator.get_output(7, GenericDataContainersCollection),
)

@@ -150,29 +153,29 @@ def get_output(self) -> tuple:
Returns
-------
tuple
First element (float) is the DIN 45681 tonality (mean difference DL), in dB.
- First element (float) is the DIN 45681 tonality (mean difference DL), in dB.
Second element (float) is the DIN 45681 tonality uncertainty, in dB.
- Second element (float) is the DIN 45681 tonality uncertainty, in dB.
Third element (float) is the DIN 45681 tonal adjustment Kt, in dB.
- Third element (float) is the DIN 45681 tonal adjustment Kt, in dB.
Fourth element (Field) is the DIN 45681 tonality over time
(decisive difference DLj), in dB.
- Fourth element (Field) is the DIN 45681 tonality over time
(decisive difference DLj), in dB.
Fifth element (Field) is the DIN 45681 tonality uncertainty over time, in dB.
- Fifth element (Field) is the DIN 45681 tonality uncertainty over time, in dB.
Sixth element (Field) is the DIN 45681 decisive frequency over time, in Hz.
- Sixth element (Field) is the DIN 45681 decisive frequency over time, in Hz.
Seventh element (Field) is the DIN 45681 tonal adjustment Kt over time, in dB.
- Seventh element (Field) is the DIN 45681 tonal adjustment Kt over time, in dB.
Eighth element (GenericDataContainer) is the DIN 45681 tonality details (individual
tone data for each spectrum).
- Eighth element (GenericDataContainer) is the DIN 45681 tonality details (individual
tone data for each spectrum).
"""
if self._output == None:
warnings.warn(
PyAnsysSoundWarning(
f"Output is not processed yet. "
f"Use the ``{__class__.__name__}.process()`` method."
f"Use the `{__class__.__name__}.process()` method."
)
)

@@ -184,21 +187,21 @@ def get_output_as_nparray(self) -> tuple[np.ndarray]:
Returns
-------
tuple[numpy.ndarray]
First element is the DIN 45681 tonality (mean difference DL), in dB.
- First element is the DIN 45681 tonality (mean difference DL), in dB.
Second element is the DIN 45681 tonality uncertainty, in dB.
- Second element is the DIN 45681 tonality uncertainty, in dB.
Third element is the DIN 45681 tonal adjustment Kt, in dB.
- Third element is the DIN 45681 tonal adjustment Kt, in dB.
Fourth element is the DIN 45681 tonality over time (decisive difference DLj), in dB.
- Fourth element is the DIN 45681 tonality over time (decisive difference DLj), in dB.
Fifth element is the DIN 45681 tonality uncertainty over time, in dB.
- Fifth element is the DIN 45681 tonality uncertainty over time, in dB.
Sixth element is the DIN 45681 decisive frequency over time, in Hz.
- Sixth element is the DIN 45681 decisive frequency over time, in Hz.
Seventh element is the DIN 45681 tonal adjustment Kt over time, in dB.
- Seventh element is the DIN 45681 tonal adjustment Kt over time, in dB.
Eighth element is the time scale, in s.
- Eighth element is the time scale, in s.
"""
output = self.get_output()

@@ -318,25 +321,25 @@ def get_spectrum_number(self) -> int:
"""
return len(self.get_output()[3])

def get_spectrum_details(self, spectrum_index: int = 0) -> tuple[float, float, float]:
def get_spectrum_details(self, spectrum_index: int) -> tuple[float]:
"""Get the spectrum data for a specific spectrum.
Returns the data (decisive difference, uncertainty, and decisive frequency) corresponding
to a specific spectrum (time step).
Parameters
----------
spectrum_index: int, default: 0
Index of the spectrum to get.
spectrum_index: int
Index of the spectrum. The index is 0-based.
Returns
-------
tuple[float,float,float]
Decisive difference DLj in dB.
tuple[float]
- First element is the decisive difference DLj in dB.
Uncertainty in dB.
- Second element is the uncertainty in dB.
Decisive frequency in Hz.
- Third element is the decisive frequency in Hz.
"""
# Check validity of the input spectrum index.
self.__check_spectrum_index(spectrum_index)
@@ -347,11 +350,16 @@ def get_spectrum_details(self, spectrum_index: int = 0) -> tuple[float, float, f
self.get_output_as_nparray()[5][spectrum_index],
)

def get_tone_number(self, spectrum_index: int = 0) -> int:
def get_tone_number(self, spectrum_index: int) -> int:
"""Get the number of tones for a specific spectrum.
Returns the number of tones detected in a specific spectrum (time step).
Parameters
----------
spectrum_index: int
Index of the spectrum where the tone was detected. The index is 0-based.
Returns
-------
int
@@ -367,43 +375,42 @@ def get_tone_number(self, spectrum_index: int = 0) -> int:

return len(spectrum.get_property("differences"))

def get_tone_details(
self, spectrum_index: int = 0, tone_index: int = 0
) -> tuple[float, float, float, str, float, float, float, float, float, float]:
def get_tone_details(self, spectrum_index: int, tone_index: int) -> tuple:
"""Get the tone data, for a specific spectrum.
Returns all data associated with a specific detected tone, in a specific spectrum (time
step).
Parameters
----------
spectrum_index: int, default: 0
Index of the spectrum where the tone was detected.
tone_index: int, default: 0
Index of the tone whose details are requested.
spectrum_index: int
Index of the spectrum where the tone was detected. The index is 0-based.
tone_index: int
Index of the tone whose details are requested. The index is 0-based.
Returns
-------
tuple[float,float,float,str,float,float,float,float,float,float]
Decisive difference DLj in dB.
tuple
- First element (float) is the decisive difference DLj in dB.
Uncertainty, in dB.
- Second element (float) is the uncertainty, in dB.
Decisive frequency, in Hz.
- Third element (float) is the decisive frequency, in Hz.
Tone type ('' for individual tones, or 'FG' for groups of tones).
- Fourth element (str) is the tone type ('' for individual tones, or 'FG' for groups
of tones).
Critical band lower limit, in Hz.
- Fifth element (float) is the critical band lower limit, in Hz.
Critical band upper limit, in Hz.
- Sixth element (float) is the critical band upper limit, in Hz.
Mean narrow-band masking noise level Ls, in dBA.
- Seventh element (float) is the mean narrow-band masking noise level Ls, in dBA.
Tone level Lt, in dBA.
- Eighth element (float) is the tone level Lt, in dBA.
Masking noise level Lg, in dBA.
- Ninth element (float) is the masking noise level Lg, in dBA.
Masking index av, in dB.
- Tenth element (float) is the masking index av, in dB.
"""
# Check validities of input indexes.
self.__check_spectrum_index(spectrum_index)
@@ -440,7 +447,7 @@ def plot(self):
"""
if self._output == None:
raise PyAnsysSoundException(
f"Output is not processed yet. Use the ``{__class__.__name__}.process()`` method."
f"Output is not processed yet. Use the `{__class__.__name__}.process()` method."
)

# Get data to plot
454 changes: 454 additions & 0 deletions src/ansys/sound/core/psychoacoustics/tonality_iso_ts_20065.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -48,12 +48,12 @@
EXP_SPECTRUM1_TONE3_MASKING_NOISE_LEVEL = -5.367661476135254
EXP_STR = (
"TonalityDIN45681 object.\n"
+ "Data\n"
+ f'Signal name: "Acceleration_with_Tacho"\n'
+ f"Window length: 3.0 s\n"
+ f"Overlap: 0.0 %\n"
+ f"Mean difference DL: 9.8 (+/-0.9) dB\n"
+ f"Tonal adjustment Kt: 5 dB\n"
"Data\n"
f'\tSignal name: "Acceleration_with_Tacho"\n'
f"\tWindow length: 3.0 s\n"
f"\tOverlap: 0.0 %\n"
f"Mean tonality (difference DL): 9.8 (+/-0.9) dB\n"
f"Tonal adjustment Kt: 5 dB"
)


@@ -129,7 +129,7 @@ def test_tonality_din45681_process_exception():

with pytest.raises(
PyAnsysSoundException,
match="No input signal defined. Use ``TonalityDIN45681.signal``.",
match="No input signal defined. Use `TonalityDIN45681.signal`.",
):
tonality.process()

@@ -153,7 +153,7 @@ def test_tonality_din45681_get_output_unprocessed():

with pytest.warns(
PyAnsysSoundWarning,
match="Output is not processed yet. Use the ``TonalityDIN45681.process\\(\\)`` method.",
match="Output is not processed yet. Use the `TonalityDIN45681.process\\(\\)` method.",
):
output = tonality.get_output()
assert output is None
@@ -185,7 +185,7 @@ def test_tonality_din45681_get_output_as_nparray_unprocessed():

with pytest.warns(
PyAnsysSoundWarning,
match="Output is not processed yet. Use the ``TonalityDIN45681.process\\(\\)`` method.",
match="Output is not processed yet. Use the `TonalityDIN45681.process\\(\\)` method.",
):
DL, U, Kt, DLj, Uj, fTj, Ktj, time = tonality.get_output_as_nparray()
assert np.isnan(DL)
@@ -386,7 +386,7 @@ def test_tonality_din45681_plot_exception():

with pytest.raises(
PyAnsysSoundException,
match="Output is not processed yet. Use the ``TonalityDIN45681.process\\(\\)`` method.",
match="Output is not processed yet. Use the `TonalityDIN45681.process\\(\\)` method.",
):
tonality.plot()

Original file line number Diff line number Diff line change
@@ -0,0 +1,374 @@
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from unittest.mock import patch

from ansys.dpf.core import Field
import numpy as np
import pytest

from ansys.sound.core._pyansys_sound import PyAnsysSoundException, PyAnsysSoundWarning
from ansys.sound.core.psychoacoustics import TonalityISOTS20065
from ansys.sound.core.signal_utilities import LoadWav

EXP_DL = 9.737211
EXP_U = 0.8541942
EXP_DLJ3 = 7.597201
EXP_UJ3 = 2.154871
EXP_FTJ3 = 113.0493
EXP_TIME3 = 10.48608
EXP_SPECTRUM_NUMBER = 6
EXP_SPECTRUM1_DECISIVE_DIFFERENCE = 7.895215
EXP_SPECTRUM1_UNCERTAINTY = 1.483663
EXP_SPECTRUM1_DECISIVE_FREQUENCY = 16359.85
EXP_SPECTRUM1_TONE_NUMBER = 4
EXP_SPECTRUM1_TONE3_DIFFERENCE = 4.705133
EXP_SPECTRUM1_TONE3_TYPE = ""
EXP_SPECTRUM1_TONE3_MASKING_NOISE_LEVEL = 0.6313946
EXP_STR = (
"TonalityISOTS20065 object.\n"
"Data\n"
f'\tSignal name: "Acceleration_with_Tacho"\n'
f"\tWindow length: 3.0 s\n"
f"\tOverlap: 0.0 %\n"
f"Mean tonality (audibility DL): 9.7 (+/-0.9) dB"
)


def test_tonality_iso_ts_20065_instantiation():
"""Test TonalityISOTS20065 instantiation."""
tonality = TonalityISOTS20065()
assert tonality.signal == None
assert tonality.window_length == pytest.approx(3.0)
assert tonality.overlap == pytest.approx(0.0)


def test_tonality_iso_ts_20065_properties():
"""Test TonalityISOTS20065 properties."""
tonality = TonalityISOTS20065()
tonality.signal = Field()
assert type(tonality.signal) == Field

tonality.window_length = 2.0
assert tonality.window_length == 2.0

tonality.overlap = 15.6
assert tonality.overlap == 15.6


def test_tonality_iso_ts_20065___str__():
"""Test __str__ method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

assert tonality.__str__() == EXP_STR


def test_tonality_iso_ts_20065_setters_exceptions():
"""Test TonalityISOTS20065 setters' exceptions."""
tonality = TonalityISOTS20065()
with pytest.raises(
PyAnsysSoundException,
match="Signal must be specified as a DPF field.",
):
tonality.signal = "Invalid"

with pytest.raises(PyAnsysSoundException, match="Window length must be strictly positive."):
tonality.window_length = 0.0

with pytest.raises(
PyAnsysSoundException, match="Overlap must be positive and strictly smaller than 100.0 %."
):
tonality.overlap = 100.0

with pytest.raises(
PyAnsysSoundException, match="Overlap must be positive and strictly smaller than 100.0 %."
):
tonality.overlap = -1.0


def test_tonality_iso_ts_20065_process():
"""Test process method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()


def test_tonality_iso_ts_20065_process_exception():
"""Test process method's exception."""
tonality = TonalityISOTS20065()

with pytest.raises(
PyAnsysSoundException,
match="No input signal defined. Use `TonalityISOTS20065.signal`.",
):
tonality.process()


def test_tonality_iso_ts_20065_get_output():
"""Test get_output method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

output = tonality.get_output()
assert output is not None


def test_tonality_iso_ts_20065_get_output_unprocessed():
"""Test get_output method's warning."""
tonality = TonalityISOTS20065()

with pytest.warns(
PyAnsysSoundWarning,
match="Output is not processed yet. Use the `TonalityISOTS20065.process\\(\\)` method.",
):
output = tonality.get_output()
assert output is None


def test_tonality_iso_ts_20065_get_output_as_nparray():
"""Test get_output_as_nparray method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

DL, U, DLj, Uj, fTj, time = tonality.get_output_as_nparray()
assert DL == pytest.approx(EXP_DL)
assert U == pytest.approx(EXP_U)
assert DLj[3] == pytest.approx(EXP_DLJ3)
assert Uj[3] == pytest.approx(EXP_UJ3)
assert fTj[3] == pytest.approx(EXP_FTJ3)
assert time[3] == pytest.approx(EXP_TIME3)


def test_tonality_iso_ts_20065_get_output_as_nparray_unprocessed():
"""Test get_output_as_nparray method's warning."""
tonality = TonalityISOTS20065()

with pytest.warns(
PyAnsysSoundWarning,
match="Output is not processed yet. Use the `TonalityISOTS20065.process\\(\\)` method.",
):
DL, U, DLj, Uj, fTj, time = tonality.get_output_as_nparray()
assert np.isnan(DL)
assert np.isnan(U)
assert len(DLj) == 0
assert len(Uj) == 0
assert len(fTj) == 0
assert len(time) == 0


def test_tonality_iso_ts_20065_get_mean_audibility():
"""Test get_mean_audibility method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

DL = tonality.get_mean_audibility()
assert DL == pytest.approx(EXP_DL)


def test_tonality_iso_ts_20065_get_uncertainty():
"""Test get_uncertainty method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

U = tonality.get_uncertainty()
assert U == pytest.approx(EXP_U)


def test_tonality_iso_ts_20065_get_decisive_audibility_over_time():
"""Test get_decisive_audibility_over_time method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

DLj = tonality.get_decisive_audibility_over_time()
assert DLj[3] == pytest.approx(EXP_DLJ3)


def test_tonality_iso_ts_20065_get_uncertainty_over_time():
"""Test get_uncertainty_over_time method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

Uj = tonality.get_uncertainty_over_time()
assert Uj[3] == pytest.approx(EXP_UJ3)


def test_tonality_iso_ts_20065_get_time_scale():
"""Test get_time_scale method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

time = tonality.get_time_scale()
assert time[3] == pytest.approx(EXP_TIME3)


def test_tonality_iso_ts_20065_get_spectrum_number():
"""Test get_spectrum_number method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

spectrum_number = tonality.get_spectrum_number()
assert spectrum_number == EXP_SPECTRUM_NUMBER


def test_tonality_iso_ts_20065_get_spectrum_details():
"""Test get_spectrum_details method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

decicive_audibility, uncertainty, decisive_frequency = tonality.get_spectrum_details(
spectrum_index=1
)
assert decicive_audibility == pytest.approx(EXP_SPECTRUM1_DECISIVE_DIFFERENCE)
assert uncertainty == pytest.approx(EXP_SPECTRUM1_UNCERTAINTY)
assert decisive_frequency == pytest.approx(EXP_SPECTRUM1_DECISIVE_FREQUENCY)


def test_tonality_iso_ts_20065_get_tone_number():
"""Test get_tone_number method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

tone_number = tonality.get_tone_number(spectrum_index=1)
assert tone_number == EXP_SPECTRUM1_TONE_NUMBER


def test_tonality_iso_ts_20065_get_tone_details():
"""Test get_tone_details method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

details = tonality.get_tone_details(spectrum_index=1, tone_index=3)
assert details[0] == pytest.approx(EXP_SPECTRUM1_TONE3_DIFFERENCE)
assert details[3] == pytest.approx(EXP_SPECTRUM1_TONE3_TYPE)
assert details[7] == pytest.approx(EXP_SPECTRUM1_TONE3_MASKING_NOISE_LEVEL)


def test_tonality_iso_ts_20065_get_tone_details_exception():
"""Test get_tone_details method's exception."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

with pytest.raises(
PyAnsysSoundException,
match="Tone index 10 is out of bounds \\(total tone count in specified spectrum is 6\\).",
):
tonality.get_tone_details(spectrum_index=1, tone_index=10)


@patch("matplotlib.pyplot.show")
def test_tonality_iso_ts_20065_plot(mock_show):
"""Test plot method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

tonality.plot()


def test_tonality_iso_ts_20065_plot_exception():
"""Test plot method's exception."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])

with pytest.raises(
PyAnsysSoundException,
match="Output is not processed yet. Use the `TonalityISOTS20065.process\\(\\)` method.",
):
tonality.plot()


def test_tonality_iso_ts_20065___check_spectrum_index():
"""Test __check_spectrum_index method."""
wav_loader = LoadWav(pytest.data_path_Acceleration_with_Tacho_nonUnitaryCalib)
wav_loader.process()
fc = wav_loader.get_output()

tonality = TonalityISOTS20065(signal=fc[0])
tonality.process()

with pytest.raises(
PyAnsysSoundException,
match="Spectrum index 10 is out of bounds \\(total spectrum count is 6\\).",
):
tonality._TonalityISOTS20065__check_spectrum_index(spectrum_index=10)