# -*- coding: utf-8 -*-
# This source code is licensed under the BSD-style license found in the
# LICENSE.rst file in the root directory of this source tree.
# pyatsyn Copyright (c) <2023>, <Johnathan G Lyon>
# All rights reserved.
# Except where otherwise noted, ATSA and ATSH is Copyright (c) <2002-2004>,
# <Oscar Pablo Di Liscia, Pete Moss, and Juan Pampin>
"""Critical Bands and Signal-to-Mask Ratio Evaluation
This module is used to evaluate critical band masking for signal-to-mask ratio calculations
Attributes
----------
ATS_CRITICAL_BAND_EDGES : ndarray[float]
1D array containing 26 frequencies that distinguish the default 25 critical bands
"""
from numpy import log10, array
from pyatsyn.atsa.utils import amp_to_db_spl
ATS_CRITICAL_BAND_EDGES = array([0.0,100.0,200.0,300.0, 400.0,
510.0, 630.0, 770.0, 920.0, 1080.0,
1270.0, 1480.0, 1720.0, 2000.0, 2320.0,
2700.0, 3150.0, 3700.0, 4400.0, 5300.0,
6400.0, 7700.0, 9500.0, 12000.0, 15500.0,
20000.0], dtype="float64")
[docs]def evaluate_smr(peaks, slope_l = -27.0, delta_db = -50):
"""Function to evaluate signal-to-mask ratio for the given peaks
This function evaluates masking values (SMR) for :obj:`~pyatsyn.ats_structure.AtsPeak` in list `peaks`
Iteratively the parameters will be use to generate a triangular mask
with a primary vertex at the frequency of, and at delta_dB below the amplitude
of the masker.
.. image:: _static/img/smr.png
:width: 350
:alt: graphic depiction of smr calculation
All other peaks are evaluated based on the triangular
edges descending from the primary vertex according to slope_l for lower
frequencies, and a calculated slope for higher frequencies. Maskee amplitudes
proportions above this edge are then assigned to the maskee peak's smr property.
By the end of the iteration, the largest smr seen as maskee is kept in the peak's
smr property.
Parameters
----------
peaks : Iterable[:obj:`~pyatsyn.ats_structure.AtsPeak`]
An iterable collection of AtsPeaks that will have their `smr` attributes updated
slope_l : float, optional
A float (in dB/bark) to dictate the slope of the left side of the mask (default: -27.0)
delta_db : float, optional
A float (in dB) that sets the amplitude threshold for the masking curves
Must be (<= 0dB) (default: -50)
Raises
------
ValueError
If `delta_db` is not less than or equal to 0.
"""
if delta_db > 0:
raise ValueError("delta_db must be <= 0")
n_peaks = len(peaks)
if n_peaks == 1:
peaks[0].smr = amp_to_db_spl(peaks[0].amp)
else:
for p in peaks:
p.barkfrq = frq_to_bark(p.frq)
p.db_spl = amp_to_db_spl(p.amp)
p.slope_r = compute_slope_r(p.db_spl, slope_l)
for maskee_ind, maskee in enumerate(peaks):
for masker_ind in [ i for i in range(n_peaks) if i != maskee_ind]:
masker = peaks[masker_ind]
mask_term = masker.db_spl + delta_db + (masker.slope_r * abs(maskee.barkfrq - masker.barkfrq))
if mask_term > maskee.smr:
maskee.smr = mask_term
maskee.smr = maskee.db_spl - maskee.smr
[docs]def frq_to_bark(freq):
"""Function to convert frequency from Hz to bark scale
This function will convert frequency from Hz to bark scale, a psychoacoustical scale used
for subjective measurements of loudness.
Parameters
----------
freq : float
A frequency (in Hz) to convert to bark scale
Returns
-------
float
the frequency in bark scale
"""
if freq <= 0.0:
return 0.0
elif freq <= 400.0:
return freq * 0.01
elif freq >= 20000.0:
return None
else:
band = find_band(freq)
low = ATS_CRITICAL_BAND_EDGES[band]
high = ATS_CRITICAL_BAND_EDGES[band+1]
return 1 + band + abs(log10(freq/low) / log10(low/high))
[docs]def find_band(freq):
"""Function to retrieve lower band edge in :obj:`~pyatsyn.atsa.critical_bands.ATS_CRITICAL_BAND_EDGES`
Parameters
----------
freq : float
A frequency (in Hz) to find the related band in :obj:`~pyatsyn.atsa.critical_bands.ATS_CRITICAL_BAND_EDGES` for
Returns
----------
int
index into :obj:`~pyatsyn.atsa.critical_bands.ATS_CRITICAL_BAND_EDGES` that marks the lower band edge for the given freq
Raises
----------
LookupError
if the frequency given is outside the range of the lowest or highest edge in :obj:`~pyatsyn.atsa.critical_bands.ATS_CRITICAL_BAND_EDGES`
"""
if freq < ATS_CRITICAL_BAND_EDGES[0]:
raise LookupError("Frequency is below range of ATS_CRITICAL_BAND_EDGES")
if freq > ATS_CRITICAL_BAND_EDGES[-1]:
raise LookupError("Frequency is above range of ATS_CRITICAL_BAND_EDGES")
for ind in range(len(ATS_CRITICAL_BAND_EDGES)-2,0,-1):
if freq > ATS_CRITICAL_BAND_EDGES[ind]:
return ind
return 0
[docs]def compute_slope_r(masker_amp_db, slope_l = -27.0):
"""Function to compute right slope of triangular mask
Computes the right slope of mask, dependent on the level of the masker
Parameters
----------
masker_amp_db : float
Amplitude (in dB) of the masker peak
slope_l : float, optional
slope (in dB / bark) of the lower frequency side of the masking triangle
"""
return slope_l + (max(masker_amp_db - 40.0, 0.0) * 0.37)