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 , a scalar that grows with loudness and is lifted further when the sound is sharp, rough or slowly fluctuating. This page covers the exact 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 = 37.05 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 = 37.05 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 (the loudness exceeded 5 % of the time), in sone, from the ISO 532-1 Zwicker time-varying model (loudness_zwicker, see Loudness).
  • Sharpness , in acum: the spectral balance towards high frequencies, DIN 45692 (sharpness_din).
  • Roughness , in asper: the harshness of fast (~70 Hz) amplitude modulation, ECMA-418-2 Sottek model (roughness_ecma).
  • Fluctuation strength , 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 by a factor that grows with the sharpness weighting and the combined roughness/fluctuation weighting :

is zero for (sharpness only adds annoyance above that threshold); weights roughness more heavily than fluctuation strength (0.6 vs 0.4). Those two choices are the whole of the modelling judgement in the formula, and they are easier to trust once seen:

Left: the sharpness weighting wS against sharpness from 1 to 5 acum for percentile loudnesses of 10, 30 and 60 sone, flat at zero below 1.75 acum and fanning out above it. Right: psychoacoustic annoyance against a sensation magnitude spent entirely on roughness or entirely on fluctuation strength at a fixed loudness of 30 sone and sharpness of 2 acum, the roughness curve rising to 45.4 against 40.5 for fluctuation strength at a magnitude of 1.5Left: the sharpness weighting wS against sharpness from 1 to 5 acum for percentile loudnesses of 10, 30 and 60 sone, flat at zero below 1.75 acum and fanning out above it. Right: psychoacoustic annoyance against a sensation magnitude spent entirely on roughness or entirely on fluctuation strength at a fixed loudness of 30 sone and sharpness of 2 acum, the roughness curve rising to 45.4 against 40.5 for fluctuation strength at a magnitude of 1.5

A sound has to be sharper than the 1.75 acum reference before sharpness costs anything at all, and the fan above that point is the loudness dependence of . On the right, the same sensation magnitude buys about half again as much annoyance when it arrives as roughness rather than as fluctuation strength.

Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
# `psychoacoustics` is imported by the snippet above.
sharpness = np.linspace(1.0, 5.0, 200)
for n5 in (10.0, 30.0, 60.0):
w_s = [psychoacoustics.psychoacoustic_annoyance(n5, float(s), 0.0, 0.0).w_s
for s in sharpness]
plt.plot(sharpness, w_s, label=f"N5 = {n5:g} sone")
plt.axvline(1.75, linestyle="--")
plt.xlabel("Sharpness S [acum]")
plt.ylabel("Sharpness weighting wS")
plt.legend()
plt.show()
# The same magnitude spent on roughness or on fluctuation strength, at a
# fixed N5 = 30 sone and S = 2.0 acum:
rough = psychoacoustics.psychoacoustic_annoyance(30.0, 2.0, 0.0, 1.5).annoyance
fluct = psychoacoustics.psychoacoustic_annoyance(30.0, 2.0, 1.5, 0.0).annoyance
print(round(rough, 1), round(fluct, 1)) # 45.4 40.5

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.0477
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)

What that number means. carries no unit and no criterion. It is built so that a neutral sound — sharpness at or below 1.75 acum, no roughness, no fluctuation — has a equal to its in sone, and every sharp, rough or fluctuating quality can only push it above that floor. So read as “the loudness this sound is worth once its character is taken into account”: the worked example’s 37.05 against an of 30 sone says the character costs 23 %. Comparison is the point — rank two designs of the same product, or quantify what a fix bought — and no limit exists to quote it against.

The figure above sweeps against for three profiles: a neutral baseline (, , so ), a sharp sound and a rough-and-fluctuating sound, both lifted above the baseline, with the worked example marked.

The model is small enough to draw whole: two weightings and one combination. The diagram traces the worked example through it, sensation by sensation.

Block diagram of the Fastl and Zwicker psychoacoustic annoyance model: the four sensations of the worked example, sharpness of 2.0 acum, percentile loudness N5 of 30 sone, fluctuation strength of 0.5 vacil and roughness of 0.3 asper, feed two weightings, the sharpness weighting wS of 0.1001 that is zero below 1.75 acum and the roughness-and-fluctuation weighting wFR of 0.2125 that weighs roughness 0.6 against 0.4, and combine into PA equal to N5 times one plus the root of wS squared plus wFR squared, giving 37.05; a closing note recalls that a neutral sound sits on the baseline PA equal to N5Block diagram of the Fastl and Zwicker psychoacoustic annoyance model: the four sensations of the worked example, sharpness of 2.0 acum, percentile loudness N5 of 30 sone, fluctuation strength of 0.5 vacil and roughness of 0.3 asper, feed two weightings, the sharpness weighting wS of 0.1001 that is zero below 1.75 acum and the roughness-and-fluctuation weighting wFR of 0.2125 that weighs roughness 0.6 against 0.4, and combine into PA equal to N5 times one plus the root of wS squared plus wFR squared, giving 37.05; a closing note recalls that a neutral sound sits on the baseline PA equal to N5

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

The convenience is only as good as the record it is given, and neither entry point on this page takes a calibration argument, so x must already be calibrated pressure in pascals — see Measuring the input on the loudness page for how that factor is obtained. Beyond calibration: unweighted; one declared operating state rather than a run-up; long enough that is a percentile of something and that the ~4 Hz fluctuation analysis has enough modulation periods, which in practice means several seconds and the same length for every design being compared; and field= matching how the sound was captured. A two-second clip yields an that is nearly and an biased low. A binaural or artificial-head recording is analysed one ear at a time, already equalized to the free or diffuse field, and the louder channel is the one usually reported.

import numpy as np
from phonometry import psychoacoustics
# A raw recording plus its calibration so the guide runs standalone. There is
# no calibration_factor argument here, so x must already be in pascals.
fs = 48000
t = np.arange(int(5.0 * fs)) / fs
x = (1.0 + 0.8 * np.sin(2 * np.pi * 4.0 * t)) * np.sin(2 * np.pi * 1000 * t)
x *= 2e-5 * 10 ** (70 / 20) / np.sqrt(np.mean(x**2)) # calibrated to 70 dB SPL
res = psychoacoustics.psychoacoustic_annoyance_from_signal(x, fs, field="free")
print(f"PA = {res.annoyance:.1f} (N5 = {res.n5:.1f} sone, S = {res.sharpness:.2f} acum,"
f" R = {res.roughness:.2f} asper, F = {res.fluctuation_strength:.2f} vacil)")
res.plot() # the same PA / wS / wFR view, now from the four derived sensations

All four sensations must come from the same record and the same operating state: PA is a combination rule, not a measurement in its own right, so computing over a minute and over two seconds of it silently mixes two different sounds. Quote each sensation with the model that produced it.

Fluctuation strength (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 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 two of the three fluctuation-strength models in the library: the Fastl & Zwicker closed form for amplitude-modulated broadband noise (§3.1) and the Osses et al. (2016) signal model for arbitrary signals (§3.2). The normative Sottek-model fluctuation strength of ECMA-418-2 Clause 9, in vacil_HMS, lives in Sound Quality Metrics — note that this page’s unit is the vacil and that one the vacil_HMS, so the two are not the same number for the same sound.

Left: the closed-form and signal-model fluctuation strength of the same amplitude-modulated broadband noise at 60 dB against modulation frequency, both band-pass around 4 Hz, the signal model peaking at 5.5 vacil against the closed form's 3.7, a factor 1.5 overshoot at 4 Hz. Right: the signal model on the amplitude-modulated 1 kHz tone at 70 dB it was calibrated for, peaking at 1.09 vacil at 4 HzLeft: the closed-form and signal-model fluctuation strength of the same amplitude-modulated broadband noise at 60 dB against modulation frequency, both band-pass around 4 Hz, the signal model peaking at 5.5 vacil against the closed form's 3.7, a factor 1.5 overshoot at 4 Hz. Right: the signal model on the amplitude-modulated 1 kHz tone at 70 dB it was calibrated for, peaking at 1.09 vacil at 4 Hz

The two models on the same stimulus, which is the comparison the caution box below turns into a rule. Both are band-passes centred near 4 Hz, but on AM broadband noise the signal model overshoots the closed form by about half again, because it accumulates modulation band by band across the whole Bark axis. On the AM tone it was calibrated for, right, it behaves.

Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from phonometry import psychoacoustics
# Left panel, the two models on the same stimulus: 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]
rng = np.random.default_rng(3)
tn = np.arange(int(4.0 * fs)) / fs
noise = rng.standard_normal(tn.size)
fm_noise = [0.5, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0]
f_noise = []
for fm in fm_noise:
am = (1.0 + np.sin(2 * np.pi * fm * tn)) * noise
am = am / np.sqrt(np.mean(am ** 2)) * 2e-5 * 10 ** (60 / 20)
f_noise.append(psychoacoustics.fluctuation_strength(am, float(fs)).fluctuation_strength)
print(round(f_noise[3], 1), round(f_bbn[np.argmin(np.abs(fmod - 4.0))], 1)) # 5.5 3.7
fig, ax = plt.subplots()
ax.semilogx(fmod, f_bbn, label="closed form, Eq. 10.2")
ax.semilogx(fm_noise, f_noise, "s--", label="Osses 2016 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]")
ax.legend(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 , level and modulation frequency :

The denominator is the 4 Hz band-pass: it bottoms out near and rises on either side. The result is clamped at 0 (the sensation vanishes below ~20 dB or ). 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 (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(round(res.fluctuation_strength, 2)) # 0.65 vacil for the signal above
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 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 , DIN 45692 , ECMA-418-2 ) 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.15-0.19 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.