Skip to content

Psychoacoustic annoyance and fluctuation strength

Key references: Fastl & Zwicker 2007Osses Vecchi et al. 2016

How annoying a sound is depends on more than how loud it is. Fastl & Zwicker, Psychoacoustics: Facts and Models, combine four psychoacoustic sensations (loudness, sharpness, roughness and fluctuation strength) into a single psychoacoustic annoyance PA, a scalar that grows with loudness and is lifted further when the sound is sharp, rough or slowly fluctuating. This page covers the exact PA model (Eqs 16.2–16.4), the fluctuation strength it consumes, both the closed form for amplitude-modulated broadband noise (Eq. 10.2) and the Osses et al. (2016) signal model, and the signal convenience that derives all four sensations from a recording.

Psychoacoustic annoyance PA against percentile loudness N5 for three sensation profiles: a neutral baseline where PA equals N5, a sharp sound and a rough-and-fluctuating sound both lifted above the baseline, with the worked example PA = 30.82 markedPsychoacoustic annoyance PA against percentile loudness N5 for three sensation profiles: a neutral baseline where PA equals N5, a sharp sound and a rough-and-fluctuating sound both lifted above the baseline, with the worked example PA = 30.82 marked
Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from phonometry import psychoacoustics
n5 = np.linspace(4.0, 60.0, 200)
profiles = [
("Baseline: S = 1.75 acum, F = R = 0", 1.75, 0.0, 0.0),
("Sharp: S = 3.5 acum", 3.5, 0.0, 0.0),
("Rough + fluctuating: F = 1.2 vacil, R = 0.7 asper", 2.0, 1.2, 0.7),
]
fig, ax = plt.subplots()
for label, s, f, r in profiles:
pa = [psychoacoustics.psychoacoustic_annoyance(v, s, f, r).annoyance for v in n5]
ax.plot(n5, pa, label=label)
ex = psychoacoustics.psychoacoustic_annoyance(30.0, 2.0, 0.5, 0.3)
ax.plot([30.0], [ex.annoyance], "o", label=f"Worked example (PA = {ex.annoyance:.2f})")
ax.set_xlabel("Percentile loudness N5 [sone]")
ax.set_ylabel("Psychoacoustic annoyance PA")
ax.legend()
plt.show()

Psychoacoustic annoyance rests on four hearing sensations, each with its own model and unit in the library:

  • Loudness: the percentile loudness N5 (the loudness exceeded 5 % of the time), in sone, from the ISO 532-1 Zwicker time-varying model (loudness_zwicker, see Loudness).
  • Sharpness S, in acum: the spectral balance towards high frequencies, DIN 45692 (sharpness_din).
  • Roughness R, in asper: the harshness of fast (~70 Hz) amplitude modulation, ECMA-418-2 Sottek model (roughness_ecma).
  • Fluctuation strength F, in vacil: the sensation of slow (~4 Hz) loudness fluctuation (§3 below).

2. Psychoacoustic annoyance (Eqs 16.2–16.4)

Section titled “2. Psychoacoustic annoyance (Eqs 16.2–16.4)”

The exact model (Fastl & Zwicker Eq. 16.2; origin Widmann 1992) scales N5 by a factor that grows with the sharpness weighting wS and the combined roughness/fluctuation weighting wFR:

wS is zero for S ≤ 1.75 acum (sharpness only adds annoyance above that threshold); wFR weights roughness more heavily than fluctuation strength (0.6 vs 0.4). psychoacoustic_annoyance takes the four quantities directly and returns the annoyance together with the two intermediate weightings:

from phonometry import psychoacoustics
res = psychoacoustics.psychoacoustic_annoyance(30.0, 2.0, 0.5, 0.3) # N5, S, F, R
print(round(res.annoyance, 4)) # 37.0478
print(round(res.w_s, 4), round(res.w_fr, 4)) # 0.1001 0.2125
res.plot() # PA beside its wS and wFR weightings (needs matplotlib)

The figure above sweeps PA against N5 for three profiles: a neutral baseline (S = 1.75 acum, F = R = 0, so PA = N5), a sharp sound and a rough-and-fluctuating sound, both lifted above the baseline, with the worked example marked.

psychoacoustic_annoyance_from_signal is a convenience that derives all four sensations from a calibrated pressure signal and combines them: N5 from the ISO 532-1 Zwicker time-varying loudness, S from DIN 45692 sharpness, R from the ECMA-418-2 Sottek roughness and F from the fluctuation-strength signal model.

from phonometry import psychoacoustics
res = psychoacoustics.psychoacoustic_annoyance_from_signal(x, fs, field="free")
print(res.annoyance, res.n5, res.sharpness, res.roughness, res.fluctuation_strength)
res.plot() # the same PA / wS / wFR view, now from the four derived sensations

Fluctuation strength F (vacil) quantifies the perception of slow loudness fluctuation. Like roughness it is a band-pass sensation of the modulation frequency, but it peaks about an order of magnitude lower, at fmod ≈ 4 Hz rather than the ~70 Hz roughness peak. By definition, a 1 kHz tone at 60 dB, 100 % amplitude-modulated at 4 Hz, produces 1 vacil. This page covers the Fastl & Zwicker models; the normative Sottek-model fluctuation strength of ECMA-418-2 Clause 9 lives in Sound Quality Metrics.

Fluctuation strength versus modulation frequency on a log axis: the closed-form AM broadband-noise curve and the AM-tone signal-model sweep both show a band-pass characteristic peaking at 4 HzFluctuation strength versus modulation frequency on a log axis: the closed-form AM broadband-noise curve and the AM-tone signal-model sweep both show a band-pass characteristic peaking at 4 Hz
Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from phonometry import psychoacoustics
# Exact closed form (Eq. 10.2), AM broadband noise at 60 dB, 100 % modulation:
fmod = np.logspace(np.log10(0.5), np.log10(32.0), 240)
f_bbn = [psychoacoustics.fluctuation_strength_am_noise(60.0, 1.0, fm) for fm in fmod]
# Osses 2016 signal model on a 1 kHz / 70 dB AM tone over the same sweep:
fs = 48000
t = np.arange(int(2.0 * fs)) / fs
carrier = np.sin(2 * np.pi * 1000 * t)
fm_tone = [1.0, 2.0, 4.0, 8.0, 16.0, 32.0]
f_tone = []
for fm in fm_tone:
am = (1.0 + np.sin(2 * np.pi * fm * t)) * carrier
am = am / np.sqrt(np.mean(am ** 2)) * 2e-5 * 10 ** (70 / 20)
f_tone.append(psychoacoustics.fluctuation_strength(am, float(fs)).fluctuation_strength)
fig, ax = plt.subplots()
ax.semilogx(fmod, f_bbn, label="AM broadband noise (closed form)")
ax2 = ax.twinx()
ax2.plot(fm_tone, f_tone, "s--", color="tab:green", label="AM tone (signal model)")
ax.axvline(4.0, ls="--", color="0.4")
ax.set_xlabel("Modulation frequency f_mod [Hz]")
ax.set_ylabel("Fluctuation strength F [vacil]")
h1, l1 = ax.get_legend_handles_labels()
h2, l2 = ax2.get_legend_handles_labels()
ax.legend(h1 + h2, l1 + l2, loc="upper right")
plt.show()

3.1 Closed form for AM broadband noise (Eq. 10.2)

Section titled “3.1 Closed form for AM broadband noise (Eq. 10.2)”

For sinusoidally amplitude-modulated broadband noise, Fastl & Zwicker give a closed form (Eq. 10.2) in modulation factor m, level L and modulation frequency fmod:

The denominator is the 4 Hz band-pass: it bottoms out near fmod ≈ 3.7 Hz and rises on either side. The result is clamped at 0 (the sensation vanishes below ~20 dB or m < 0.2). This exact form is the value to quote for AM broadband noise.

from phonometry import psychoacoustics
print(round(psychoacoustics.fluctuation_strength_am_noise(60.0, 1.0, 4.0), 4)) # 3.6943 vacil

fluctuation_strength implements the Osses et al. (2016) signal model: it builds an excitation pattern over 47 auditory filters, extracts the ~4 Hz envelope modulation per band, weights and combines it, and returns the overall F (vacil) plus the specific fluctuation strength over the Bark axis and the time-dependent trace.

from phonometry import psychoacoustics
res = psychoacoustics.fluctuation_strength(x, fs)
print(res.fluctuation_strength) # vacil
res.plot() # specific fluctuation strength F′(z) over the Bark axis (needs matplotlib)
Specific fluctuation strength over the Bark axis for a 1 kHz tone at 70 dB SPL fully amplitude-modulated at 4 Hz: the sensation is concentrated in the critical bands around the carrier near 8 Bark and integrates to about 1.1 vacilSpecific fluctuation strength over the Bark axis for a 1 kHz tone at 70 dB SPL fully amplitude-modulated at 4 Hz: the sensation is concentrated in the critical bands around the carrier near 8 Bark and integrates to about 1.1 vacil
Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from phonometry import psychoacoustics
# The reference-like stimulus: a 1 kHz tone at 70 dB SPL, 100 % amplitude
# modulated at 4 Hz, where the sensation peaks.
fs = 48000
t = np.arange(int(2.0 * fs)) / fs
am = (1.0 + np.sin(2 * np.pi * 4.0 * t)) * np.sin(2 * np.pi * 1000 * t)
am = am / np.sqrt(np.mean(am ** 2)) * 2e-5 * 10 ** (70 / 20)
res = psychoacoustics.fluctuation_strength(am, float(fs))
print(round(res.fluctuation_strength, 2)) # 1.09 vacil
# One line: the specific fluctuation strength over the Bark axis.
res.plot()
plt.show()
# Or draw f'(z) by hand from the arrays the result carries:
fig, ax = plt.subplots()
ax.fill_between(res.bark_axis, res.specific, alpha=0.3)
ax.plot(res.bark_axis, res.specific)
ax.set_xlabel("Critical-band rate z [Bark]")
ax.set_ylabel("Specific fluctuation strength [vacil/Bark]")
plt.show()

The sensation stays where the modulated energy is, in the critical bands around the carrier, which is exactly why the closed form of §3.1 and the signal model diverge for broadband modulated noise: there the modulation is spread over the whole Bark axis and the band-by-band model accumulates more of it than the single closed form allows.

Covered. Fastl & Zwicker’s Psychoacoustics: Facts and Models gives the exact PA model, Eqs 16.2-16.4 (origin Widmann 1992), run by psychoacoustic_annoyance. It also gives the closed form for AM broadband noise, Eq. 10.2, run by fluctuation_strength_am_noise. The Osses et al. (2016) signal model for fluctuation strength runs as fluctuation_strength, cross-checked against its Table 1 literature values and the SQAT reference. psychoacoustic_annoyance_from_signal derives the four inputs from a signal (ISO 532-1 Zwicker N5, DIN 45692 S, ECMA-418-2 R) and combines them with the exact model.

Not covered. The Osses 2016 model’s accuracy for FM tones is explicitly not pursued. Only AM stimuli are validated here. The front-end also departs from the paper’s exact framing (2 s frames, 90 % overlap, absolute-threshold gating). As a result, a steady 1 kHz tone reads about 0.09 vacil instead of 0, and steady broadband noise reads about 0.4 vacil. For AM broadband noise, quote fluctuation_strength_am_noise (§3.1) instead of fluctuation_strength, which overshoots that stimulus. The normative Sottek-model fluctuation strength of ECMA-418-2 Clause 9 is not implemented on this page: it lives in Sound Quality Metrics.

  • Fastl, H., & Zwicker, E. (2007). Psychoacoustics: Facts and models (3rd ed.). Springer. https://doi.org/10.1007/978-3-540-68888-4The source of the psychoacoustic-annoyance model of section 2 (Eqs 16.2-16.4, chapter 16; origin Widmann 1992) and of the closed-form fluctuation strength for AM broadband noise of section 3.1 (Eq. 10.2, chapter 10).
  • Felix Greco, G., Merino-Martínez, R., Osses, A., & Lotinga, M. J. B. (2025). SQAT: A sound quality analysis toolbox for MATLAB. GitHub. https://doi.org/10.5281/zenodo.7934709The open MATLAB reference (open-source software) used as the numeric oracle for the fluctuation-strength cross-checks on this page.
  • Osses Vecchi, A., García León, R., & Kohlrausch, A. (2016). Modelling the sensation of fluctuation strength. Proceedings of Meetings on Acoustics, 28, 050005. https://doi.org/10.1121/2.0000410The fluctuation-strength signal model implemented in section 3.2 (presented at ICA 2016; clean-room, with no numeric standard, calibrated to 1 vacil at the reference AM tone), including the Table 1 literature values used as its cross-check.
Created and maintained by· GitHub· PyPI· All projects