Split timeweighting into different types for each of the possible use cases.
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Anne de Jong 2023-02-03 20:41:59 +01:00
parent 9aba6040f7
commit b61fb7b014
4 changed files with 76 additions and 64 deletions

1
.gitignore vendored
View File

@ -20,3 +20,4 @@ doc
.spyproject .spyproject
.cache .cache
_skbuild _skbuild
acme_log.log

View File

@ -13,28 +13,29 @@ using rte = std::runtime_error;
using std::unique_ptr; using std::unique_ptr;
SLM::SLM(const d fs, const d Lref, const us downsampling_fac, const d tau, SLM::SLM(const d fs, const d Lref, const us downsampling_fac, const d tau,
std::unique_ptr<Filter> pre_filter, std::unique_ptr<Filter> pre_filter,
std::vector<std::unique_ptr<Filter>> bandpass) std::vector<std::unique_ptr<Filter>> bandpass)
: _pre_filter(std::move(pre_filter)), _bandpass(std::move(bandpass)), : _pre_filter(std::move(pre_filter)), _bandpass(std::move(bandpass)),
_alpha(exp(-1 / (fs * tau))), _alpha(exp(-1 / (fs * tau))),
_sp_storage(_bandpass.size(), arma::fill::zeros), // Storage for _sp_storage(_bandpass.size(), arma::fill::zeros), // Storage for
// components of // components of
// single pole low pass // single pole low pass
// filter // filter
Lrefsq(Lref*Lref), // Reference level Lrefsq(Lref * Lref), // Reference level
downsampling_fac(downsampling_fac), downsampling_fac(downsampling_fac),
// Initalize mean square // Initalize mean square
Pm(_bandpass.size(), arma::fill::zeros), Pm(_bandpass.size(), arma::fill::zeros),
// Initalize max // Initalize max
Pmax(_bandpass.size(), arma::fill::zeros), Pmax(_bandpass.size(), arma::fill::zeros),
// Initalize peak // Initalize peak
Ppeak(_bandpass.size(), arma::fill::zeros) Ppeak(_bandpass.size(), arma::fill::zeros)
{ {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
DEBUGTRACE_PRINT(_alpha);
// Make sure thread pool is running // Make sure thread pool is running
getPool(); getPool();
@ -42,7 +43,7 @@ SLM::SLM(const d fs, const d Lref, const us downsampling_fac, const d tau,
if (Lref <= 0) { if (Lref <= 0) {
throw rte("Invalid reference level"); throw rte("Invalid reference level");
} }
if (tau <= 0) { if (tau < 0) {
throw rte("Invalid time constant for Single pole lowpass filter"); throw rte("Invalid time constant for Single pole lowpass filter");
} }
if (fs <= 0) { if (fs <= 0) {
@ -66,43 +67,43 @@ std::vector<unique_ptr<Filter>> createBandPass(const dmat &coefs) {
} }
return bf; return bf;
} }
us SLM::suggestedDownSamplingFac(const d fs,const d tau) { us SLM::suggestedDownSamplingFac(const d fs, const d tau) {
if(tau<0) throw rte("Invalid time weighting time constant"); if (fs <= 0)
if(fs<=0) throw rte("Invalid sampling frequency"); throw rte("Invalid sampling frequency");
// A reasonable 'framerate' for the sound level meter, based on the // A reasonable 'framerate' for the sound level meter, based on the
// filtering time constant. // filtering time constant.
if (tau > 0) { if (tau > 0) {
d fs_slm = 10 / tau; d fs_slm = 10 / tau;
if(fs_slm < 30) { if (fs_slm < 30) {
fs_slm = 30; fs_slm = 30;
} }
return std::max((us) 1, static_cast<us>(fs / fs_slm)); return std::max((us)1, static_cast<us>(fs / fs_slm));
} else { } else {
return 1; return 1;
} }
} }
SLM SLM::fromBiquads(const d fs, const d Lref, const us downsampling_fac, SLM SLM::fromBiquads(const d fs, const d Lref, const us downsampling_fac,
const d tau, const vd &pre_filter_coefs, const d tau, const vd &pre_filter_coefs,
const dmat &bandpass_coefs) { const dmat &bandpass_coefs) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
return SLM(fs, Lref, downsampling_fac, tau, return SLM(fs, Lref, downsampling_fac, tau,
std::make_unique<SeriesBiquad>(pre_filter_coefs), std::make_unique<SeriesBiquad>(pre_filter_coefs),
createBandPass(bandpass_coefs)); createBandPass(bandpass_coefs));
} }
SLM SLM::fromBiquads(const d fs, const d Lref, const us downsampling_fac, SLM SLM::fromBiquads(const d fs, const d Lref, const us downsampling_fac,
const d tau, const dmat &bandpass_coefs) { const d tau, const dmat &bandpass_coefs) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
return SLM(fs, Lref, downsampling_fac, tau, return SLM(fs, Lref, downsampling_fac, tau,
nullptr, // Pre-filter nullptr, // Pre-filter
createBandPass(bandpass_coefs) // Bandpass coefficients createBandPass(bandpass_coefs) // Bandpass coefficients
); );
} }
vd SLM::run_single(vd work,const us i) { vd SLM::run_single(vd work, const us i) {
// Filter input in-place // Filter input in-place
_bandpass[i]->filter(work); _bandpass[i]->filter(work);
@ -126,7 +127,7 @@ vd SLM::run_single(vd work,const us i) {
for (us j = 0; j < work.n_rows; j++) { for (us j = 0; j < work.n_rows; j++) {
// Update mean square of signal, work is here still signal power // Update mean square of signal, work is here still signal power
Pm(i) = (Pm(i) * static_cast<d>(N_local) + work(j)) / Pm(i) = (Pm(i) * static_cast<d>(N_local) + work(j)) /
(static_cast<d>(N_local) + 1); (static_cast<d>(N_local) + 1);
N_local++; N_local++;
@ -142,7 +143,7 @@ vd SLM::run_single(vd work,const us i) {
Pmax(i) = std::max(Pmax(i), arma::max(work)); Pmax(i) = std::max(Pmax(i), arma::max(work));
// Convert to levels in dB // Convert to levels in dB
work = 10*arma::log10((work+arma::datum::eps)/Lrefsq); work = 10 * arma::log10((work + arma::datum::eps) / Lrefsq);
return work; return work;
} }
@ -170,8 +171,8 @@ dmat SLM::run(const vd &input_orig) {
/* DEBUGTRACE_PRINT(res.n_rows); */ /* DEBUGTRACE_PRINT(res.n_rows); */
/* DEBUGTRACE_PRINT(futs.size()); */ /* DEBUGTRACE_PRINT(futs.size()); */
// Update the total number of samples harvested so far. NOTE: *This should be // Update the total number of samples harvested so far. NOTE: *This should
// done AFTER the threads are done!!!* // be done AFTER the threads are done!!!*
} }
N += input.n_rows; N += input.n_rows;

View File

@ -286,46 +286,57 @@ class this_lasp_shelve(Shelve):
return os.path.join(lasp_appdir, f'{node}_config.shelve') return os.path.join(lasp_appdir, f'{node}_config.shelve')
class TimeWeighting: class TimeWeighting:
none = (-1, 'Raw (no time weighting)') # This is not actually a time weighting
none = (0, 'Raw (no time weighting)')
uufast = (1e-4, '0.1 ms') uufast = (1e-4, '0.1 ms')
ufast = (35e-3, 'Impulse (35 ms)') ufast = (35e-3, 'Impulse (35 ms)')
fast = (0.125, 'Fast (0.125 s)') fast = (0.125, 'Fast (0.125 s)')
slow = (1.0, 'Slow (1.0 s)') slow = (1.0, 'Slow (1.0 s)')
tens = (10., '10 s') tens = (10., '10 s')
infinite = (0, 'Infinite')
types_realtime = (ufast, fast, slow, tens, infinite) # All-averaging: compute the current average of all history. Kind of the
types_all = (none, uufast, ufast, fast, slow, tens, infinite) # equivalent level. Only useful for power spectra. For Sound Level Meter,
# equivalent levels does that thing.
averaging = (-1, 'All-averaging')
types_all = (none, uufast, ufast, fast, slow, tens, averaging)
# Used for combobox of realt time power spectra
types_realtimeaps = (ufast, fast, slow, tens, averaging)
# Used for combobox of realt time sound level meter
types_realtimeslm = (ufast, fast, slow, tens)
# Used for combobox of sound level meter - time dependent
types_slmt = (none, uufast, ufast, fast, slow, tens)
# Used for combobox of sound level meter - Lmax statistics
types_slmstats = (uufast, ufast, fast, slow, tens)
default = fast default = fast
default_index = 3
default_index_realtime = 1
@staticmethod @staticmethod
def fillComboBox(cb, realtime=False): def fillComboBox(cb, types):
""" """
Fill TimeWeightings to a combobox Fill TimeWeightings to a combobox
Args: Args:
cb: QComboBox to fill cb: QComboBox to fill
types: The types to fill it with
""" """
cb.clear() cb.clear()
if realtime:
types = TimeWeighting.types_realtime logging.debug(f'{types}')
defindex = TimeWeighting.default_index_realtime
else:
types = TimeWeighting.types_all
defindex = TimeWeighting.default_index
for tw in types: for tw in types:
cb.addItem(tw[1], tw) cb.addItem(tw[1], tw)
if TimeWeighting.default == tw:
cb.setCurrentIndex(defindex) cb.setCurrentIndex(cb.count()-1)
@staticmethod @staticmethod
def getCurrent(cb): def getCurrent(cb):
if cb.count() == len(TimeWeighting.types_realtime): for type in TimeWeighting.types_all:
return TimeWeighting.types_realtime[cb.currentIndex()] if cb.currentText() == type[1]:
else: return type
return TimeWeighting.types_all[cb.currentIndex()]
class FreqWeighting: class FreqWeighting:

View File

@ -32,10 +32,10 @@ class SLM:
def __init__(self, def __init__(self,
fs, fs,
fbdesigner=None, fbdesigner=None,
tw: TimeWeighting =TimeWeighting.fast, tw: TimeWeighting = TimeWeighting.fast,
fw: FreqWeighting =FreqWeighting.A, fw: FreqWeighting = FreqWeighting.A,
xmin = None, xmin=None,
xmax = None, xmax=None,
include_overall=True, include_overall=True,
level_ref_value=P_REF, level_ref_value=P_REF,
offset_t=0): offset_t=0):
@ -72,7 +72,8 @@ class SLM:
self.xs = list(range(xmin, xmax + 1)) self.xs = list(range(xmin, xmax + 1))
nfilters = len(self.xs) nfilters = len(self.xs)
if include_overall: nfilters +=1 if include_overall:
nfilters += 1
self.include_overall = include_overall self.include_overall = include_overall
self.offset_t = offset_t self.offset_t = offset_t
@ -91,7 +92,7 @@ class SLM:
# This is a bit of a hack, as the 5 is hard-encoded here, but should in # This is a bit of a hack, as the 5 is hard-encoded here, but should in
# fact be coming from somewhere else.. # fact be coming from somewhere else..
sos_overall = np.array([1,0,0,1,0,0]*5, dtype=float) sos_overall = np.array([1, 0, 0, 1, 0, 0]*5, dtype=float)
if fbdesigner is not None: if fbdesigner is not None:
assert fbdesigner.fs == fs assert fbdesigner.fs == fs
@ -108,14 +109,14 @@ class SLM:
# Create a unit impulse response filter, every third index equals # Create a unit impulse response filter, every third index equals
# 1, so b0 = 1 and a0 is 1 (by definition) # 1, so b0 = 1 and a0 is 1 (by definition)
# a0 = 1, b0 = 1, rest is zero # a0 = 1, b0 = 1, rest is zero
sos[:,-1] = sos_overall sos[:, -1] = sos_overall
self.nom_txt.append('overall') self.nom_txt.append('overall')
else: else:
# No filterbank, means we do only compute the overall values. This # No filterbank, means we do only compute the overall values. This
# means that in case of include_overall, it creates two overall # means that in case of include_overall, it creates two overall
# channels. That would be confusing, so we do not allow it. # channels. That would be confusing, so we do not allow it.
sos = sos_overall[:,np.newaxis] sos = sos_overall[:, np.newaxis]
self.nom_txt.append('overall') self.nom_txt.append('overall')
# Downsampling factor, determine from single pole low pass filter time # Downsampling factor, determine from single pole low pass filter time
@ -137,7 +138,6 @@ class SLM:
# Initialize counter to 0 # Initialize counter to 0
self.N = 0 self.N = 0
def run(self, data): def run(self, data):
""" """
Add new fresh timedata to the Sound Level Meter Add new fresh timedata to the Sound Level Meter
@ -169,7 +169,6 @@ class SLM:
return output return output
def return_as_dict(self, dat): def return_as_dict(self, dat):
""" """
Helper function used to return resulting data in a proper way. Helper function used to return resulting data in a proper way.