Skip to content

feat: Tonality IS/TS 20065 #239

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

Merged
merged 5 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
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
Expand Up @@ -21,6 +21,7 @@ Psychoacoustics
ToneToNoiseRatio
ToneToNoiseRatioForOrdersOverTime
TonalityDIN45681
TonalityISOTS20065
TonalityECMA418_2
TonalityISO1996_2
TonalityISO1996_2_OverTime
Expand Down
2 changes: 2 additions & 0 deletions src/ansys/sound/core/psychoacoustics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -60,6 +61,7 @@
"Roughness",
"FluctuationStrength",
"TonalityDIN45681",
"TonalityISOTS20065",
"TonalityISO1996_2_OverTime",
"TonalityAures",
"SpectralCentroid",
Expand Down
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
Expand Up @@ -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")


Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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).
Expand All @@ -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),
)

Expand All @@ -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."
)
)

Expand All @@ -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()

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Loading