Skip to content

Surface Scattering, Diffusion and In-situ Absorption

Standards: ISO 17497ISO 13472ISO 9613Key references: Cox & D'Antonio 2017Hargreaves et al. 2000

How a surface returns incident sound (how much it scatters away from the specular direction, how uniformly it spreads what it scatters, and how much it absorbs) is measured by a family of dedicated methods. The reverberation room gives the random-incidence scattering coefficient of a surface by comparing decays with the sample held still and rotating (ISO 17497-1). A free-field goniometer measures the polar response of the reflected sound and condenses it into a diffusion coefficient (ISO 17497-2). And out on a road, a loudspeaker and a single microphone recover the in-situ absorption of the pavement, either over an extended surface by subtracting the incident wave (ISO 13472-1) or through a small tube pressed onto the surface (ISO 13472-2). This page covers all four.

The scattering and diffusion coefficients answer different questions and are not interchangeable: scattering is how much energy leaves the specular direction; diffusion is how evenly the reflected energy is spread over angle.

1. Random-incidence scattering coefficient (ISO 17497-1)

Section titled “1. Random-incidence scattering coefficient (ISO 17497-1)”

The scattering coefficient is the fraction of reflected energy that does not leave the surface in the specular direction. ISO 17497-1 measures it in a reverberation room from four reverberation-time situations: with the test sample mounted on a turntable and held stationary, and with the turntable rotating (which averages the phase-coherent specular reflection away), each with and without a reflecting base plate.

ISO 17497-1 random-incidence scattering setup: a reverberation room with the test sample on a turntable, a rotating loudspeaker boom and a microphone, measuring reverberation time with the sample stationary (giving the random-incidence absorption) and rotating (giving the specular absorption), from which the scattering coefficient is derivedISO 17497-1 random-incidence scattering setup: a reverberation room with the test sample on a turntable, a rotating loudspeaker boom and a microphone, measuring reverberation time with the sample stationary (giving the random-incidence absorption) and rotating (giving the specular absorption), from which the scattering coefficient is derived

Absorption from reverberation time (Clause 6). Each situation converts to a Sabine absorption coefficient with the standard’s own air-attenuation term:

where is the room volume, the sample area, the reverberation time, the speed of sound (Eq. (2): ) and the power attenuation coefficient of air. The stationary pair gives the random-incidence absorption (Eq. (1)); the rotating pair gives the specular absorption (Eq. (4)).

Scattering coefficient (Eq. (5)). The two combine into

A fully specular surface reflects all its non-absorbed energy in the specular direction, so and ; a strong diffuser sends energy everywhere, raising towards 1 and towards 1.

from phonometry import materials
# Four reverberation-time situations reduced to two absorption coefficients.
# alpha_s from the stationary pair (Eq. 1); alpha_spec from the rotating pair
# (Eq. 4). V = 200 m^3, S = 10 m^2, c = 343.2 m/s throughout.
alpha_s = materials.random_incidence_absorption(200.0, 10.0, c1=343.2, T1=8.0,
c2=343.2, T2=6.0)
alpha_spec = materials.specular_absorption_coefficient(200.0, 10.0, c3=343.2, T3=7.5,
c4=343.2, T4=5.0)
s = materials.scattering_coefficient(alpha_spec, alpha_s) # Eq. (5)
print(round(float(alpha_s), 4)) # 0.1343
print(round(float(alpha_spec), 4)) # 0.2148
print(round(float(s), 4)) # 0.0931

Over a full one-third-octave measurement, scattering_coefficient_spectrum pairs the per-band and with their band centres and returns a plottable ScatteringResult:

import numpy as np
from phonometry import materials
# A 13-band measurement (250-4000 Hz): the random-incidence absorption alpha_s
# (stationary sample) and the specular absorption alpha_spec (rotating
# turntable). A diffuser scatters more with frequency, so s(f) rises.
freqs = np.array([250, 315, 400, 500, 630, 800, 1000,
1250, 1600, 2000, 2500, 3150, 4000], float)
alpha_s = np.full_like(freqs, 0.10)
alpha_spec = 0.11 + 0.75 * (np.log10(freqs / 250) / np.log10(4000 / 250))
result = materials.scattering_coefficient_spectrum(freqs, alpha_spec, alpha_s)
print(np.round(result.scattering[[0, 6, 12]], 3)) # [0.011 0.428 0.844]
result.plot() # s(f) on a log-frequency axis, 0 to 1 (needs matplotlib)
The random-incidence scattering coefficient s of a diffusing surface over the 13 one-third-octave bands from 250 to 4000 Hz, rising smoothly from near zero at low frequency towards 0.84 at 4 kHzThe random-incidence scattering coefficient s of a diffusing surface over the 13 one-third-octave bands from 250 to 4000 Hz, rising smoothly from near zero at low frequency towards 0.84 at 4 kHz

The scattering coefficient climbs with frequency: at low frequency the surface relief is small compared with the wavelength and the reflection stays specular (); as the wavelength shrinks the relief scatters more energy out of the specular direction ().

Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from phonometry import materials
# A 13-band measurement (250-4000 Hz): the random-incidence absorption alpha_s
# (stationary sample) and the specular absorption alpha_spec (rotating
# turntable). A diffuser scatters more with frequency, so s(f) rises.
freqs = np.array([250, 315, 400, 500, 630, 800, 1000,
1250, 1600, 2000, 2500, 3150, 4000], float)
alpha_s = np.full_like(freqs, 0.10)
alpha_spec = 0.11 + 0.75 * (np.log10(freqs / 250) / np.log10(4000 / 250))
result = materials.scattering_coefficient_spectrum(freqs, alpha_spec, alpha_s)
# result is the ScatteringResult computed above. One line:
result.plot()
plt.show()
# By hand, from the result's fields, mirroring what ScatteringResult.plot() draws:
fig, ax = plt.subplots()
ax.semilogx(result.frequencies, result.scattering, "o-", color="#1f77b4")
ax.set_xlabel("Frequency [Hz]")
ax.set_ylabel("Scattering coefficient s")
ax.set_ylim(0.0, 1.0)
ax.set_title("Random-incidence scattering coefficient (ISO 17497-1)")
plt.show()

Base-plate check (Clause 6.4, Table 1). The empty base plate must itself scatter only negligibly, or it would bias the result. ISO 17497-1 caps the base-plate scattering coefficient per one-third-octave band; the library exposes those limits and a checker.

from phonometry import materials
# The normative per-band ceilings (Table 1): 0.05 up to 500 Hz, rising to 0.25.
# materials.BASE_PLATE_BANDS is the band tuple; materials.BASE_PLATE_MAX_SCATTERING maps band -> ceiling.
print(materials.BASE_PLATE_BANDS[0], materials.BASE_PLATE_MAX_SCATTERING[100]) # 100 0.05
# A base plate whose measured scattering stays under the ceiling passes silently;
# an over-limit band raises a ScatteringDiffusionWarning listing the offenders.
materials.check_base_plate_scattering([0.02] * len(materials.BASE_PLATE_BANDS))

ScatteringResult.report(path) renders a one-page accredited scattering test report (ISO 17497-1): a metadata header (specimen, sample area , room volume , test room, climate), the per-one-third-octave table of the random-incidence absorption and the scattering coefficient beside the curve on a categorical band axis, and a boxed characterisation headline. It is a characterisation, so there is no pass/fail verdict. verbose=True adds the specular absorption column, and language="es" renders the Spanish fiche. It needs the report extra (pip install phonometry[report]).

ISO 17497-1 scattering-coefficient example report (PDF)

One-page random-incidence scattering fiche: a metadata header, the per-one-third-octave table of the random-incidence absorption and the scattering coefficient beside the s(f) band-axis curve, and the boxed characterisation headline over the tested frequency range.

Download the report (PDF)

Random-incidence scattering fiche (ScatteringResult.report), the alpha_s and s spectra.

The diffusion coefficient measures the spatial uniformity of the reflected sound, not how much is scattered. A goniometer sweeps a receiver over a polar arc and records the reflected level at each angle; the coefficient is the normalised autocorrelation of the polar energy distribution.

ISO 17497-2 free-field diffusion goniometer: a test sample on a turntable, a fixed loudspeaker source, and a semicircular arc of receiver microphones sampling the reflected polar response, from which the autocorrelation diffusion coefficient is computedISO 17497-2 free-field diffusion goniometer: a test sample on a turntable, a fixed loudspeaker source, and a semicircular arc of receiver microphones sampling the reflected polar response, from which the autocorrelation diffusion coefficient is computed

Autocorrelation (Formula (5)). For receivers at equal angular spacing, with the band energy at receiver ,

A perfectly uniform polar response ( all equal) gives ; a single sharp specular lobe gives . When receivers subtend unequal solid angles, Formula (6) area-weights each energy by from Formula (8), and those area factors are evaluated in radians, which is why a 5° spacing at the zenith produces a weight near 1.57, not 51.9.

The animation below runs that goniometer experiment numerically: the same plane wavefront hits a flat rigid panel and a Schroeder diffuser (an N = 7 quadratic-residue profile), and the scattered energy on the receiver arc turns a collimated specular beam (d = 0.32) into a wide fan (d = 0.63).

A 2D FDTD simulation of a plane wavefront hitting a flat rigid panel and a Schroeder quadratic-residue diffuser side by side. The flat panel throws a collimated specular beam back while the diffuser's phase-step wells spread the same energy into a wide fan, and the scattered field on a receiver arc yields diffusion coefficients of 0.32 versus 0.63.

Download the animation (WebM)

A 2D FDTD simulation of a plane wavefront hitting a flat rigid panel and a Schroeder quadratic-residue diffuser side by side. The flat panel throws a collimated specular beam back while the diffuser's phase-step wells spread the same energy into a wide fan, and the scattered field on a receiver arc yields diffusion coefficients of 0.32 versus 0.63.

Download the animation (WebM)

The single-plane response below is the far-field prediction of a published diffuser geometry: an quadratic-residue diffuser of 6 periods, 3.6 m total width and 0.2 m maximum well depth (the “N = 7 QRD, 6 periods, 0.2 m deep” row of Cox & D’Antonio, Acoustic Absorbers and Diffusers, 3rd ed., Appendix B; the commercial N = 7 QRD measured by Hargreaves, Cox, Lam & D’Antonio, J. Acoust. Soc. Am. 108(4), 1710-1720, 2000, Table I) and its equal-footprint flat reference panel, at 1000 Hz, normal incidence, on the standard 37-point semicircle (5° spacing, to ). The same model levels drive the phonometry conformance suite as an arithmetic oracle for Formulas (5) and (7); the external third-party anchor checks the model’s band-averaged normalised diffusion against the published Appendix B BEM table in the 200-400 Hz bands (agreement within 0.01; over the full published 100-5000 Hz range the model-vs-BEM mean absolute deviation is about 0.09, because edge diffraction is outside the Fraunhofer model).

The surface itself is worth drawing before predicting anything from it. plot_qrd_geometry turns the depth sequence into the to-scale well profile below, and a predicted DiffuserPolarResponse retains its geometry, so qrd.plot_geometry() draws the surface it was computed for.

To-scale well profile of two periods of the commercial N = 7 quadratic-residue diffuser: wells 80.7 mm wide at an 85.7 mm pitch, separated by thin fins on a rigid base, following the depth sequence 0, 50, 200, 100, 100, 200, 50 mm with the deepest wells at 200 mm, and the incident sound arriving from aboveTo-scale well profile of two periods of the commercial N = 7 quadratic-residue diffuser: wells 80.7 mm wide at an 85.7 mm pitch, separated by thin fins on a rigid base, following the depth sequence 0, 50, 200, 100, 100, 200, 50 mm with the deepest wells at 200 mm, and the incident sound arriving from above

Two of the six periods, to scale: the quadratic residues turned into a buildable surface. The 490 Hz design frequency is what puts the deepest well at exactly 200 mm.

Show the code for this figure
import matplotlib.pyplot as plt
from phonometry import materials
# The published N = 7 QRD: well width 3.6 m / 42, deepest well 0.2 m.
depths = materials.qrd_well_depths(7, 490.0, speed_of_sound=343.0)
pitch = 3.6 / 42 # 42 wells across the 3.6 m panel
fin = 0.005 # thin fins, included in the pitch
materials.plot_qrd_geometry(depths, pitch - fin, fin_width=fin, periods=2)
plt.show()
# A predicted DiffuserPolarResponse retains its geometry, so
# qrd.plot_geometry() draws the profile it was computed for (all 6 periods).
from phonometry import materials
# The published geometry: N = 7 QRD, 6 periods, 3.6 m wide, 0.2 m deep
# (Cox & D'Antonio 3rd ed., Appendix B; Hargreaves et al. 2000, Table I).
# Design frequency 490 Hz puts the deepest well at exactly 0.2 m.
depths = materials.qrd_well_depths(7, 490.0) # [0, 0.05, 0.2, 0.1, ...] m
qrd = materials.predict_diffuser_polar_response(
3.6 / 42, 1000.0, depths=depths, periods=6)
# The flat reference panel: the same 3.6 m footprint with zero-depth wells.
flat = materials.predict_diffuser_polar_response(
3.6 / 42, 1000.0, depths=[0.0] * 7, periods=6)
qrd.plot() # predicted polar response, d in the title (needs matplotlib)
d = materials.directional_diffusion_coefficient(qrd.levels) # Formula (5)
print(round(float(d), 4)) # 0.1099
d_ref = materials.directional_diffusion_coefficient(flat.levels)
print(round(float(d_ref), 4)) # 0.0049
# Normalise against the flat reference to isolate the diffuser's own effect
# (Formula (7)): d_n = (d - d_ref) / (1 - d_ref).
d_n = materials.normalized_diffusion_coefficient(d, d_ref)
print(round(float(d_n), 4)) # 0.1055
# Random-incidence value: average the band coefficients over source positions,
# with the standard's 2-D weighting (0 deg -> 1, +/-30/+/-60 deg -> 3).
d_random = materials.random_incidence_diffusion(
[0.5, 0.2, 0.2, 0.2, 0.2], weights=materials.TWO_DIMENSIONAL_SOURCE_WEIGHTS)
print(round(float(d_random), 4)) # 0.2231

directional_diffusion keeps the receiver angles beside the levels of a full goniometer sweep and returns a plottable DiffusionResult. Reusing the QRD polar levels from above:

import numpy as np
from phonometry import materials
# qrd.levels: the 37 predicted QRD levels from the example above.
angles = np.arange(-90.0, 90.5, 5.0)
result = materials.directional_diffusion(angles, qrd.levels)
print(round(result.coefficient, 2)) # 0.11
result.plot() # polar reflected response, d in the title (needs matplotlib)
A polar plot of the predicted reflected sound-pressure level of a six-period N=7 quadratic-residue diffuser over 37 receivers from -90 to 90 degrees, the energy concentrated in a fan of discrete grating lobes, giving an autocorrelation diffusion coefficient d of about 0.11A polar plot of the predicted reflected sound-pressure level of a six-period N=7 quadratic-residue diffuser over 37 receivers from -90 to 90 degrees, the energy concentrated in a fan of discrete grating lobes, giving an autocorrelation diffusion coefficient d of about 0.11

The periodic QRD array splits the reflected energy into a fan of discrete grating lobes rather than one specular spike, but six repetitions of the same period concentrate the energy in those few directions, so the autocorrelation diffusion coefficient stays modest (). The flat reference panel collapses into the specular direction alone and drops to ; this lobing penalty of periodic arrays is exactly why Cox & D’Antonio recommend modulated arrangements.

Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from phonometry import materials
# qrd.levels: the 37 predicted levels L_i of the six-period N = 7 QRD from
# the example above (Fraunhofer far-field model, published Cox & D'Antonio
# Appendix B geometry, 1000 Hz, normal incidence) on the -90..90 deg, 5 deg
# semicircle. The periodic array concentrates the energy into grating lobes,
# so the Formula (5) coefficient d is modest.
angles = np.arange(-90.0, 90.5, 5.0)
result = materials.directional_diffusion(angles, qrd.levels)
# result is the DiffusionResult computed above. One line:
result.plot()
plt.show()
# By hand: a polar plot of the levels, with d annotated in the title.
fig, ax = plt.subplots(subplot_kw={"projection": "polar"})
theta = np.radians(result.angles)
ax.plot(theta, result.levels, "o-", color="#1f77b4")
ax.fill(theta, result.levels, color="#1f77b4", alpha=0.15)
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_thetamin(-90)
ax.set_thetamax(90)
ax.set_title(f"Directional diffusion d = {result.coefficient:.2f} (ISO 17497-2)")
plt.show()

Collected across the one-third-octave bands, the diffusion coefficient forms a DiffusionSpectrum, whose report(path) renders a one-page diffusion test report (ISO 17497-2, Clause 8.5): the per-band table of beside the band-axis curve, with a boxed characterisation headline. Per Clause 8.4, the random-incidence coefficient is itself a per-band quantity, the average of the directional coefficients over the source positions band by band (not a mean across frequency), so d here is one coefficient per band. verbose=True adds the normalised column to the table (the curve always draws as a companion when it is present).

import numpy as np
from phonometry import materials
# One diffusion coefficient per band (here a closed-form example). In practice
# each band's random-incidence d is the source-position average of the
# directional coefficients: for band k, average directional_diffusion_coefficient
# over the source positions with random_incidence_diffusion (Clause 8.4).
freqs = np.array([250, 500, 1000, 2000, 4000], float)
d = np.array([0.30, 0.45, 0.60, 0.75, 0.88])
spectrum = materials.diffusion_spectrum(freqs, d)
spectrum.plot() # d(f) on the band axis (needs matplotlib)
spectrum.report("diffusion.pdf") # one-page fiche (needs phonometry[report])
ISO 17497-2 diffusion-coefficient example report (PDF)

One-page diffusion fiche: a metadata header, the per-one-third-octave table of the diffusion coefficient d beside the d(f) band-axis curve (with the normalised d_n drawn as a companion curve), and the boxed characterisation headline over the tested frequency range.

Download the report (PDF)

Diffusion fiche (DiffusionSpectrum.report), the per-band random-incidence d(f) with the normalised d_n companion curve.

A single band’s polar response is itself reportable: DiffusionResult.report(path) renders the corrected receiver-angle / reflected-level table beside the semicircular polar plot, boxing that band’s directional diffusion coefficient.

ISO 17497-2 polar-response example report (PDF)

One-page single-source polar-response fiche: a metadata header, the corrected reflected level per receiver angle beside the semicircular polar plot, and the boxed directional diffusion coefficient.

Download the report (PDF)

Single-source polar-response fiche (DiffusionResult.report), the reflected level over the receiver arc.

Predicting diffusion from a diffuser design

Section titled “Predicting diffusion from a diffuser design”

The autocorrelation coefficient above reduces a measured polar response. The same coefficient can be predicted from the physical design of a Schroeder phase-grating diffuser, so a well-depth sequence can be graded before a sample is built. predict_diffuser_polar_response evaluates the single-plane Fraunhofer (far-field) model of Cox and D’Antonio: each rigid-bottom well of depth contributes a pressure reflection coefficient , and the scattered pressure at reflection angle for a source at incidence is the sum over the wells of the periodic surface (Eq. (5.8)),

with the well centre and . The predicted polar levels feed the same directional_diffusion_coefficient as a measurement. For a quadratic residue diffuser the depth sequence follows from the prime generator and design frequency (Eqs. (10.2)/(10.3)): and with .

import numpy as np
from phonometry import materials
# An N = 7 quadratic residue diffuser, design frequency 500 Hz.
depths = materials.qrd_well_depths(7, 500.0) # Eqs. (10.2)/(10.3)
print(np.round(depths * 100, 1)) # well depths in cm:
# [ 0. 4.9 19.6 9.8 9.8 19.6 4.9]
# Predicted far-field polar response at one frequency (5 repeated periods,
# 10 cm wells), reduced to the ISO 17497-2 directional diffusion coefficient.
surface = materials.predict_diffuser_polar_response(0.10, 2000.0, depths=depths,
periods=5)
print(round(surface.coefficient, 3)) # 0.210
surface.plot() # predicted polar response, d in the title (needs matplotlib)
# The spectrum variant normalises band by band against the same-footprint flat
# reference (Formula (7)): the flat panel maps to exactly zero, the QRD well
# above it.
freqs = np.array([500, 1000, 2000, 4000], float)
qrd = materials.predicted_diffusion_spectrum(0.10, freqs, depths=depths, periods=5)
print(np.round(qrd.normalized, 3)) # [0.352 0.275 0.208 0.073]
flat = materials.predicted_diffusion_spectrum(0.10, freqs,
depths=np.zeros_like(depths), periods=5)
print(np.round(flat.normalized, 3)) # [0. 0. 0. 0.]
Predicted diffusion coefficient over the one-third-octave bands from 250 to 5000 Hz for an N = 7 quadratic residue diffuser design compared with a flat panel of the same footprint: the QRD curve sits well above the near-zero flat-panel curve across the bandPredicted diffusion coefficient over the one-third-octave bands from 250 to 5000 Hz for an N = 7 quadratic residue diffuser design compared with a flat panel of the same footprint: the QRD curve sits well above the near-zero flat-panel curve across the band

Predicted from the design alone: the N = 7 QRD spreads the reflected energy far more evenly than the flat panel of the same footprint, so its predicted diffusion coefficient sits well above the near-specular flat reference across the band. This is a far-field design estimate, not a substitute for an ISO 17497-2 measurement; like every Fourier diffuser model it loses accuracy at low frequency, at grazing angles and for strongly absorbing surfaces. An arbitrary complex per-well reflection sequence can be supplied through the reflection argument, so an admittance or resonator-loaded surface computed elsewhere can be graded the same way.

Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from phonometry import materials
# N = 7 QRD (design frequency 500 Hz, 10 cm wells, five periods) versus the
# flat panel of the same footprint, over the one-third-octave bands.
freqs = np.array([250, 315, 400, 500, 630, 800, 1000, 1250, 1600,
2000, 2500, 3150, 4000, 5000], float)
depths = materials.qrd_well_depths(7, 500.0)
qrd = materials.predicted_diffusion_spectrum(0.10, freqs, depths=depths, periods=5)
# The DiffusionSpectrum plots the predicted d(f) directly. One line:
qrd.plot()
plt.show()
# By hand: predicted d(f) for the QRD against the flat reference.
flat = materials.predicted_diffusion_spectrum(0.10, freqs,
depths=np.zeros_like(depths), periods=5,
normalize=False)
fig, ax = plt.subplots()
ax.semilogx(freqs, qrd.diffusion, "o-", color="#1f77b4", label="N = 7 QRD design")
ax.semilogx(freqs, flat.diffusion, "s--", color="#d62728", label="Flat panel")
ax.set_ylim(0.0, 1.0)
ax.set_xlabel("Frequency [Hz]")
ax.set_ylabel("Predicted diffusion coefficient d")
ax.legend()
plt.show()

Scattering or diffusion? Two coefficients, two jobs

Section titled “Scattering or diffusion? Two coefficients, two jobs”

The two coefficients above are routinely treated as interchangeable, in product data sheets and occasionally in simulation manuals. They are not, and neither can be computed from the other. The scattering coefficient (ISO 17497-1) does energy bookkeeping: what fraction of the reflected energy leaves the specular direction. It says nothing about where that energy goes. The diffusion coefficient (ISO 17497-2) grades spatial quality: how evenly the reflected energy covers the receiver arc, one source direction at a time. It says nothing about how the energy divides between the specular lobe and the rest.

A pair of counterexamples keeps them apart. A curved or tilted element that redirects the reflection concentrates nearly all the reflected energy into one strong lobe away from the specular direction: almost everything is non-specular, so is high, yet the beam is as collimated as a mirror’s, so stays low. Conversely, a small flat panel measured at low frequency sends nearly all the reflected energy toward the specular direction, so stays near zero, yet edge diffraction spreads that reflection so broadly over the receiver arc that the measured comes out surprisingly high. High does not mean uniform; decent does not mean much energy was scattered at all.

Which one for design. The two numbers serve different consumers:

  • The scattering coefficient feeds room-acoustics simulation. Geometrical-acoustics engines decide at every wall reflection how much energy continues specularly and how much is redistributed; the per-band random-incidence of each surface is precisely that split, which is what ISO 17497-1 was written to supply. Getting it wrong shows up as the wrong reverberation-time and clarity predictions in non-mixing rooms.
  • The diffusion coefficient qualifies diffusers. When the task is to break up an echo, a flutter or a focusing reflection, what matters is that the reflected energy is spread over angle, and measures exactly that. The normalised (Formula (7)) additionally subtracts the edge diffraction that every finite panel exhibits, so products of different sizes can be compared fairly.

Swapping them fails in both directions: a diffusion coefficient dropped into a simulator’s scattering slot biases the energy split, and a scattering coefficient quoted as proof of “diffusion” may describe a surface that merely redirects the problem reflection somewhere else. Both coefficients are one-third-octave-band functions that generally rise once the surface relief is no longer small against the wavelength; a frequency-blind single number (“scatters 90 % of the sound”) is neither of them.

3. In-situ road absorption: subtraction technique (ISO 13472-1)

Section titled “3. In-situ road absorption: subtraction technique (ISO 13472-1)”

Out in the field there is no reverberation room. ISO 13472-1 measures the sound absorption of a road surface (or any extended flat surface) in situ by firing an impulse from a loudspeaker at height down onto the surface and recording the impulse response at a microphone at height . The incident and reflected components are separated in time with an Adrienne window; their transfer function gives the reflection factor and hence the absorption.

ISO 13472-1 in-situ road absorption by the subtraction technique: a loudspeaker at 1.25 m and a microphone at 0.25 m above the road surface, with the direct and road-reflected ray paths and a free-field reference measurement, the reflected component isolated by an Adrienne time windowISO 13472-1 in-situ road absorption by the subtraction technique: a loudspeaker at 1.25 m and a microphone at 0.25 m above the road surface, with the direct and road-reflected ray paths and a free-field reference measurement, the reflected component isolated by an Adrienne time window

Geometrical spreading (Clause 4.1). The reflected wave travels farther than the direct wave, so it is attenuated by the geometrical-spreading factor

which equals for the mandatory geometry m, m. The absorption follows from the windowed incident and reflected spectra , :

import numpy as np
from phonometry import materials
# A band-limited incident impulse response and a synthetic road reflection
# hr = Kr * r0 * delayed(hi): a reflection of magnitude r0 = 0.4, delayed by the
# extra path, and scaled by the geometrical-spreading factor Kr.
fs, n = 48000.0, 4096
t = np.arange(n) / fs
hi = np.zeros(n)
hi[:64] = np.hanning(64) * np.cos(2.0 * np.pi * 1500.0 * t[:64])
kr = materials.geometric_spreading_factor() # (ds - dm)/(ds + dm) = 2/3
hr = kr * 0.4 * np.roll(hi, 96)
# Narrow-band absorption, then reduced to one-third octaves over 250-4000 Hz.
alpha = materials.insitu_absorption_coefficient(hi, hr) # 1 - (1/Kr^2)|Hr/Hi|^2
freq = np.fft.rfftfreq(n, 1.0 / fs)
centres, band = materials.one_third_octave_absorption(freq, alpha)
print(round(kr, 4)) # 0.6667
print(round(float(band[2]), 3)) # 0.84 (alpha = 1 - 0.4^2 = 0.84)

Adrienne window (Clause 6.4). The time window that isolates the reflection mandates only a sharp leading edge, a 5 ms flat portion and a cosine-squared or Blackman-Harris trailing edge; the exact durations are reported per measurement, not fixed, so they are configurable here.

from phonometry import materials
# Default: 0.5 ms leading edge, 5 ms flat top, 5 ms Blackman-Harris trailing.
w = materials.adrienne_window(48000.0)
print(w.shape[0]) # 504 samples at 48 kHz
print(round(float(w.max()), 3)) # 1.0 (flat top and edges meet at unity)

End-to-end spectrum. insitu_absorption_spectrum runs the whole chain (the windowed incident and reflected impulse responses to the narrow-band absorption and on to one-third-octave bands) and returns a plottable InsituAbsorptionResult:

import numpy as np
from phonometry import materials
from scipy.signal import firwin, lfilter
# A synthetic-but-realistic measurement. hi is a unit incident impulse; the road
# reflection hr = Kr * r0 * roll(hi, shift) uses the geometrical-spreading
# factor Kr, a mildly frequency-dependent r0 (a gentle low-pass, so a porous
# surface reflects less as frequency rises) and the reflected-path delay
# shift = round(2 dm / c * fs).
fs, n = 48000.0, 8192
kr = materials.geometric_spreading_factor() # (ds - dm)/(ds + dm) = 2/3
hi = np.zeros(n)
hi[0] = 1.0
taps = firwin(41, 1200.0, fs=fs)
taps = taps / taps.sum()
shift = int(round(2.0 * 0.25 / 340.0 * fs)) # reflected-path delay 2 dm / c
hr = kr * 0.85 * np.roll(lfilter(taps, 1.0, hi), shift)
result = materials.insitu_absorption_spectrum(hi, hr, fs)
print(result.frequencies[[0, -1]].astype(int)) # [ 250 4000]
print(np.round(result.absorption[[0, 6, 12]], 2)) # [0.31 0.65 1. ]
result.plot() # alpha(f) bar chart over 250-4000 Hz (needs matplotlib)
An in-situ one-third-octave road-surface absorption spectrum computed by the reflection-factor route from a synthetic road reflection, rising from about 0.3 at 250 Hz to near 1.0 above 2 kHzAn in-situ one-third-octave road-surface absorption spectrum computed by the reflection-factor route from a synthetic road reflection, rising from about 0.3 at 250 Hz to near 1.0 above 2 kHz

The absorption rises with frequency because the surface reflects less of the high-frequency energy, exactly as the low-pass reflection factor dictates through .

Show the code for this figure
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import firwin, lfilter
from phonometry import materials
# A synthetic-but-realistic measurement. hi is a unit incident impulse; the road
# reflection hr = Kr * r0 * roll(hi, shift) uses the geometrical-spreading
# factor Kr, a mildly frequency-dependent r0 (a gentle low-pass, so a porous
# surface reflects less as frequency rises) and the reflected-path delay
# shift = round(2 dm / c * fs).
fs, n = 48000.0, 8192
kr = materials.geometric_spreading_factor() # (ds - dm)/(ds + dm) = 2/3
hi = np.zeros(n)
hi[0] = 1.0
taps = firwin(41, 1200.0, fs=fs)
taps = taps / taps.sum()
shift = int(round(2.0 * 0.25 / 340.0 * fs)) # reflected-path delay 2 dm / c
hr = kr * 0.85 * np.roll(lfilter(taps, 1.0, hi), shift)
result = materials.insitu_absorption_spectrum(hi, hr, fs)
# result is the InsituAbsorptionResult computed above. One line:
result.plot()
plt.show()
# By hand: a bar chart of alpha over the one-third-octave bands.
freqs = result.frequencies
positions = np.arange(freqs.size)
fig, ax = plt.subplots()
ax.bar(positions, np.nan_to_num(result.absorption), width=0.7, color="#1f77b4")
ax.set_xticks(positions)
ax.set_xticklabels([f"{f:g}" for f in freqs], rotation=45, ha="right")
ax.set_xlabel("Frequency [Hz]")
ax.set_ylabel("Absorption coefficient alpha")
ax.set_ylim(0.0, 1.0)
ax.set_title("In-situ road-surface absorption (ISO 13472-1)")
plt.show()

Maximum sampled area (Annex A). The finite time window limits how much of the surface contributes to the reflection. The maximum sampled area is a circle whose radius the library computes from the geometry and window width; the Annex A worked example ( m, m, m/s, 5 ms flat window) gives about 1.34 m.

from phonometry import materials
print(round(materials.max_sampled_area_radius(5.0e-3), 3)) # 1.343 (metres)

4. In-situ road absorption: spot method (ISO 13472-2)

Section titled “4. In-situ road absorption: spot method (ISO 13472-2)”

For smaller patches, ISO 13472-2 seals a short circular tube onto the surface and measures the absorption with the two-microphone transfer-function method of ISO 10534-2. The library provides the spot-method geometry and validity helpers; the transfer-function DSP itself is the impedance-tube routine two_microphone_impedance (see the Acoustic Materials guide).

ISO 13472-2 spot method: a short circular tube sealed onto the road surface with a loudspeaker at the top and two microphones flush in the tube wall at spacing s, measuring absorption over 250 to 1600 Hz via the ISO 10534-2 two-microphone transfer-function methodISO 13472-2 spot method: a short circular tube sealed onto the road surface with a loudspeaker at the top and two microphones flush in the tube wall at spacing s, measuring absorption over 250 to 1600 Hz via the ISO 10534-2 two-microphone transfer-function method

Plane-wave limits (Clause 5.4). The tube supports only plane waves below

with the tube diameter, and the microphone spacing must sit between and . The reported range is the one-third-octave bands 250–1600 Hz.

from phonometry import materials
# Upper usable frequency of a 100 mm tube and the valid spacing window.
print(round(materials.spot_tube_upper_frequency(0.100, 343.0), 1)) # 1989.4 Hz
s_min, s_max = materials.spot_microphone_spacing_bounds(
343.0, f_min=220.0, f_max=1800.0)
print(round(s_min, 3), round(s_max, 3)) # 0.078 0.086 (metres)

Covered. ISO 17497-1:2004+A1:2014’s random-incidence scattering coefficient (the Clause 6 absorption-from-reverberation-time relations and Eq. (5)) with the Table 1 base-plate ceilings, via materials.random_incidence_absorption, materials.specular_absorption_coefficient, materials.scattering_coefficient, materials.scattering_coefficient_spectrum and materials.check_base_plate_scattering; ISO 17497-2:2012’s directional and random-incidence diffusion coefficient (Formulas (5) to (8)) via materials.directional_diffusion and materials.random_incidence_diffusion, plus the Cox & D’Antonio Fraunhofer diffuser-prediction model; ISO 13472-1:2002’s subtraction technique (the Clause 4.1 geometrical spreading, the Clause 6.4 Adrienne time window and the Annex A sampled-area radius) via materials.insitu_absorption_spectrum and its building blocks; and ISO 13472-2:2010’s spot-method plane-wave frequency limit and microphone-spacing geometry.

Not covered. Both in-situ road standards have since been revised (ISO 13472-1:2022, ISO 13472-2:2025); only the earlier 2002 and 2010 editions are implemented here, as the reference notes above flag. ISO 13472-2’s own transfer-function processing is not implemented in this module either: only the geometry and validity helpers are provided, and the actual two-microphone DSP is the ISO 10534-2 routine of the Acoustic Materials guide. The Fraunhofer diffuser-prediction model is a design estimate, not part of ISO 17497-2 itself, and its own stated limits (it loses accuracy at low frequency, at grazing angles and for strongly absorbing surfaces, and it ignores edge diffraction) mean it is no substitute for a measured diffusion coefficient.

Created and maintained by· GitHub· PyPI· All projects