Skip to content

Commit f1bc676

Browse files
authored
Merge pull request #25 from mglesser/master
Documentation updates + bug fix in Loudness computation
2 parents 0b69ea4 + b916a73 commit f1bc676

19 files changed

+579
-345
lines changed

CITATION.cff

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
cff-version: 1.2.0
2+
message: "If you use this software, please cite it as below."
3+
authors:
4+
- family-names: Green Forge Coop
5+
given-names:
6+
title: MOSQITO
7+
version: 0.3.3
8+
doi: 10.5281/zenodo.5419590
9+
date-released: 2021-09-03
10+
keywords:
11+
- audio
12+
- python
13+
- signal-processing
14+
- audio-analysis
15+
- acoustic
16+
- sound-quality
17+
- sq-metrics
18+
license: Apache-2.0

README.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,34 @@ It is written in Python, one of the most popular free programming language in th
3535

3636
Tutorials are available in the [tutorials](./tutorials/) folder. Documentation and validation of the MOSQITO functions are available in the [documentation](./documentation/) folder.
3737

38+
## Getting MOSQITO
39+
MOSQITO is available on [pip](https://pypi.org/project/pip/). Simply type in a shell the following command:
40+
41+
pip install mosqito
42+
43+
This command line should download and install MOSQITO on your computer, along with all the needed dependencies.
44+
45+
If you need to import .uff or .unv files, you will need the pyuff package dependency. Note that 'pyuff' is released under the GPL license which prevents MOSQITO from being used in other software that must be under a more permissive license. To include the 'pyuff' dependancy anyway, type the following command:
46+
47+
pip install mosqito[uff]
48+
3849
## Contact
3950

40-
You can contact us on Github by opening an issue (to request a feature, ask a question or report a bug).
51+
You can contact us on Github by opening an issue (to request a feature, ask a question or report a bug).
52+
53+
## Citing MOSQITO
54+
55+
If you are using MOSQITO in your research activities, please help our scientific visibility by citing our work! You can use the following citation in APA format:
56+
57+
Green Forge Coop. MOSQITO [Computer software]. https://doi.org/10.5281/zenodo.5284054
58+
59+
If you need to cite the current release of MOSQITO, please use the "Cite this repository" feature in the "About" section of this Github repository.
60+
61+
62+
## Publications citing MOSQITO
63+
64+
Glesser, M., Ni, S., Degrendele, K., Wanty, S., & Le Besnerais, J. (2021, October 13-14). *Sound quality analysis of Electric Drive Units under different switching control strategies* [Paper presentation]. Automotive NVH Comfort Congress 2021, Le Mans , France.
65+
66+
San Millán-Castillo, R., Latorre-Iglesias, E., Glesser, M., Wanty, S., Jiménez-Caminero, D., & Álvarez-Jimeno, J.M. (2021). MOSQITO: an open-source and free toolbox for sound quality metrics in the industry and education. *INTER-NOISE and NOISE-CON Congress and Conference Proceedings*, 12, 1164-1175. https://doi.org/10.3397/IN-2021-1767
4167

42-
## How to cite MOSQITO
4368

44-
If you use MOSQITO for your research activities and need to cite the software in a publication, please use the following citation:
45-
TODO

mosqito/functions/loudness_zwicker/loudness_zwicker_nonlinear_decay.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def calc_nl_loudness(core_loudness):
2626
"""
2727
# Initialization
2828
sample_rate = 2000
29-
nl_loudness = core_loudness
29+
nl_loudness = core_loudness.copy()
3030
# Factor for virtual upsampling/inner iterations
3131
nl_iter = 24
3232
# Time constants for non_linear temporal decay
@@ -52,17 +52,17 @@ def calc_nl_loudness(core_loudness):
5252
]
5353
nl_lp = {"B": B}
5454

55-
for i_cl in np.arange(np.shape(core_loudness)[0]):
55+
for i_cl in np.arange(np.shape(nl_loudness)[0]):
5656
# At beginning capacitors C1 and C2 are discharged
5757
nl_lp["uo_last"] = 0
5858
nl_lp["u2_last"] = 0
5959

60-
for i_time in np.arange(np.shape(core_loudness)[1] - 1):
60+
for i_time in np.arange(np.shape(nl_loudness)[1] - 1):
6161
# interpolation steps between current and next sample
6262
delta = (
63-
core_loudness[i_cl, i_time + 1] - core_loudness[i_cl, i_time]
63+
nl_loudness[i_cl, i_time + 1] - nl_loudness[i_cl, i_time]
6464
) / nl_iter
65-
ui = core_loudness[i_cl, i_time]
65+
ui = nl_loudness[i_cl, i_time]
6666
nl_lp = calc_nl_lp(ui, nl_lp)
6767
nl_loudness[i_cl, i_time] = nl_lp["uo_last"]
6868
ui += delta
@@ -71,9 +71,9 @@ def calc_nl_loudness(core_loudness):
7171
for i_in in np.arange(1, nl_iter):
7272
nl_lp = calc_nl_lp(ui, nl_lp)
7373
ui += delta
74-
nl_lp = calc_nl_lp(core_loudness[i_cl, i_time + 1], nl_lp)
74+
nl_lp = calc_nl_lp(nl_loudness[i_cl, i_time + 1], nl_lp)
7575
nl_loudness[i_cl, i_time + 1] = nl_lp["uo_last"]
76-
return core_loudness
76+
return nl_loudness
7777

7878

7979
def calc_nl_lp(ui, nl_lp):
@@ -104,7 +104,7 @@ def calc_nl_lp(ui, nl_lp):
104104
uo = ui # lower than ui
105105
u2 = uo
106106
else:
107-
if abs(ui - nl_lp["uo_last"] < 1e-5): # case 2 (charge)
107+
if abs(ui - nl_lp["uo_last"]) < 1e-5: # case 2 (charge)
108108
uo = ui
109109
if uo > nl_lp["u2_last"]: # case 2.1
110110
u2 = (nl_lp["u2_last"] - ui) * nl_lp["B"][5] + ui

mosqito/functions/loudness_zwicker/loudness_zwicker_shared.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,219 @@ def calc_main_loudness(spec_third, field_type):
169169
return nm
170170

171171

172+
def calc_main_loudness_ea(spec_third, field_type):
173+
"""Calculate core loudness
174+
175+
The code is based on BASIC program published in "Program for
176+
calculating loudness according to DIN 45631 (ISO 532B)", E.Zwicker
177+
and H.Fastl, J.A.S.J (E) 12, 1 (1991).
178+
It also corresponds to the following functions of the C program
179+
published with ISO 532-1:2017:
180+
- corr_third_octave_intensities
181+
- f_calc_lcbs
182+
- f_calc_core_loudness
183+
- f_corr_loudness
184+
185+
Parameters
186+
----------
187+
spec_third : numpy.ndarray
188+
A third octave band spectrum [dB ref. 2e-5 Pa]
189+
field_type : str
190+
Type of soundfield correspondin to spec_third ("free" or
191+
"diffuse")
192+
193+
Outputs
194+
-------
195+
nm : numpy.ndarray
196+
Core loudness
197+
"""
198+
#
199+
# Date tables definition (variable names and description according to
200+
# Zwicker:1991)
201+
# Ranges of 1/3 octave band levels for correction at low frequencies
202+
# according to equal loudness contours
203+
rap = np.array([45, 55, 65, 71, 80, 90, 100, 120])
204+
# Reduction of 1/3 octave band levels at low frequencies according to
205+
# equal loudness contours within the eight ranges defined by RAP
206+
dll = np.array(
207+
[
208+
(-32, -24, -16, -10, -5, 0, -7, -3, 0, -2, 0),
209+
(-29, -22, -15, -10, -4, 0, -7, -2, 0, -2, 0),
210+
(-27, -19, -14, -9, -4, 0, -6, -2, 0, -2, 0),
211+
(-25, -17, -12, -9, -3, 0, -5, -2, 0, -2, 0),
212+
(-23, -16, -11, -7, -3, 0, -4, -1, 0, -1, 0),
213+
(-20, -14, -10, -6, -3, 0, -4, -1, 0, -1, 0),
214+
(-18, -12, -9, -6, -2, 0, -3, -1, 0, -1, 0),
215+
(-15, -10, -8, -4, -2, 0, -3, -1, 0, -1, 0),
216+
]
217+
)
218+
# Critical band level at absolute threshold without taking into
219+
# account the transmission characteristics of the ear
220+
ltq = np.array([30, 18, 12, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3])
221+
# Correction of levels according to the transmission characteristics
222+
# of the ear
223+
a0 = np.array(
224+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.5, -1.6, -3.2, -5.4, -5.6, -4, -1.5, 2, 5, 12]
225+
)
226+
# Level difference between free and diffuse sound fields
227+
ddf = np.array(
228+
[
229+
0,
230+
0,
231+
0.5,
232+
0.9,
233+
1.2,
234+
1.6,
235+
2.3,
236+
2.8,
237+
3,
238+
2,
239+
0,
240+
-1.4,
241+
-2,
242+
-1.9,
243+
-1,
244+
0.5,
245+
3,
246+
4,
247+
4.3,
248+
4,
249+
]
250+
)
251+
# Adaptation of 1/3 oct. band levels to the corresponding critical
252+
# band level
253+
dcb = np.array(
254+
[
255+
-0.25,
256+
-0.6,
257+
-0.8,
258+
-0.8,
259+
-0.5,
260+
0,
261+
0.5,
262+
1.1,
263+
1.5,
264+
1.7,
265+
1.8,
266+
1.8,
267+
1.7,
268+
1.6,
269+
1.4,
270+
1.2,
271+
0.8,
272+
0.5,
273+
0,
274+
-0.5,
275+
]
276+
)
277+
#
278+
# Correction of 1/3 oct. band levels according to equal loudness
279+
# contours 'xp' and calculation of the intensities for 1/3 oct.
280+
# bands up to 315 Hz
281+
282+
# Prepare al arrays to work with
283+
spec_third_aux = spec_third[: dll.shape[1], :]
284+
spec_third_aux[:, -1] = 0
285+
286+
# Convert rap, dll in 3 dimensional array
287+
# 1. generate the array shape
288+
base_mat = np.ones(dll.shape[0] * dll.shape[1] * spec_third_aux.shape[1]).reshape(
289+
dll.shape[0], dll.shape[1], spec_third_aux.shape[1]
290+
)
291+
# 2. start saving data in rap and DLL in a 3D array
292+
rap_mat = np.array(
293+
[base_mat[:, i, :].T * rap for i in np.arange(dll.shape[1])]
294+
).transpose(2, 0, 1)
295+
dll_mat = np.array(
296+
[np.multiply(base_mat[:, :, i], dll) for i in np.arange(spec_third.shape[1])]
297+
).transpose(1, 2, 0)
298+
spec_third_aux_mat = np.array(
299+
[
300+
np.multiply(base_mat[i, :, :], spec_third_aux)
301+
for i in np.arange(dll.shape[0])
302+
]
303+
)
304+
# create the the array rap-dll
305+
rap_dll_mat = rap_mat - dll_mat
306+
307+
# This part substitutes the while loop.
308+
# create the mask to operate
309+
logic_mat = spec_third_aux_mat > rap_dll_mat
310+
dll_result = dll_mat[0, :, :]
311+
dll_result[logic_mat[0, :, :]] = 0
312+
for i in np.arange(1, dll_mat.shape[0] - 1):
313+
mask = np.logical_xor(logic_mat[i - 1, :, :], logic_mat[i, :, :])
314+
dll_result[mask] = dll_mat[i, mask]
315+
316+
xp = dll_result + spec_third_aux
317+
ti = np.power(10, (xp / 10))
318+
319+
# Determination of levels LCB(1), LCB(2) and LCB(3) within the
320+
# first three critical bands
321+
322+
gi = np.zeros([3, dll_result.shape[1]])
323+
gi[0, :] = ti[0:6, :].sum(axis=0)
324+
gi[1, :] = ti[6:9, :].sum(axis=0)
325+
gi[2, :] = ti[9:11, :].sum(axis=0)
326+
327+
logic_gi = gi > 0
328+
lcb = np.zeros([3, dll_result.shape[1]])
329+
lcb = 10 * np.log10(gi[logic_gi])
330+
lcb = lcb.reshape(3, dll_result.shape[1])
331+
332+
# Calculation of main loudness
333+
s = 0.25
334+
nm = np.zeros([20, spec_third_aux.shape[1]])
335+
le = np.copy(spec_third[8:, :])
336+
# le = le.reshape((20))
337+
le[0:3, :] = lcb
338+
a0_mat = np.ones(a0.shape[0] * spec_third.shape[1]).reshape(
339+
a0.shape[0], spec_third.shape[1]
340+
)
341+
a0_mat = (a0_mat.T * a0).T
342+
le = le - a0_mat
343+
344+
if field_type == "diffuse":
345+
ddf_mat = np.ones(ddf.shape[0] * spec_third.shape[1]).reshape(
346+
ddf.shape[0], spec_third.shape[1]
347+
)
348+
ddf_mat = (ddf_mat.T * ddf).T
349+
le += ddf_mat
350+
351+
ltq_mat = np.ones(ltq.shape[0] * spec_third.shape[1]).reshape(
352+
ltq.shape[0], spec_third.shape[1]
353+
)
354+
ltq_mat = (ltq_mat.T * ltq).T
355+
i = le > ltq_mat
356+
dcb_mat = np.ones(dcb.shape[0] * spec_third.shape[1]).reshape(
357+
dcb.shape[0], spec_third.shape[1]
358+
)
359+
dcb_mat = (dcb_mat.T * dcb).T
360+
le[i] -= dcb_mat[i]
361+
362+
mp1 = 0.0635 * np.power(10, 0.025 * ltq_mat)
363+
mp1[i == False] = 0
364+
mp2 = np.power(1 - s + s * np.power(10, 0.1 * (le - ltq_mat)), 0.25) - 1
365+
mp2[i == False] = 0
366+
367+
nm = np.multiply(mp1, mp2)
368+
369+
nm[i == False] = 0
370+
nm[nm < 0] = 0
371+
current_shape = nm.shape
372+
nm = np.append(nm, np.zeros(current_shape[1])).reshape(
373+
current_shape[0] + 1, current_shape[1]
374+
)
375+
#
376+
# Correction of specific loudness in the lowest critical band
377+
# taking into account the dependance of absolute threshold
378+
# within this critical band
379+
korry = 0.4 + 0.32 * nm[0] ** 0.2
380+
nm[0, korry <= 1] *= korry
381+
nm[:, -1] = 0
382+
return nm
383+
384+
172385
def calc_slopes(nm):
173386
""""""
174387
# Upper limits of approximated critical bands in terms of critical

mosqito/functions/loudness_zwicker/loudness_zwicker_time.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# Local applications imports
1111
from mosqito.functions.loudness_zwicker.loudness_zwicker_shared import (
1212
calc_main_loudness,
13+
calc_main_loudness_ea,
1314
)
1415
from mosqito.functions.loudness_zwicker.loudness_zwicker_nonlinear_decay import (
1516
calc_nl_loudness,
@@ -54,10 +55,14 @@ def loudness_zwicker_time(third_octave_levels, field_type):
5455
"""
5556

5657
# Calculate core loudness
57-
num_sample_level = np.shape(third_octave_levels)[1]
58-
core_loudness = np.zeros((21, num_sample_level))
59-
for i in np.arange(num_sample_level - 1):
60-
core_loudness[:, i] = calc_main_loudness(third_octave_levels[:, i], field_type)
58+
# num_sample_level = np.shape(third_octave_levels)[1]
59+
# core_loudness = np.zeros((21, num_sample_level))
60+
# for i in np.arange(num_sample_level - 1):
61+
# core_loudness[:, i] = calc_main_loudness(third_octave_levels[:, i], field_type)
62+
63+
# Calculate core loudness (vectorized version, need debugging)
64+
core_loudness = calc_main_loudness_ea(third_octave_levels, field_type)
65+
6166
#
6267
# Nonlinearity
6368
core_loudness = calc_nl_loudness(core_loudness)

0 commit comments

Comments
 (0)