Commits
Keep the guides expanded on the desktop column, fold them on the phone drawer,
and scroll the highlighted row into view when a reader lands from a search
engine and nothing was restored.
The decisive measurement is not the height of the tree, it is that Starlight
restores the sidebar scroll position on desktop and skips it below the
breakpoint on purpose, so a 3534 px tree costs the desktop reader once per
session and a 3140 px drawer costs the phone reader on every hamburger tap.
Folding the drawer turns that into 762 to 984 px, all eleven domains on the
first screen, and the reader's own page visible with no scrolling.
The write-up carries the inventory and geometry, the cold-landing numbers, the
research with sources, the analysis of every candidate for both form factors,
the resolution of the group label that would otherwise navigate and toggle at
once, where the decision meets the API sidebar work, and the file-by-file
change list.
Twenty-three shots in toc-ux-shots/: the status quo, the responsive treatment
and the collapsed treatment, at 1440x900 and 390x844, in both themes and both
languages, plus the tapped and keyboard-focused states of the fold control.
Desktop shots are clipped to the sidebar column, which is the subject; phone
shots are the whole viewport, because there the tree is a drawer over the page.
The pa11y config for the drawer opens the hamburger before testing, which the
default run never does, so the folded tree and its controls are audited in the
state a phone reader actually sees.
The fold control keeps a 40px square target on the drawer, past the 24px floor
of WCAG 2.2 SC 2.5.8 and small enough that the eleven closed groups still fit
one screen, and it restates the two eyebrow-header rules that the row wrapper
would otherwise stop matching.
The tree ships exactly as it does today and the browser adds the controls, so
the server-rendered markup is unchanged and a reader with no JavaScript keeps
the fully expanded tree. Treatment comes from data-guides-nav on <html>:
expanded (status quo), responsive (expanded on desktop, folded on the mobile
drawer) and collapsed (folded on both). The branch holding the current page is
always open, and on desktop the tree scrolls the highlighted row into view when
nothing was restored, which is the case for a reader arriving from a search
engine.
Two rules hold whatever the treatment. The group label stays a link to the
section landing page and the fold lives on a sibling button with aria-expanded
and aria-controls, and a folded list is hidden with hidden="until-found"
rather than display:none so find-in-page still matches inside it.
The companion script reports what each treatment gives the reader, reading the
keyboard and accessibility numbers from checkVisibility() and the accessibility
snapshot rather than from bounding boxes.
The reader who arrives from a search engine has no session storage, so
Starlight restores nothing and the tree opens at the top. The harness now
records, for that case, where the highlighted row sits, how far the tree has
to be scrolled before it comes into view, and whether the top-level group that
contains the page carries any mark of its own.
Drives headless Chrome against a dev server and reports the real geometry of
the navigation tree: group and item counts, the rendered height of the tree at
desktop and inside the mobile menu, how much of it is above the fold, and how
far the last group sits. The candidate heights (all groups closed, only the
current group open, only the current branch open) are measured on the real
tree with a throwaway stylesheet rather than estimated.
INTEGRATION-NOTES.md now carries the cap, the chip landing, the theme toggle
and the desktop control I deliberately left alone, plus every gate result and
the two traps worth knowing: pa11y cannot use its own default port while the
dev server is up, and the managed dev server needed a --force restart before
it would see a frontmatter edit.
The header was lifting Starlight's theme select out of the hamburger, so the
mobile control was still a menu: open it, read three options, pick one. It is
a button now, and one tap flips light and dark.
Auto stays out of the cycle and stays implicit. With nothing stored the site
follows the system preference and keeps following it live, and the first tap
is what turns an inferred theme into a stored choice. Anyone who wants to
hand the decision back to the system still has Starlight's own select, with
its explicit Auto option, in the mobile menu.
No theme state is forked. The button sets the value on Starlight's select and
fires its change event, so the site lands in exactly the state it would have
been in had the reader picked that option from the menu: updatePickers, the
data-theme attribute and the starlight-theme key are all written by
Starlight's own handler. The button then reads its state back from data-theme,
which is the output of that same path, so it cannot disagree with the desktop
control or with the before-paint script.
The icon is the mode the tap would give you, not the mode you are in, and the
accessible name says the same in words ("Switch to dark theme"), updated from
the same attribute. The desktop select keeps showing the current mode
instead; the two are never on screen together, so I left it alone.
Driven in headless Chrome from both system preferences: initial state follows
the system, a live system flip is followed while nothing is stored, Enter on
the focused button flips it, a tap flips it back, a reload keeps it, a system
flip after a choice is ignored, and clearing storage returns to following the
system. Both Starlight selects agree with the button after every step.
Three things on the header chips.
The "+N more" chip pointed at the References heading, which had no landing
offset of its own, while the entries had one. Starlight's scroll-padding
clears the sticky chrome for both, so neither was hidden, but the heading came
to rest hard against the sticky bar while an entry came to rest a comfortable
way below it, and on a phone, where the mobile table of contents is part of
that chrome, the difference is the whole gap. The offset is now one custom
property in one rule covering the entries, the heading and its wrapper, so
the two targets cannot drift apart again. Both land at 168 px on a 1440 px
desktop and 200 px on a 390 px phone, EN and ES, with and without the
language bar.
The cap is nine chips for the run as a whole, with a tenth slot for the
"+N more" link, replacing five standards plus three works. The budget is not
split per category any more, with one guard: the named works hold a
reservation of up to three slots when the page has any, the standards take
the rest, and whatever the standards do not use goes back to the works. Nine
standards on a standards-only page, nine papers on a paper-only page, and a
page with fourteen standards and three papers shows six and three rather than
nine and none. The `primary: true` flag still orders the run first: flagging a
work that was past the old cap on the underwater page brings it to the front
of its run and into the visible nine.
Worth knowing: nine chips no longer fit the one line the run was designed
around. The densest page is four rows at 1440 px and six at 390 px. It reads
as a block under the H1 rather than a dateline, which is the trade the higher
cap buys.
scripts/check-page-chips.mjs is the new gate: the cap on seven pages in both
locales, and the landing of an entry chip against the more chip at four
viewport sizes, both themes, both locales and with the language bar up.
INTEGRATION-NOTES.md records what came from each branch, the gitignore rule
that kept the chip helper untracked, the colour retuning with its numbers,
the two language-bar defects and what is still open.
The chip branch's notes carried the old colours and the old ratios in prose;
they now point at the palette and at check-contrast.mjs, which is where the
numbers are produced rather than transcribed.
The bar is fixed across the whole bottom edge, but it was mounted in the
Footer override, which renders inside Starlight's `.main-pane`. That pane
sets `isolation: isolate`, so the bar got its own stacking context and
painted under the sidebar pane no matter what its z-index said: on a desktop
layout the sentence was cut mid-word by the sidebar and the dismiss button
was cut by the table of contents. Mounting it in a PageFrame override makes
it a sibling of the page frame, in the root stacking context, where its
z-index means what it reads. The frame renders on every route, splash
included, so nothing loses the bar.
The floating treatment switcher was landing on the dismiss button as well,
the two branches having each pinned something to that corner. The bar now
publishes its height on <html> while it is on screen and the switcher steps
over it.
check-lang-suggest.mjs gets the scenario that would have caught this: hit
test the bar over the sidebar, over the content and over the toc, plus the
dismiss button itself. The markup was always right; only the paint order
was wrong, so only a hit test can see it.
The chips were tuned against the old Starlight default palette, and against
the instrument palette they were doing two things I do not want. The
standards teal sat at OKLCH hue 181 with the accent at 216 to 225, close
enough to read as a near-miss of the accent rather than a category of its
own, and both chip hues carried more chroma than the accent itself (0.103
and 0.141 against 0.056 in the dark theme), so the header run out-weighed
the one colour this palette lets mean something.
So the standards chip now takes the palette accent itself, which is honest:
the standards are what the page is about and the chip is a link. The named
works take its warm mirror, same OKLCH lightness and chroma at hue 74 and 70,
which keeps the two categories far apart without introducing a second loud
colour. The tokens live in theme.css with the rest of the palette and the
chip stylesheet only references them.
check-contrast.mjs now audits the nine chip pairs per theme: text on the
page and on the hover fill at 4.5:1, and the pill border, which is what
carries the category, at 3:1. Everything clears, the tightest being the
"+N more" border at 3.04:1 dark and the standards border at 3.42:1 light.
Also add scripts/integration-shots.mjs, the capture matrix for this branch.
The root gitignore's lib/ rule, which exists for distutils output, also
matched site/src/lib and silently kept the page-chips helper untracked, so
the build could not resolve it from a fresh clone.
The notes described a matrix that no longer exists. They now describe
the single design: the instrument palette with its measured contrast
table, the front page, the mobile theme control and the language bar
with its kill switch. The two discarded palettes and the two discarded
language modes are recorded as history, with the commit they can be
read back from (1113e19c) and the exact paths.
New section on the clause-number defect and its fix, with what was
measured before (20 of 26 cases overlapping, in both locales) and the
three axes it failed on, since the enlarged-text one is the least
obvious and the one that reached the desktop.
The screenshots are regenerated for one palette: the ones showing the
discarded directions and the prototype switcher are gone, and four new
crops document the heading defect and the fix at 390 px in Spanish and
at 1440 px with a 24 px default text size.
Gates, all green on this branch: build 441 pages with every internal
link valid, html-validate clean, pa11y-ci 46/46 at WCAG2AA, i18n
parity 100/100, contrast 25 pairs with none below threshold, language
handling 9 scenarios, landing headings 40 layout cases. The two
browser-driven checks were run against the built site under astro
preview as well as against the dev server.
I have decided both questions this branch was carrying, so the code
should carry one answer and not three. The palette is "instrument",
cool steel greys with a deep cyan indicator, and the language handling
is the banner: it offers the other language and nothing ever navigates
on its own.
Gone with them: the floating prototype switcher, the blueprint and
graphite palettes, the `data-accent` attribute and the before-paint
script in Head.astro that stamped it, the `phonometry:accent` and
`phonometry:lang-mode` storage keys, the redirect and off language
modes with their per-session guard, and the bar-height custom property
that only existed so the switcher could sit clear of the bar.
src/styles/theme-directions.css becomes src/styles/theme.css, with the
winning direction promoted to the plain :root and
:root[data-theme='light'] rules.
The language feature keeps a kill switch, but as a build-time constant
rather than a control: ENABLED in LangSuggest.astro renders no markup
and no script when it is false. Nothing on the page and nothing in
storage can flip it.
Everything else about the behaviour is unchanged and still checked: an
explicit choice always wins, the picker and a dismissal both record
one, crawlers are skipped and the English-only API subtree renders no
bar at all. check-contrast.mjs now resolves the two themes of the one
palette (25 pairs, 0 below threshold) and check-lang-suggest.mjs runs
the nine scenarios that survive without modes.
The number beside each landing block was an absolutely positioned
::before, and the gutter it needs was reserved by a padding that only
existed inside a `min-width: 62rem` media query. Everywhere else the
number was painted straight onto the first letter of the title: the
teal "01" sat on the Q of "Qué es" at every width below 992px, at
200 % and 400 % browser zoom, and at any width at all once the reader
enlarges the default text size, because a rem media query resolves
against that size and 62rem stops matching a 1440px window as soon as
the default goes past 23px. I measured it at 20 of the 26 cases I
checked, in both locales.
The number is now a real box in the layout instead of an overlay. Each
block is a wrapping flex line of two items, the number and the block
body, so the space is reserved by the layout itself at every width:
when the line has room the number sits in its gutter beside the
heading, and when it does not the body wraps and the number ends up on
its own line above the title. There is no threshold left to get wrong,
and no state in which the two can occupy the same pixels.
scripts/check-home-headings.mjs proves it: 40 cases, both locales, 320
to 1920 px, 200 % and 400 % zoom, and default text sizes of 24 and 32
px. It asserts that no clause number overlaps any text of its block,
that no step number overlaps its step heading, that the list markers
fit the indent their item reserves, and that the document never
scrolls sideways.
I had assumed the dark matplotlib figures were transparent, so the page
ground would show through them. They are not. Their figure patch is emitted
as a <path> with no fill attribute, and SVG paints an unfilled path black, so
404 of the 506 dark figures are opaque black plates and the other 102 are the
diagrams' #0d1117 plates. Sampling the rendered page confirms it: inside a
dark figure the pixels are #000000, not the page colour.
That changes what the dark ground is for. It cannot help the ink, which sits
on the figure's own black; all it can do is stop the plate edge from reading.
So the three dark grounds move down into the band between #000000 and
#0d1117:
instrument #12181c -> #0d1114 plate seams 1.11:1 and 1.00:1
blueprint #0f1421 -> #0b0f1a 1.10:1 and 1.01:1
graphite #17150f -> #100e0a 1.09:1 and 1.02:1
against 1.22:1 and 1.10:1 for the current theme, so all three roughly halve
the visible edge and make the diagram plate disappear outright. Nav, sidebar
and card surfaces follow the ground down so the separation between them is
unchanged. Body text improves to 11.3-11.6:1; 75 pairs measured, 0 enforced
pair below AA.
The contrast script now measures both plate seams instead of ink that never
lands on the page, and the notes carry the corrected analysis, including the
observation that saving the figures with a transparent background would
remove the constraint altogether at the cost of regenerating all 1220 files.
The pill without fill is the chosen style, so it is the only one left: the
plain text run and the filled pill are deleted with their CSS, the
data-page-chips attribute is gone and the chips render whenever a page has
references. The switcher keeps only the API treatment and is labelled for it.
The pill outline was the one thing I had left below its accessibility target.
It now clears 3:1 against the page background in both themes, measured on the
composited colour: 50 % alpha dark for both categories, 80 and 90 % light,
where solid amber on white only reaches 3.99:1 to begin with. Nothing else
about the chip changed, so the approved size, padding, radius and spacing are
untouched.
Which sources survive the caps is no longer frontmatter order alone: a
reference marked primary: true is promoted within its category run, with
frontmatter order as the tiebreak, so a page with a long bibliography can
advertise the sources it leans on. The field is optional and documented in
the schema.
The ICAO Annex 16 entry on the aircraft-noise page carried an edition string
where its document number belongs, so that page derived no chip for the
standard it implements. Both language versions now name the document.
Front page. The landing was the README feature list as ten cards of 60 to 90
words: it stated everything and showed nothing, and its numbers were an
earlier release's (371 checks, 235 standards, against the 427 over 278 the
conformance report now produces). It is now five blocks that each answer one
question: the numbers with no adjectives, what this is with who it is for and
an explicit list of what it is not, one worked example beside the figure it
draws and one rating beside the PDF fiche it renders, the coverage of nine
areas as a table of standards, and a three-step path from nothing to a first
analysis. Text lives in src/data/home.ts, one object per locale sharing one
shape, so English and Spanish cannot drift apart; every number in it is
sourced from the repository and the file records where.
The splash column widens to 62rem, the sidebar gutter Starlight reserves is
zeroed so the page is centred, and the hero grid collapses to one column
since there is no hero image.
Mobile header. Below the md breakpoint Starlight hides the theme control
behind the hamburger. Header.astro now surfaces it next to the language
picker, in the desktop order, as an icon-and-caret stub. Starlight's own
updatePickers() already walks every theme select on the page, so the mobile
stub and the desktop control stay in sync with no extra code.
Language detection. LangSuggest.astro offers the other language on a first
visit, in banner mode by default, with redirect and off available from the
prototype switcher. An explicit locale in the URL wins, the language picker
records the choice, and once anything is stored nothing ever navigates on its
own again, so a deliberately opened English URL is never hijacked.
Redirection is guarded by a stored choice, a session flag and a target check,
so it cannot loop; crawlers are skipped by user agent; the English-only API
subtree opts out entirely; and none of it touches canonical or hreflang.
scripts/check-lang-suggest.mjs drives eleven scenarios in isolated browser
contexts: 0 failing.
Build with link check, html-validate, pa11y at WCAG2AA (46/46) and the EN/ES
parity gate all pass.
The sidebar keeps the tree and nothing else. The annotation experiment is
deleted rather than switched off: the data-toc-style dimension and its five
presentations, the annotation line, the hover card and its keyboard handling,
the page subtitle and the section landing tables all go, and with them the
attrs['data-chips'] classification in the sidebar config and in the API
generator, plus the shortened sidebar labels the chips had justified.
SidebarSublist.astro and Sidebar.astro are back to the smallest diff against
upstream Starlight that still gives me non-collapsible groups, the
overview-first convention where a group label links to its landing page, and
the fold toggle the API treatments need. Each file header now records what
differs from upstream and why, so a future Starlight upgrade is cheap to
reconcile.
Everything about what governs a page now lives in the page header run, which
takes its data from the frontmatter bibliography. That run gains two more
presentations to compare against the quiet text version: outlined pills, so
the chips look like the links they are, and the same pills with a faint
category tint. Both keep the eight chips of the densest page on one line and
clear 5.9:1 on their text in either theme.
The site rides Starlight's defaults today: hue 224 greys and a fully
saturated blue-violet accent that sits badly next to a library whose output
is calibration numbers, and that reads as part of the plot whenever a figure
puts a blue trace on the page.
Three complete palettes now live in src/styles/theme-directions.css, each
covering light and dark, selected with a data-accent attribute on <html>
that an inline script in Head.astro stamps before first paint and
AccentSwitcher.astro writes to localStorage:
instrument cool steel greys, deep cyan (default, also the plain :root
block, so the site is correct with JavaScript disabled)
blueprint drafting blue-greys, prussian ink, navy dark ground
graphite warm neutral greys, amber
The palettes are chosen around the committed figures, which do not change.
404 of the 506 dark figures are transparent matplotlib plots drawing in
white plus tab10, so every dark ground stays dark enough for #1f77b4 to
clear 3:1 (3.71, 3.81, 3.79). The other 102 are diagrams with an opaque
#0d1117 ground, so the ground is kept close to it: the plate seam goes from
1.10:1 today to 1.06, 1.03 and 1.04. Every light figure carries an opaque
white ground, which is why all three keep the light page white and tint the
surfaces instead.
scripts/check-contrast.mjs parses the stylesheet and measures all six
palettes: 87 pairs, 0 enforced pair below WCAG AA. scripts/redesign-shots.mjs
drives its own headless Chrome for a reproducible screenshot matrix.
The desktop is the target, so the touch penalty that pushed the hover variant
down the list mostly disappears, and with a header run carrying the standards
the sidebar only has to answer which neighbour to read next.
The same information the sidebar experiment has been trying to fit into the
tree, placed under the H1 where there is horizontal room and no navigation to
pollute: one quiet line naming the standards that govern the page, then the
works its methods are attributed to.
It is derived from each page's own typed `references` block rather than from
a second hand-maintained list, so the header and the References section at the
bottom cannot disagree, and every chip links to its full entry there, which
now carries a stable id and highlights when jumped to. Standard designations
collapse to their family (ISO 10140-2:2010 becomes ISO 10140) and books and
articles become author and year; five standards and three works fit on one
line, anything beyond that folds into a single "+N more" link. Pages without
references render nothing.
It rides on its own `data-page-chips` attribute so it can be compared against
any sidebar variant, and it supersedes the hand-derived subtitle of the clean
variant, which now yields whenever the run is on.
The annotation line reused the sidebar link colour, so metadata carried
exactly the weight of the navigation labels; it now sits one step quieter in
both themes and still clears WCAG AA at 11px. The dotted marker of the hover
variant was invisible on the dark sidebar, so each theme gets its own value,
and the card border no longer disappears against the white light-theme
surface.
On a phone the three-column standards table pushed its last column off screen
and wrapped page names to four lines, so below 30rem it stacks one page per
block. The table also gained the tabindex the markdown tables get, since it is
a scrollable region.
Replaces the earlier ad-hoc captures with the ux-variants2 set.
The section standards table is injected after the markdown pipeline, so its
heading never reached "On this page". The heading now has an id and the
prototype script mirrors the entry into the table of contents while the clean
variant is active. The API caret also gained aria-controls.
The dotted marker no longer sits on the resting tree: almost every page here
is governed by something, so an always-on underline marked nearly every row
and read as texture rather than information. It now fades in at hairline
weight only while the pointer or the keyboard focus is inside the sidebar.
The floating card is anchored to the link's trailing edge, so it can no
longer push the sidebar into horizontal scroll at any nesting depth, Escape
dismisses it without moving the focus (WCAG 2.2 1.4.13), and on touch devices
it degrades to the always-visible annotation line because there is no hover
there. The topic back link pointed at a page that does not exist.
Every sidebar item already knows which standards it implements and which
named references its method is attributed to. That classification rides along
as `attrs['data-chips']` JSON, built by the `chips()` helpers in the sidebar
config and by the API generator, and is rendered as a run of badges inside
each link.
This adds five presentations of that run behind a `data-toc-style` attribute
on `<html>`: the annotation line for the current section only, the same line
on the whole tree, a floating card on hover or keyboard focus, a page level
subtitle plus a table on section landings, and nothing at all. It also adds
three treatments of the API reference tree behind `data-api-style`: expanded
in place, folded behind a caret, and a topic style split with a back link.
A floating switcher applies and persists the choice before first paint, so I
can compare all of them on the running site without a rebuild.
Prototype only. See TOC-REDESIGN-NOTES.md.
* Normalize Spanish docs prose to UNE acoustic terminology
Sweep the Spanish pages for the non-UNE forms "presion sonora",
"potencia sonora" and "intensidad sonora" and normalize the prose to
the wording used by the official Spanish editions of the standards:
"presion acustica" (UNE-EN ISO 16283-2:2016, UNE-EN ISO 10052:2005,
UNE-EN ISO 3746:2011, UNE-ISO 226:2013), "potencia acustica"
(UNE-EN ISO 3746:2011, UNE-EN ISO 4871:2010, UNE-EN ISO 9614-1:2010),
"intensidad acustica" (UNE-EN 61043:1999, UNE-EN ISO 9614-1:2010) and
"nivel de presion acustica de impactos" (UNE-EN ISO 12354-2:2018,
UNE-EN ISO 717-2:2013). 92 replacements across 32 pages, prose only:
code snippets keep the labels they share with the generated figures.
Rename the intensity guide to "Intensidad acustica (p-p)" and the
structure-borne power guide to "Potencia acustica estructural de
equipos (EN 15657)", updating every cross-link text and the theory
anchor intensidad-sonora-iec-61043 accordingly. The API sidebar group
label becomes "Potencia acustica e intensidad" via api_taxonomy.py
with the generated sidebar regenerated.
* Use decimal commas in Spanish docs prose quantities
Sweep every Spanish page for decimal points leaking into prose numbers
and switch them to the comma the locale convention calls for: the
rotorcraft validation prose (fourteen tolerances such as 0,08 dB(A) and
0,5 s), the insulation-prediction worked example (53,6 dB / 53,8 dB),
the tone-audibility Annex E tone (137,3 Hz), the Mackenzie check value
(1550,744 m/s), the EPNL, loudness and absorption fiche descriptions
(98,3 EPNdB; 8,2 sonos and 70,4 fonios; 0,60 y 0,55), the junction
constants table and the third-octave band-edge table in the signal
analysis theory page. Code snippets, inline code, math, clause numbers
and quoted source text keep their points.
* Pass language="es" in Spanish figure-reproduction snippets
The <details> blocks under each localized figure in the Spanish guides
are meant to reproduce the _es variant shown on the page, but 82 of the
one-line result .plot() calls omitted the language="es" argument and
would render English axis labels instead. Add the argument across 33
guides, matching the pattern the intensity field-indicators snippet
already used. The three lab-insulation results whose plot() does not
take a language argument (lab airborne, lab impact and the
element-normalized intensity difference) are left as they were.
* Extend the UNE terminology sweep to sound absorption
Normalize "absorción sonora" to "absorción acústica" across the
Spanish pages (36 occurrences in 14 pages, including the EN 12354-6
guide title and the theory headings), the wording of UNE-EN ISO
3746:2011 and of the equivalent absorption area in UNE-EN ISO
10140-3:2011, UNE-EN ISO 12354-2:2018 and UNE-EN ISO 16283-2:2016, and
the same term the accredited report strings already use. Also restore
the accent in the theory index anchor to the renamed intensity heading,
since heading slugs keep accented characters.
* Theory pages: illustrate each section family with existing result figures
The seven theory pages carried no figures at all. Embed sixteen of the
existing generated figures (light and dark variants, Spanish variants on
the Spanish mirrors) where the concept matches the prose: weighting
curves, time integration and the p-p intensity estimator in the signal
analysis page; equal-loudness contours, the specific-loudness pattern
and the hearing-threshold panels in perception; NC/RC rating, the
Schroeder decay and the ISO 717-1 shifted reference in rooms and
buildings; the Lden profile, ISO 9613-1 absorption and the three sound
power routes in environment and transport; the scattering coefficient
and ISO 11654 rating in materials and surfaces; and the Wk weighting and
the resonator mobility triplet in vibration. Each embed reuses the alt
text of the guide that owns the figure and adds a one-line caption.
Also give the wave-simulation section hub the reading-order paragraph
its siblings already have, linking the closed-form pages the FDTD solver
cross-checks, in both languages.
* Address review findings from the Spanish locale sweeps
Second pass fixes flagged by review: five terminology occurrences the
line-based sweep missed because the noun and adjective were split by a
line wrap or emphasis markup (sound-power, rotorcraft-noise and two
section hubs); six Spanish pages that still embedded the English figure
variant although the _es variant exists (junction-transmission,
aircraft-noise airport contour, miso-coherence,
objective-intelligibility, psychoacoustic-annoyance twice); and 19 more
language="es" arguments on figure-reproduction plot calls the first
pass missed, chained-call receivers such as room.noise_criterion(spl)
.plot() and the axes-creating first call of the two-profile
atmospheric-refraction snippet among them. The lab-insulation plot
wrappers forward keyword arguments to the rating plot, so the three
calls excluded earlier now take the argument too. All Spanish python
fences re-checked to compile and EN/ES parity re-verified.
* Carry the UNE terminology into the generated-asset Spanish strings
Apply the same evidence-based decisions to every Spanish string source
that feeds generated assets: the translation tables of
generate_graphs.py and generate_diagrams.py, the _STRINGS tables of the
plot modules and the report i18n values. 35 strings change: "presión
acústica" (UNE-EN ISO 16283-2:2016, 10052:2005, 3746:2011),
"potencia acústica" (UNE-EN ISO 3746:2011, 4871:2010, 9614-1:2010),
"intensidad acústica" (UNE-EN 61043:1999, 9614-1:2010), "absorción
acústica" (UNE-EN ISO 3746:2011) and "nivel de presión acústica de
impactos" (UNE-EN ISO 12354-2:2018). Strings the UNE editions keep are
untouched: "emisión sonora", "exposición sonora", "campo sonoro",
"fuente sonora". Only Spanish values change; the English keys stay.
The two plot-i18n test assertions that pin the axis label follow, and
the eight Spanish doc fences that deliberately mirrored the old figure
labels now mirror the new ones.
* Regenerate the Spanish figure variants with the UNE terminology
make graphs after the string-source change: 48 files, all of them _es
or _es_dark variants (24 figure/diagram pairs), picking up "presión
acústica", "potencia acústica", "intensidad acústica", "absorción
acústica" and "nivel de presión acústica de impactos" in titles and
axis labels. The English variants are untouched. The committed example
fiches need no regeneration: every example in generate_reports.py
renders in English, so no changed Spanish report string reaches them.
* Align the intensity-fiche Spanish assertions with the UNE terminology
* Record that ISO 10848-1:2017 corrects the 2006 critical-frequency misprint
The second edition prints the pi-free thin-plate critical frequency in its
Formula (5), confirming the spurious pi of the 2006 Formula (20) as a
misprint. The errata entry now records the upstream correction and the
docstring cites the 2017 formula alongside the existing evidence.
* Clarify the errata status sentence for the corrected misprint
* Add ResampledSignalResult.plot(): the delivered anti-alias filter against its design spec
* Rewrite the multichannel guide intro: concrete use cases, per-channel guarantees, Bendat & Piersol citations
* Give the levels page a real introduction: descriptor families, when-to-use guidance and survey references
* Time weighting: framing intro with detector history, ANSI S1.4/Bies references and sibling links
* Calibration: proper introduction (SPL vs dBFS choice, workflow framing) with Bies reference and sibling links
* Add eleven core-metrology figures: resampling spec, cepstrum variants, liftering, correlation, IR alignment, Hilbert envelope, cross-spectra, Golay recovery, TSA noise law, runs test
* Test signals: anti-alias figure with dual snippet, res.plot() mention, See also, docs mirror sync
* Cepstrum guide: variants and liftering figures with dual snippets, complex-cepstrum plot mention
* Correlation guide: normalization, IR-alignment and Hilbert-envelope figures with dual snippets
* Spectral analysis: cross-spectral density and coherent-output-SNR figures with dual snippets
* System measurement: Golay exact-recovery figure and plot mentions for the inversion workflow
* Synchronous averaging: sqrt(N) figure, diagnostics-tool guidance, See also, recording naming
* Data qualification: runs-test figure with dual snippet and peak-statistics plot mention
* MISO coherence: conditioning-order snippet, practical pitfalls and See also across the three trees
* Time-frequency: Harris window reference and See also across the three trees
* GUM uncertainty: map the domain pages that consume the clause-5 machinery, expand See also
* Filter banks: compliance plot mentions and recording naming in the crossover snippet
* Changelog entry for the core-metrology documentation figures and prose
* Regenerate llms-full.txt for the core-metrology documentation
* Drop the duplicated phase-axis translation key
* Add ModulationDistortionResult and per-band FieldIndicators .plot() with ISO 9614-1 figure
* Electroacoustics: modulation-sideband and piston-impedance figures, docs mirror gains IEC 60268-4/-5 report sections
* Noise control: side-branch, HVAC end-reflection and enclosure figures across guide, docs and Spanish mirrors
* Swept-sine distortion: phase-decomposition figure and Farina-method plot mention, docs mirror synced
* Programme loudness: gating and loudness-range figures with plot mentions across guide, docs and Spanish mirrors
* Sound power: report and declaration sections ported to the docs mirror, plot mentions and Spanish decimal fixes
* Regenerate API docs and llms exports, changelog entry for the emission and electroacoustics figures
* Add atmospheric-refraction and barrier insertion-loss documentation figures
Four new deterministic figures in the four language/theme variants:
the log-linear effective sound-speed profiles (downward vs upward
refraction), the downward-refraction ray fan, the GFPE relative level
versus range at the receiver height for three atmospheres, and the
Kurze-Anderson / exact half-plane / coherent-ground barrier
insertion-loss comparison on the guide geometry.
* Environment and underwater guides: figures embedded, model-selection prose, mirror sync
Embed the new figures with reproducible code blocks in the
atmospheric-refraction and ground-and-barriers pages (EN, ES and the
docs mirrors) and add the missing .plot() one-liners next to every
plottable snippet, including the outdoor-propagation usage and report
examples.
Prose: a when-does-refraction-matter introduction for the
atmospheric-refraction guide (Attenborough & Van Renterghem 2021,
Ch. 11); a Choosing-a-model section for the underwater-propagation
guide covering the validity ranges of the UNESCO / Del Grosso /
Mackenzie sound-speed equations and the Francois-Garrison /
Ainslie-McColm / Thorp absorption models, spreading-law guidance, the
ray / normal-mode / parabolic-equation selection table after Jensen
et al. (2011, Ch. 1) and a worked passive-sonar budget; and, for the
underwater-acoustics guide, the ISO 17208-1 measurement discipline
(CPA distance, +/-30 degree data window, four runs, averaging chain)
and the ISO 18406 regulatory context (scope, the 750 m reference
position) behind the closed forms.
Mirror sync: the outdoor-propagation docs mirror gains the missing
Prediction reports section, the ground-barriers and
atmospheric-refraction docs mirrors gain the figure code blocks, and
the ES mirrors are updated with equivalent text (decimal commas fixed
in three stray values). ISO 18405 added to the underwater-propagation
references; llms-full.txt regenerated.
* Fix the porous-absorbers link in the ground-barriers docs mirror and a stray comment label
* Regenerate llms-full.txt after the docs link fix
* Address review notes: shadow boundary in PE snippets, solver wording, Fresnel decades, barrier legend
The atmospheric_pe_range figure snippets now draw the closed-form
shadow-zone boundary they show; the underwater solver table describes
normal_modes as a finite-difference modal sum and the PE as marching
the range-independent profile all three solvers assume; the barrier
comparison claim is corrected to two decades of Fresnel number; and
the 5 dB grazing line is relabelled as the Kurze-Anderson limit in the
figure (regenerated, all four variants), the snippets and the alt
texts. llms-full.txt regenerated.
* Perception, hearing and speech guides: result figures and when-to-use prose
Add ToneAssessment.plot(), which draws an ECMA-418-1 tone against the
prominence criterion of its own method (recovered from criterion_db) over the
whole range of interest, with the deciding margin marked, and a
view="levels" option on ToneAudibilityResult.plot() that exposes the
level-versus-frequency view the ISO 1996-2 fiche already embedded.
Add eleven single-concept figures to the perception, hearing and speech
guides, each a realistic result drawn by its own .plot(): the tone prominence
verdict, the ISO/PAS 20065 tone levels, the ISO/PAS 1996-3 onset detection,
the ISO 532-2 specific loudness, the ECMA-418-2 specific tonality, the
specific fluctuation strength, the SII band audibility under a hearing loss,
the ISO 7029 fractile band, the ISO 1999 NIPTS audiogram, the STOI per-band
correlation and the IEC 60268-16 per-band MTI. Every figure is embedded in
the English guide, the Spanish mirror and the docs mirror with the dual
snippet block, and every plottable snippet on those pages now names its
.plot().
Prose: the tone-prominence guide gains a comparison of the four tonality
assessments the library implements and when each applies, the
hearing-threshold guide explains how the ISO 7029 fractiles feed the ISO 1999
age component and gains a See-also section, and the objective-intelligibility
guide gains the "which measure, and when" section that existed only in the
docs mirror, extended with the STOI/ESTOI, STI and SII choice.
Also label the STOI band axis at whole-hertz resolution and let the shaded
critical band of the tone-audibility levels plot follow the figure
background, so it reads on the white report page and on dark figures alike.
* Correct two figure alt texts against the plotted values
The ISO 7029 fractile figure describes the median and the 90 % fractile with
the values it actually draws (10 to 50 dB and 22 to 74 dB), and the
ECMA-418-2 specific tonality peak is at 9 Bark_HMS.
* Add .plot() to the ISO 717 enlarged-range rating results
ExtendedWeightedRatingResult and ExtendedImpactRatingResult now carry the
full enlarged-range band curve (band_centers, measured) and expose a
.plot() that draws the measured curve over the whole range against the
shifted ISO 717 reference on the 16 core bands, with the unfavourable
deviations shaded, the enlarged-range bands marked and every covered
Annex B / A.2.1 adaptation term in the title. EN/ES labels follow the
existing renderer conventions.
* Field insulation guide: enlarged-range, field-chain, facade and survey-impact figures
Four new deterministic figures (extended_insulation_rating,
field_airborne_insulation, facade_field_insulation,
survey_impact_insulation) generated in the four language/theme variants,
embedded next to their snippets in the EN guide, the ES mirror and the
docs mirror with the dual usage/hand-written code blocks. One-line
.plot() mentions added for the extended rating, the field airborne
result, the survey impact result and the ISO 12999-1 band uncertainty.
The docs mirror gains the previously missing ISO 10052 survey-reports
section, and the survey snippet now defines the li and l1_2m inputs it
uses. Multi-line figure info boxes are translated as whole artists, and
the review fixes (headroom, legend placement, proper unit and Greek
glyphs) are applied to the new generators.
* Laboratory insulation guide: ISO 10140, ISO 15186-1 element and ISO 10848 Dn,f figures
Embed the new lab_insulation_result, intensity_element_insulation and
flanking_level_difference figures next to their snippets in the EN guide,
the ES mirror and the docs mirror, each with the dual usage/hand-written
code blocks. Add the one-line .plot() mentions for the laboratory
airborne result, the element-normalized intensity result and the
floor-covering improvement result, and a note on when Dn,f plots (the
inline example uses 9 bands, below the 16/5-band rating threshold).
* EN 12354 prediction guide: impact-term and radiated-power figures
Embed the new impact_prediction_terms and radiated_power_outdoor figures
next to their snippets in the EN guide, the ES mirror and the docs
mirror, with the dual usage/hand-written code blocks, and add the
one-line .plot() mentions for the airborne prediction, the impact
prediction, the facade prediction and the radiated-power result.
* Panel insulation guide: rated Sharp prediction figure and plot mentions
Embed the new single_panel_rating figure (the 6 mm glass Sharp prediction
rated per ISO 717-1, coincidence dip marked) in the EN guide, the ES
mirror and the docs mirror with the dual code blocks, and add the
one-line .plot() mentions for the single-panel, double-wall and
radiation-efficiency results next to the existing concept figure.
* Junction transmission guide: Kij design chart, EN 12354 worked example and see-also
Add the junction_kij_thickness figure (wave-approach Kij versus the plate
thickness ratio for the X, T and L junctions, identical-plates value
marked) next to the coupling-loss-factor section, a res.plot() mention
in its snippet, a worked example that feeds the predicted K12 = 9.8 dB
of the 100/200 mm concrete X-junction into the EN 12354-1 flanking model
(R'w = 55.4 dB, direct path dominant), and a See-also section linking
the prediction, laboratory, structure-borne power and mobility guides.
EN guide, ES mirror and docs mirror in sync.
* Junction transmission docs mirror: sync the Kij chart, worked example and see-also
* Structure-borne power and installed-source guides: plot mentions, Hopkins citation and see-also
Add the one-line .plot() mentions for the reception-plate power result
(EN 15657) and the installed-source prediction (EN 12354-5) next to their
existing figures, add the one-line .plot() usage to both figures' code
blocks, cite Hopkins (2007) Section 2.8 for the point-excited-plate power
input behind the clause 4.4.3 coupling term with a prose paragraph
connecting the mobility chain, and give the EN 12354-5 page a proper
See-also linking the EN 15657, ISO 7626, junction and EN 12354 guides.
EN guides, ES mirrors and docs mirrors in sync.
* Vibration FRF guides: plot mentions and transfer-stiffness docs sync
Add the one-line .plot() usage and mentions for the ISO/TS 7849
sound-power-from-vibration result, the EN 29052-1 dynamic-stiffness
result and the ISO 10846 indirect transfer-stiffness result next to
their existing figures (EN guides, ES mirrors and docs mirrors), and
port the previously missing ISO 10846 test-report fiche section to the
transfer-stiffness docs mirror.
* Mechanical mobility guide: driving-point reading figure
Embed the new mobility_result_lines figure (|Y(f)| of the SDOF bundle
with the stiffness line, the mass line and the 1/c damping peak) next to
the MobilityResult snippet in the EN guide, the ES mirror and the docs
mirror, with the dual usage/hand-written code blocks and the one-line
res.plot() mention.
* Human vibration and multiple-shock guides: one-line plot mentions
Add the res.plot() one-liners next to the existing figures for the
ISO 8041-1 weighting response, the ISO 2631-1 weighted band spectrum, the
Directive 2002/44/EC daily exposure A(8) and the ISO 2631-5 multiple-shock
assessment, in the EN guides, the ES mirrors and the docs mirrors.
* Docs mirrors: restore missing report-fiche and concept sections
Sync the docs mirrors of the pages touched by this wave with their site
guides: the ISO 16283-3 facade report subsection on the field insulation
page, the EN 29052-1 and ISO 7626 fiche example snippets, and the panel
concept-figure code block.
* Changelog, API docs and llms regeneration; lint and typing fixes for the figure generators
* Fix nested emphasis in the survey-impact figure captions
* Restore the numerical-propagation rasters truncated by a mid-regeneration snapshot
* Reconcile the figure-script additions with the rooms and materials wave
* docs: regenerate the llms bundle after the figure-script reconciliation
* Rooms & materials: TransferMatrix.plot() and nine result figures in the EN guides
* Rooms & materials: sync the docs mirrors (figures, plot mentions, report sections, diffuser-prediction port)
* Rooms & materials: ES guide mirrors, changelog and generated api-docs/llms
* Docs mirrors: ISO 10534-2 fiche section, slow-sound and reflectogram figure snippets
* Fix vertical line collisions in fiche prose with sub/superscripts
reportlab shifts a <sub>/<super> fragment by half the font size but
spaces paragraph lines by the style's fixed leading alone: its
autoLeading modes compute line bounds from per-fragment font sizes and
ignore the script rise entirely, so a wrapped fiche line ending in deep
subscripts (e.g. the EN 12354-5 L_n,s,ij formula strip) overprinted the
ascenders and superscripts of the next line.
Clearing the default half-em shift with fixed leading alone would need
1.65x-em line spacing on every prose style, growing each block by about
a third and breaking the dense one-page fiche layout. Instead, every
fiche paragraph is now built through a shared _layout.fiche_paragraph
factory (imported by the renderers as Paragraph) that rewrites the bare
<sub>/<sup>/<super> openers to the typographic quarter-em rise; the
worst-case line extent (deepest subscript directly above the tallest
superscript) is then 1.24x the font size, inside the fixed leading of
every fiche prose style, and the scripts sit closer to their base text
as accredited lab reports typeset them.
* Regenerate the example fiches with collision-safe sub/superscripts
* Report the true 1/fs time axis from time_synchronous_average
The averaged samples sit on the 1/fs sampling grid (each block is aligned
back to it by the band-limited fractional delay before averaging), but the
result reported a time axis with T/M spacing. For a non-integer number of
samples per period the two grids drift apart across the period: with
100.37 samples per period the noiseless recovery error read against the
reported axis was 9.3e-2, versus 2.8e-6 on the true m/fs grid.
Return times = arange(M)/fs, document that the M samples cover the period
only approximately when fs*T is not an integer (McFadden 1987 revised
model: a block average on the sampling grid, no angular resampling), and
assert the non-integer-period tests on the true grid, with a regression
test pinning the 100.37-sample case on both grids. Also soften the comb
response [0, 1] claim (float cancellation beside a tooth can exceed 1 by
a few 1e-9) and mark the ~14 dB N = 32 figure as derived from Eq. 8
rather than printed in the paper.
* Detect inverting echoes and off-sample delays in echo_detection
echo_detection picked the maximum of the signed power cepstrum, so an
inverting reflection (a < 0), whose first rahmonic is a itself and hence
negative, was silently missed: with a = -0.4 at 313 samples the true
rahmonic c[313] = -0.42 was ignored and an unrelated 4739-sample peak
returned. Peak-pick on |cepstrum| within the searched band instead and
report the signed cepstrum value at the peak as the reflection
coefficient (the Mercator series ln(1 + a e^{-j theta}) holds for either
sign of a).
Also refine the reported delay by three-point parabolic interpolation of
|cepstrum| through the peak, so an off-sample delay (e.g. 313.5 samples)
is estimated to a fraction of a sample, and document the bin-splitting
caveat: at off-sample delays the peak-sample coefficient underestimates
|a| (about 65 % of it midway between bins). One-line convention note
distinguishing Milner's IDFT(ln|X|^2) power cepstrum from Bogert's
squared original. Tests cover a < 0 (exact impulse-echo closed form and
a noise source) and the interpolated half-sample delay.
* Validate parametric-EQ sections at the numerical extremes
Three validation gaps in the RBJ cookbook designer:
- A wide 'bw' at an f0 near Nyquist drives the w0/sin(w0) warping factor
into math.sinh overflow, escaping as a raw OverflowError; slightly less
extreme pairs produced a large but finite alpha whose rounded a2 landed
exactly at -1.0: poles on the unit circle designed silently. Wrap the
sinh with an informative ValueError and check the realized coefficients
against the stability triangle (|a2| < 1, |a1| < 1 + a2) after
normalization.
- A shelf slope beyond the gain-dependent bound (A + 1/A)/(A + 1/A - 2)
makes the cookbook's alpha complex and surfaced as a bare 'math domain
error'. Validate the bound at section construction with the limit in
the message (any slope is fine at 0 dB, where A = 1).
- Non-finite f0/gain_db/q/bw/slope passed validation silently (NaN
compares false against every bound) and produced NaN/inf coefficient
rows. Require finiteness explicitly.
Tests cover all three paths plus extreme-but-valid sections that must
keep designing stably.
* Scope the envelope-spectrum amplitude claim and add the band-pass front end
The docstring promised that a sinusoidal modulation 'reads out as a line
at its exact amplitude'; that holds only when the modulation frequency
falls on an analysis bin. Off-bin lines read low by the taper's
scalloping loss (about 1.42 dB, ~15 %, for the default Hann midway
between bins), like any single-record amplitude spectrum. State the
on-bin condition on the closed forms and the caveat in the module,
function and result docstrings, pinned by a midway-between-bins test.
Also add the optional band=(low, high) pre-filter to envelope_spectrum:
a zero-phase 4th-order Butterworth band-pass ahead of the detector, the
band-pass front end of Bendat & Piersol Figure 13.11 and the classical
bearing-envelope chain (isolate the excited resonance band, then
envelope it). The band travels with the result; a test shows it
recovering the exact modulation line under a strong out-of-band
interferer that swamps the raw envelope.
* Warn when a tone burst cannot close on the tone's zero crossing
IEC 60268-1 Clause A2.1 asks for an integral number of full periods, but
the gate closes after round(fs*cycles/frequency) samples: sample-exact
only when f and fs are commensurate. For 10 cycles of 997 Hz at 48 kHz
the burst spans 481.44 samples, gated at 481, and the waveform carries a
residual step at the gate edge. Document the condition and emit a
PhonometryWarning quantifying the realized residual (bounded by
amplitude*sin(pi*f/fs)) for incommensurate pairs; commensurate bursts
stay silent.
Also tighten the resampler's 'equiripple-bounded' wording: the Kaiser
window method bounds both bands by the same delta = 10^(-A/20), but its
ripple decays away from the band edges, unlike a true equiripple design.
* Document estimator conventions verified against their sources
Documentation and comment corrections, no behavior changes:
- random_data: keep the reverse-arrangement acceptance bounds on the
normal approximation deliberately and say why: B&P Table A.6 itself
follows it, and no percentage-point convention on the exact Mahonian
distribution reproduces the published entries (N = 40 and N = 100 at
a = 0.975 pin it down), so exact bounds would disagree with the table
users check against. The p-value stays exact; the one-count boundary
disagreement is now documented on TrendTestResult.
- time_frequency: the spectrogram energy-recovery statement was wrong as
written; integrating 'density' columns over frequency and summing over
time recovers the record energy only after multiplying by the hop
duration, up to the taper roll-off at the record edges (verified
0.988 vs 1 for Hann at 75 % overlap on white noise).
- spectra: note that the adaptive multitaper sigma^2 is mean(x^2) with no
mean removal (consistent with the documented no-detrending
calibration), and mark the 'P&W Eq. 369a' citation as pending
verification against the book.
- compliance + tests/reference_data: transcription note for the bare
'+3' at 20 Hz, Type 2, in ANSI S1.4-1983 Table V (read as upper-only;
+/-3 plausible; either way the verdict cannot change, the realized
deviation is -0.05 dB).
- room_ir: the Mueller & Massarani 'below 4 dB' crest-factor figure
assumes their slight magnitude smoothing, which shaped_sweep_signal
does not implement; measured sweeps here sit at 4.2-4.3 dB.
* Translate EQ section labels, complete the bibliography and changelog
- The per-section legend labels of the parametric-EQ plot printed the raw
English filter-type names in Spanish figures; route them through the
plot string table (peaking -> campana, shelves, band-pass, notch,
all-pass; lowpass/highpass were already translated for the lifter
plot). English output stays byte-identical.
- docs/references.md: add the Harris 1978 window paper and IEC
60268-1:1985, both cited by guide pages but missing from the collected
bibliography; link the IEC entry (and the one in test-signals.md) to
the IEC webstore page.
- Changelog entries for the DSP fixes; regenerate the API reference,
llms-full.txt and the cepstrum_echo figures (the detected-echo marker
moved by the sub-sample delay interpolation); the conformance report is
unchanged (419/419).
* Propagate the echo and envelope semantics to the site guides
Review follow-ups:
- The EN and ES site guides (cepstrum-echoes.mdx) still described the
old echo reading and the unqualified 'exact amplitude' claim; bring
them in step with docs/cepstrum-echoes.md: |cepstrum| peak-picking,
signed coefficient, interpolated delay, bin-splitting caveat, the
on-bin/scalloping condition and the new band= front end (the docs page
gains the same envelope-section text).
- State in the changelog and the result docstring that delay is no
longer exactly delay_samples/fs when the interpolated offset is
nonzero (delay_samples remains the whole-sample index the coefficient
is read at), and merge the duplicated Unreleased headings.
- Clearer stability message (poles 'do not lie strictly inside the unit
circle', full-precision coefficients) and an informative short-record
error for the envelope band-pass pre-filter instead of scipy's padlen
message.
- Sharper tone-burst warning test (asserts the quantified span and
residual) and a tighter interpolated-delay tolerance.
* Address review notes: cepstrum terminology, band-pass claims, edge validation
- Guides (docs and site, EN and ES): call the signed quantity what it is,
Milner's cepstrum of the log-power spectrum, and spell out that
Bogert's original 1963 power cepstrum squares once more and is
non-negative, so the signed rahmonics are the library's documented
convention; qualify the band-pass front end (out-of-band content is
strongly attenuated, not eliminated: finite Butterworth rejection and
small zero-phase edge transients).
- cepstrum: render reflection_coefficient as inline code in the
delay_samples docstring so the generated API page stops linking the
unrelated underwater seabed function.
- envelope: validate that 'band' is exactly two numeric edges before
indexing (a one-item tuple raised IndexError, extra entries were
ignored); the validated edges now travel to the result.
- equalizer: reject finite but unrepresentable gains through a shared
_gain_amplitude helper (10^(gain_db/40) overflowing, or underflowing
to zero and then dividing by zero in the designers); document the 0 dB
exception to the shelf-slope bound on the slope field.
- tone_burst: validate the repetition configuration before emitting the
incommensurate-frequency warning, so invalid setups raise even under
a warnings-as-errors filter.
- comb_filter_response: reject phase products that overflow to infinity
(sin(inf) returned NaN instead of the documented bounded response).
Tests for every new rejection path; API reference and llms regenerated.
* Test the representable gain amplitude as a positive finite bound
* Scope the first-rahmonic height claim to the source-free identity
* Correct the ISO 15186-1 Formula (8) N-term and complete the fiche's Clause 8 content
The printed Formula (8) subtracts its 10 lg(N) term, which cannot be
derived: ISO 10140-2:2010 Formula (6) adds the multi-unit correction and
ISO 15186-2:2010 Formula (12) prints the same relation without an N term.
intensity_element_normalized_difference() now returns the derivable
per-unit value (+10 lg N), warns whenever n > 1 deviates from the print,
and the defect is registered in docs/ERRATA.md.
Clause 8 completeness of the intensity fiches: the method statement now
states the test-object and measurement-surface areas S / Sm carried by
the result (Clause 8 g), and report() accepts optional per-band FpI and
pressure-residual intensity index spectra annexed as table columns
(Clause 8 i). The 6.4.2 qualification wording is corrected to 'must not
exceed 10 dB' (the clause flags FpI > 10 dB), the band-set captions now
cite ISO 717-1:2020 Clause 5.3 (statement of results) instead of 4.4,
and the Table B.1 Kc oracle gains the three printed rows 3150/4000/5000
Hz (0,1 dB each) so all 21 rows are pinned.
* Label the road-traffic facade fiche R'tr,s instead of R'45
FacadeInsulationResult now carries the measurement method
(loudspeaker or road_traffic), and the ISO 16283-3 r_prime fiche
selects its labels from it: the road-traffic apparent index is the
distinct quantity R'tr,s (Clause 3.13, -3 dB all-angle correction),
so its report no longer prints the R'45 loudspeaker basis line
(Clause 3.12) over road-traffic numbers.
* Make the ISO 10848 fiche part selectable and register the Formula (20) misprint
The Dn,f / Ln,f quantities are defined in the ISO 10848-1:2006 frame
document (Formulas (4)/(5)); which part governs a measurement depends on
the construction under test, so hardcoding 'ISO 10848-2:2006' in the
basis line mislabelled Part 3 and Part 4 measurements. report() now
accepts part=2/3/4 (default 2) and names ISO 10848-2:2006,
ISO 10848-3:2006 or ISO 10848-4:2010 accordingly, in both languages.
ISO 10848-1:2006 Clause 8.1.1 Formula (20) prints the thin-plate
critical frequency with a spurious extra pi in the denominator (the 1,8
constant already carries 2 pi / sqrt(12)); the implementation was
already pi-free, and the defect is now registered in docs/ERRATA.md
with a misprint note in the critical_frequency docstring, corroborated
by ISO 12354-1:2017 and Hopkins Eq. 2.201.
* Normalize the K24 mass-ratio convention and correct the K24 errata entry
EN 12354-1:2000 prints its two K24 lines in two different mass-ratio
conventions without saying so: the E.5 line follows the annex's own
per-path definition M = lg(m'perp,i/m'i), while the Figure E.9 (E.7)
line is stated in the figure-axis variable lg(m2/m1). Read with the
declared per-path M, the E.9 print contradicts its own curve;
ISO 12354-1:2017 E.3.5 prints the same relation consistently in the
per-path convention (+14,1 M). Re-deriving both editions against the
figures shows the earlier errata entry misread this recast as a 2017
sign misprint; the entry is rewritten accordingly.
junction_vibration_reduction() now applies the documented per-path
mass_ratio = m'perp,i/m'i to every branch: the E.7 double-leaf branch
takes leaf-over-homogeneous ratios (validity m2/m1 > 3 reads
mass_ratio < 1/3) and evaluates 3,0 + 14,1 M + 5,7 M^2, numerically
identical to the 2000 figure. Also cites Kij,min as Formula (29)
(Clause 4.4.2) and documents the ISO 12354-1:2017 both-negative
lining-combination rule next to the 2000 form combine_linings()
implements.
* Broadcast a single-number source level across the EN 12354-5 path bands
installed_source_prediction() derived its band count from the source
levels alone, so a single-number characteristic power with per-band
path data either raised a spurious band-count error or produced a
one-element installed_power_level that crashed the fiche table with an
IndexError. The band count is now the widest per-band input, every
per-band input is validated against it (one value broadcasts), and the
result's installed_power_level is broadcast to the band axis; a fiche
regression test covers the scalar-source case.
Also corrects the coupling-term docstrings: D_C is not guaranteed
non-negative, since near a mounting resonance with comparable,
opposite-phase source and receiver mobilities the |Ys + Yi|^2 numerator
collapses (counterexample in the docstring).
* Use a prediction-aware footer disclaimer on the EN 12354 fiches
The shared footer printed 'The results relate only to the tested
specimen.' on every fiche, including prediction sheets that have no
tested specimen. footer_flow() now takes predicted=True (also exposed
through the shared sound-power renderer) and prints 'Predicted result:
the values relate only to the modelled configuration, not to a tested
specimen.' instead; the EN 12354-1/-2/-3 and EN 12354-5 prediction
fiches use it, in both languages.
Also corrects the dated designations on the prediction basis lines:
the 2000 editions are EN 12354-1/-2/-3:2000 (there was no ISO 12354 in
2000), so 'EN/ISO 12354-x:2000' is replaced by 'EN 12354-x:2000'.
* Declare the actual band set on the ISO 10052 basis line and align ES impact terms
The survey fiches accept both the octave and the one-third-octave band
sets, but their standard-basis lines hardcoded '(survey method, octave
bands)'; the line now follows the band set the reported curve actually
carries, in both languages (ISO 717-1:2020 Clause 5.3 / ISO 717-2:2020
Clause 4.4 require stating it). The remaining comments citing the
band-set statement as ISO 717-1 Clause 4.4 now cite 5.3 (717-2 keeps
4.4), and the ISO 10140 fiche docstring cites the test-report clause as
Clause 9 (Clause 6 is the test arrangement).
Spanish terminology of the field impact quantities is aligned with the
UNE editions: UNE-EN ISO 16283-2:2016 defines 'nivel de presión
acústica estandarizado/normalizado de ruido de impactos' (3.13/3.14),
now used by the ISO 16283-2 and ISO 10052 basis lines; the ISO 10140-3
and ISO 12354-2 strings already match their own UNE editions
(UNE-EN ISO 10140-3:2011 3.2, UNE-EN ISO 12354-2:2018 3.1.1/3.1.2) and
stay unchanged.
* Qualify a background-limited ISO 16251-1 result as a lower bound
When any band sits at the 1,3 dB limit of measurement (Formula (2), the
per-band values already reported as > delta-L), the fiche now prints
the boxed delta-Lw with a >= qualifier and a note stating it is a lower
bound, in both languages. A manually built result without CI,delta
prints the bare delta-Lw instead of fabricating a (+0) adaptation term.
The ISO 717-2:2020 Annex C.2 errata entry is extended with the second,
independent print defect: the example's 'Ln,sum = 75,2527 = 75 dB'
reproduces to all printed digits only as the energy sum of the wrong
column over the wrong range (the measured floor with covering, 16 bands
100-3150 Hz), while the A.2.1 sum of the covered reference floor over
100-2500 Hz rounds to 76 dB with either 800 Hz value, so CI,r = -2,
CI,delta = -9 and delta-Llin = 6 dB; the earlier claim that the 800 Hz
misprint alone shifted the CI chain is corrected.
* Regenerate the conformance report, API docs and affected fiche examples
The Kc Table B.1 conformance row now covers all 21 printed rows, the
building API pages pick up the revised docstrings, and the EN 12354
prediction and ISO 15186-1 example fiches are re-rendered with the
prediction footer, the corrected designations and the Clause 8 areas
statement (kept to one page). CHANGELOG entries under [Unreleased].
* Validate the frequency axis and the facade method on the building results
The installed structure-borne prediction now rejects a frequency axis whose
length differs from the resolved band count, so a result can no longer carry
an inconsistent per-band table. FacadeInsulationResult validates its method
on construction, closing the direct-construction path that would render an
unknown method as the loudspeaker quantity. The band-count note also names
the characteristic level and coupling term as width-setting inputs.
A review pass over the psychoacoustics, hearing, speech, environmental and
noise-control fiches. The numerics held up; what did not were the statements
printed around them, plus one wrong table lookup.
ISO 1999 validated domain. nipts()/htlan() applied Formula (2) wherever they
were asked to: L_EX,8h = 130 dB over 60 years at the 0,99 fractile returned a
357,9 dB threshold shift with nothing marking it as an extrapolation. Since the
formula is quadratic in (L_EX,8h - L0) that number is meaningless, not merely
uncertain. Both now warn (NoiseInducedHearingLossWarning) outside the stated
ranges while still computing: durations outside 1-40 years (clause 6.3.1
validates Formula (2) over 10-40 and Formula (3) over 1-10), fractiles in the
tails clause 6.3.2 says "should not be estimated" (Q below 5 % or above 95 %),
and levels above the 100 dB of Annex D that the Scope's NOTE 4 restricts
validity to. The fiches print the caveat when the conditions sit outside.
ISO 1999 Q. The fiches printed the library's fractile under ISO 1999's symbol,
inverting its meaning: in Formulae (4)/(5) Q is the percentage with worse
hearing, so the most-susceptible tenth is Q = 10 %, not the "Q = 0.9" the fiche
showed while citing 6.3.2. They now print ISO's Q as ISO defines it; the guides
note the difference from the fractile argument. Two further disclosures on the
same fiches: the age component H is evaluated from ISO 7029:2017 (the edition
ISO 1999 references undated in 6.2.2), which departs from the illustrative
Table A.3 selection by up to about 7 dB at the median, and the 2/3/4 kHz
average is the user's own choice, the Scope's NOTE 1 specifying no frequency
combination. The footer states a population scope rather than "the results
relate only to the tested specimen", which describes a laboratory specimen.
ISO 1999 Annex C is wired in as an independent oracle: its worked example (male
population aged 50, 90 dB for 30 years, 1/2/4 kHz at Q = 10 %) reproduces the
Table D.2 shifts it consumes (0, 9, 19 dB), the 13,3 dB left of the 4 kHz shift
after the Formula (1) compression (C.5) and the 31,1 dB combined threshold
(C.11). The composition it exercises is now exposed as combine_age_and_noise(),
which clause 6.2 wants available on its own: 6.2.3 recommends a database B
collected on a control population of the country under consideration, and this
applies Formula (1) to whichever database the user brings.
Verdict precision. The STI and SII fiches printed the requirement with one
decimal beside a measured value at full precision, so a verdict could contradict
itself: "STI = 0.50, required >= 0.5 -> FAIL" against a 0,52 requirement, and an
SII minimum of 0,75 printed as "0.8" (the committed example said so). Both now
print the requirement at the quantity's own precision. IEC 60268-16 Annex M
joins the conformance report as an end-to-end oracle: its printed MTF matrix,
speech spectrum and ambient noise give back its published MTI row and
STI = 0,76, exercising the whole A.5.3 to A.5.8 chain.
Noise-control prediction framing. The enclosure, silencer and HVAC fiches
compute design models but read as measurements: no prediction statement,
measurement headers, a footer scoped to a tested specimen, and committed
examples citing instrumentation that never existed ("Two-microphone
transfer-matrix bench"). They now follow the EN/ISO 12354 prediction exemplar
already in the repo, with a prediction-basis line, a statement naming what each
model does not represent, and a prediction footer. Their verdicts also decided
on the half-away-from-zero display rounding while printing Python's
round-half-to-even, so 0,25 printed 0.2 against a verdict taken on 0,3; both
now use the display rounding.
ISO/PAS 1996-3. With every onset rate at or below 10 dB/s the fiche withheld
the adjustment on the onset-rate gate (clauses 4.5/8) but justified it with "no
prominent impulse is present (governing P = 6.01 <= 5)", contradicting its own
boxed P. The note now states whichever gate governs. The assessment period is
read from the result instead of being hardcoded: 30 min is the Clause 5
*default*, now the default of the new assessment_period_min argument.
Smaller corrections:
- the HVAC elbow insertion loss took the wrong row of Bies Table 8.11 for a
W/lambda exactly on a bin edge; the rows read "a <= W/lambda < b", so an edge
opens its row (1 dB at 0,14, not 0 dB);
- the wind-turbine tonality fiche cited "subclauses 9.5.2-9.5.5" of
IEC 61400-11 for a chain running through 9.5.6 to 9.5.8 (Formulae 31-34);
- the ISO 1996-2 tonal fiche notes that Table J.1 keys on the mean audibility
of the J assessed spectra (ISO/PAS 20065 Clause 5.3.9), which a
single-spectrum assessment stands in for;
- the shared 10-90 % fractile band carried a hardcoded English legend in
Spanish figures, and the Spanish silencer and HVAC fiches printed the device
kind and duct-element label in English.
docs/ERRATA.md records two more source defects found on the way: ISO/PAS
1996-3:2022 Clause 5 swaps the 3.4/3.5 cross-references of the onset rate and
the level difference (its own units contradict the clause numbers printed with
them), and ISO 9613-2:1996 Table 2 prints 4,1 dB/km at 15 C / 80 % / 1 kHz
where ISO 9613-1 gives 4,1511, the neighbouring cells rounding correctly. The
library computes that coefficient from ISO 9613-1 directly, so it is unaffected.
Committed example fiches, the conformance report, the API reference and the
llms files are regenerated.
* Return an infinite bottom loss at intromission zeros without a warning
At an angle of intromission the Rayleigh reflection coefficient vanishes
and BL = -20 lg|R| is legitimately infinite (total transmission into the
sediment). The unguarded log10 emitted a RuntimeWarning when |R| reached
exactly zero; wrap it in np.errstate(divide="ignore") in both
bottom_reflection_loss and seabed_reflection and document the inf value.
Tests cover the closed-form intromission angle for a slow mud bottom
(rho2 = 1300, c2 = 1400: 27.585 deg) and an exact |R| = 0 case, asserting
no warning leaks.
* Skip the phon contour labels when the plotted range excludes 1 kHz
The ISO 226 equal-loudness plot annotates each contour at its 1 kHz
crossing (SPL = phon by definition) with the text placed at 1180 Hz. On a
frequency subset that excludes 1 kHz the labels landed outside the axes;
they are now drawn only when the plotted range includes 1 kHz. The
default full-range figure is unchanged.
* Scope piston directivity plot kwargs to single-ka results
plot_piston_directivity applied user plot kwargs to every curve, so a
single color or label collapsed the multi-ka family styling and its
legend. User kwargs now restyle the curve only when the result holds a
single ka and are ignored for a family; both docstrings state the
behavior. The committed multi-ka figure passes no kwargs and is
unchanged.
* docs: regenerate the API reference pages for the updated piston and seabed docstrings
* Fix slit-radiation sign, generalize Kirchhoff obliquity, record paper errata
The slit-to-free-air radiation correction of the slow-sound absorber was
transcribed with the papers' printed -iw sign, which in the e^{+jwt}
convention of the library acts as a negative mass and raises the slit-panel
resonance (378.6 to 386.8 Hz for the documented base geometry) where an
added radiation mass must lower it (370.8 Hz). The term is now conjugated
like the Stinson duct series of the same model, with the direction and the
shifted peak pinned by a regression test, and both source misprints (the
sign and the 4-vs-64 duct-series constant) are recorded in the errata
registry. The committed slow-sound figures are regenerated; the
critical-coupling design they show self-compensates to alpha = 1, but the
solved cavity length moves from 48.3 mm to 44.7 mm.
The diffuser far-field predictor now uses the symmetric Kirchhoff obliquity
factor (cos theta + cos psi)/2 for oblique sources, which reduces exactly
to the Cox and D'Antonio Eq. (9.32) normal-incidence form at psi = 0, so
all normal-incidence anchors are unchanged.
Also: the EN 29052-1 low-resistivity warning now echoes the clause 8.2
requirement to state the estimated error in the test report, and the
ISO 9053-2 volume-flow helper documents the normative 6.2 form against the
Annex A.2 rms variant.
* Update the porous-absorbers misprint count for the new errata entries
The page's sources now have five recorded misprints (Maa Eq. (5b), two in
Attenborough & Van Renterghem 2e, and the two Jimenez et al. entries); the
English and Spanish guides are kept in step.
The committed 37-receiver polar arc in tests/reference_data.py is now
generated by the library's own Fraunhofer far-field model for a published
geometry: the N = 7 QRD, 6 periods, 3.6 m wide, 0.2 m deep row of
Cox & D'Antonio, Acoustic Absorbers and Diffusers, 3rd ed., Appendix B
(the commercial N = 7 QRD of Hargreaves, Cox, Lam & D'Antonio, JASA 108(4),
1710-1720, 2000, Table I). The arc is an arithmetic oracle for ISO 17497-2
Formulas (5)/(7): d = 0.1099, flat reference d = 0.0049, d_n = 0.1055,
recomputed exactly from the committed rounded levels.
A new external third-party anchor pins the model's one-third-octave
band-averaged normalised diffusion to the published Appendix B 2D BEM values
for that row at normal incidence in the 200-400 Hz bands (agreement within
0.01, asserted at +/-0.015) in the tests and in four new conformance rows.
Across the full published 100-5000 Hz range the model-vs-BEM mean absolute
deviation is about 0.09, documented everywhere as a low-band anchor.
The previous in-house COMSOL simulation arc and its numbers are removed from
the tests, the conformance report, the surface-scattering guide (EN/ES), the
theory pages and the diffusion_polar figure, which now all use the same
published-geometry prediction.
- ISO 3741:2010, 9.1.2/9.1.3: apply the background correction at each
microphone position (K1i, Eq. 14), correct the position levels first
(Eq. 15) and only then energy-average (Eq. 16). Pre-averaged 1D spectra
keep the single-K1 path, documented as an approximation.
- ISO 9614-2:1996, 10.6 b: omit the bands failing criteria 1 and/or 2 from
the A-weighted total, flag them in the new a_weighting_omitted_bands field
and name them in the fiche basis strip (EN/ES); warn when the criteria
inputs are absent and the screening cannot run.
- ISO 9614-3:2002, C.1.6.2: a band satisfying criterion 5 qualifies as a
final result even if FS(2) >= 2.
- ISO 3745:2012: bands above 10 kHz no longer abort the determination; only
the A-weighted total (outside the ISO 3744 Annex E table) is NaN.
- ISO 3744:2010, 8.2.3 / ISO 3746:2010, 8.3.3: K1 = 0 only strictly above
the upper criterion; Equation (16) still applies at exactly 15 dB / 10 dB.
- ISO 4871:1996, 3.16/6.2: verify dual-number declarations against the sum
of the separately rounded declared values (verified_dual), and lay the
dual-number fiche out exactly as Annex B.2 (no derived L_WAd row).
- Cite clause 8.4 for the apparent directivity indices and note that the
4.3.2 validity limit concerns K2A (per-band check kept, conservative).
Hand-derived oracles cover each correction; the ISO 4871 and ISO 9614-2
example fiches, the API reference pages and the llms text are regenerated.
Rate NC spectra by the standard's two-step procedure (5.2.2): the speech
interference level (clause 3.2, the average of the 500/1000/2000/4000 Hz
octave-band levels) selects the NC-(SIL) curve and the spectrum is
designated NC-(SIL) unless an octave band exceeds it, in which case the
tangency method (5.2.3) applies. NCResult gains sil, tangency_rating,
method, out_of_range and a label property; the tangency rating stays
available on every in-family result. Spectra outside the NC-15 to NC-70
family of Table 1 are flagged out of range instead of clamping the
interpolation to fabricated NC-71/NC-14 designations: above the family
the governing band is the maximum exceedance over the NC-70 curve and the
label reads ">NC-70 (band)"; below it, "<NC-15". Fiche, plot and
verdict render the flagged designation in English and Spanish.
On the RC Mark II side, warn when the clause D.4 minimum band set
(31.5 Hz through 4000 Hz) is incomplete, flag ratings outside the
tabulated RC-25 to RC-50 family (Table D.1) with an extrapolation note on
the fiche, and document the combined RH tag as a diagnostic extension
beyond the clause D.3.5 letters (N, R, H, RV; the RV vibration/rattle
classification needs the Table 6 test and is not implemented).
On the ISO 3382-1/-2 fiche, label the mid-frequency descriptor honestly
for one-third-octave data (only the 500 Hz and 1 kHz one-third-octave
bands are averaged, so the octave "500-1000 Hz" claim is dropped and the
bands are named), name the band of a fallback T30/EDT descriptor when the
range does not span both mid bands, and choose the EDT_mid vs EDT label
from EDT's own band coverage instead of T30's. Fiche verdicts (ISO 3382
and S12.2) now compare display-rounded values so the printed numbers can
never contradict the verdict at a tolerance boundary.
Correct two citations in the room-acoustics docstrings: the 5 %
reverberation-time JND is ISO 3382-1:2009 Table A.1 (ISO 3382-2:2008
Table A.1 tabulates the uncertainty constants G and H) and the -20 dB
onset trigger is ISO 3382-1:2009 A.3.4, with a note on the at-edge
sample choice. English ISO 3382-3 strings use point decimals
(STI = 0.50/0.20); the Spanish strings keep the comma.
Docs (EN/ES), API reference, conformance check, changelog and the
affected example fiches are updated accordingly.
Directive 2002/44/EC bases the whole-body daily exposure A(8) on the
highest of the frequency-weighted axis values 1,4*a_wx, 1,4*a_wy, a_wz
(Annex, Part B, point 1), not on the ISO 2631-1 Eq. (10) vector total
a_v; the hand-arm side keeps the Part A vector total a_hv.
- Relabel the whole-body magnitude of the daily-exposure fiche as
a_w,max with a printed note stating the Part B basis (EN/ES); add
vibration.wbv_exposure_basis() returning the dominant-axis value and
document in daily_vibration_exposure which magnitude each kind must
be fed with; state the same basis in the human-vibration guide
(EN/ES) and the measurement-chain diagram.
- Derive the boxed exposure zone of the fiche from the same
displayed-rounded comparisons as the assessment rows and the verdict,
so an A(8) printing exactly at a threshold cannot show a zone one
step below its own Exceeded row.
- ISO 10848 Kij fiche: distinguish an empty single-number mean caused
by every in-range band being bracketed (M < 0,25) from a spectrum
with no bands in the Annex A range (EN/ES).
- Spanish ISO 2631-5 outputs: translate the subject sex in the
injury-probability plot title and the word Formula in the fiche's
clause references.
- Correct the running_rms comment: the linear window divides by the
full window length (zero-padded front), it does not average over the
available samples.
The ruff 0.16 adoption applied the PLC0414 and F401 unsafe fixes to
src/phonometry/_plotting.py and deleted all 82 redundant-alias re-exports,
leaving only the docstring. The module is documented as a silent re-export
of the plot renderers (moved to phonometry._plot) for one deprecation
cycle, so from phonometry._plotting import plot_impulse_response and every
sibling raised ImportError. The sibling shim scripts/fdtd2d.py got the
explicit __all__ treatment in the same commit; this one was missed.
Bring back all 82 re-exports as grouped plain imports per source module
and declare an explicit __all__ so the lint fixers keep them (every target
still exists in phonometry._plot, none dropped). Keep the module out of
_compat._MOVED on purpose: that table maps one old path to one relocated
module and warns on attribute access, while _plotting fans out to thirteen
domain modules and is documented as silent.
Harden tests/test_deprecated_aliases.py so this cannot regress silently:
every pre-move module path must now expose a non-empty public surface, and
a new test pins the frozen 82-name _plotting re-export snapshot, asserting
__all__ matches and each name resolves to a callable without warnings.
Pure structural refactor of the two functions flagged by SonarCloud S3776,
with no numeric change:
- ISO 532-1 _calc_slopes: the two segment kinds of the slope state machine
move to _masked_segment/_flat_segment and the Table A.9 range searches to
_rns_index_of/_next_rns_index.
- Osses 2016 _analyze: the per-frame excitation front-end, generalised
modulation depth and neighbour cross-covariance become _band_envelopes,
_modulation_depth and _neighbour_covariance.
Outputs verified byte-for-byte identical on AM/FM tones, AM noise and
stationary/time-varying loudness cases; the committed conformance report
regenerates unchanged.
* Add slow-sound slit and Helmholtz-resonator perfect absorbers
Introduce materials.slow_sound_absorber, a transfer-matrix model of a
rigid panel of thin closed slits loaded by an array of Helmholtz
resonators. The resonators slow the sound inside the slit and pull its
resonance into the deep-subwavelength regime, and the visco-thermal
losses of the sub-millimetre slit and of the square resonator necks and
cavities make perfect absorption possible.
slit_helmholtz_absorber predicts the oblique-incidence absorption of the
panel, returning a SlitResonatorAbsorberResult with the surface
impedance, reflection factor, absorption, retrieved effective parameters
and the full chain matrix, plus a .plot() of alpha(f) with |R| overlaid.
critical_coupling_design solves the inverse problem: it tunes the cavity
length and the slit height so the reflection zero sits on the real
frequency axis, giving perfect absorption at a chosen frequency and
angle. The building blocks slit_effective_properties,
rectangular_duct_properties and helmholtz_resonator_impedance and the
HelmholtzResonator geometry are exported alongside.
The model is pinned to its exact analytic anchors: perfect absorption at
the design frequency, the Poiseuille resistivity limits 12 eta / h^2
(slit) and 28.454 eta / w^2 (square duct), and the loss-free
effective-parameter limits (rho -> rho0, kappa -> kappa0). Adds a
conformance section, an example figure, tests, an API-reference row set,
and EN/ES guide sections.
* Keep the Sonar gate clean for the slow-sound absorber
Exempt materials.slow_sound_absorber from S107 the same way the other
scientific APIs are: its functions carry the slit and resonator geometry
alongside the shared keyword-only air state, matching the porous-absorber
and JCA signatures, so the parameter count reflects the physics rather
than incidental complexity.
Extract a shared alpha(f) + |R| overlay renderer for the layered-absorber
and slit-resonator plots so the two oblique-incidence absorber renderers
no longer duplicate their body. Output is byte-identical.
* Make the slow-sound absorber files Ruff 0.16 clean
Use dict literals for the shared air-state mappings and drop the
redundant quoted type annotations (the module already imports
annotations from __future__). Keeps the new feature files clean under
the widened Ruff 0.16 default rule set without touching unrelated files.
* Modernize the slow-sound absorber additions for ruff 0.16 and regenerate references
* Silence the unused figure handle in the slow-sound graph helper
* Clarify slow-sound docstrings and cover the optional-correction branches
Fix the trailing space in a docstring code span, state the design warning is
emitted rather than raised, correct the deep-subwavelength depth ratio in the
figure docstring, and add a test exercising the end_correction and
slit_radiation False branches.
* Regenerate API reference and llms for the slow-sound docstring fixes
* Regenerate conformance and references after rebase
* Translate the slow-sound figure strings and require convergence in the check
Add the Spanish figure-string translations for the slow-sound absorber plot
(legend, design annotation, title and the panel-depth pattern) so the ES
variant is not left in English, and make the critical-coupling conformance
check fail unless the design solver converged.
* Regenerate conformance and references after rebase
* Use a real polar response for the ISO 17497-2 diffusion oracle
Replace the hand-made four-level diffusion anchor with a real free-field
polar response: a boundary-element (COMSOL) prediction of an N = 7
quadratic-residue diffuser and its flat reference panel at 1000 Hz, normal
incidence, reduced to the standard 37-receiver single-plane semicircle
(5 deg spacing, -90 to +90 deg) in the plane of maximum diffusion. The 37
arc points are coplanar to 3.4e-3 of unit radius, so the equal-area
Formula (5) applies exactly; levels are L_i = 20 lg |p_i|.
- reference_data: commit the 37 QRD and flat-reference arc levels and the
directional diffusion coefficients d_theta = 0.7572 (QRD) and 0.1391
(flat) and the normalised d_theta,n = 0.7180 (Formula (7)), with full
provenance. For context the full dense hemisphere gives 0.5007/0.1487/
0.4135 (energy) and reproduces the source's own value under the amplitude
convention.
- conformance: three real-data rows (Formula (5) twice, Formula (7)),
reproduced to 1e-6; regenerate docs/CONFORMANCE.md (412/412 pass).
- tests: clean-room checks that re-derive Formula (5) from the levels
independently of the library kernel.
- Surface Scattering guide (English and Spanish, plus the GitHub mirror)
and the theory anchor now use the cited real QRD example; regenerate the
polar figure (d = 0.76) and llms-full.txt.
* Regenerate conformance and references after rebase
* Correct the diffusion oracle provenance to an in-house COMSOL FEM simulation
The single-plane QRD field is the maintainer's own COMSOL finite-element
free-field simulation cross-validated against an independent MATLAB
implementation of ISO 17497-2 Formula (5), not a boundary-element method and
not a third-party published measurement; drop the unverifiable thesis
citation and the mislabelled method across the guide, its mirror and the
oracle provenance.
* Make the diffusion-oracle provenance consistent in the test module
Match the reference-data provenance: in-house COMSOL finite-element simulation
cross-validated against an independent MATLAB implementation, not a
boundary-element 2018 thesis and not a measurement; rename the tests to
_matches_simulation_oracle.
* Add diffuser-design far-field prediction of the diffusion coefficient
Predict the far-field polar response and the ISO 17497-2 directional
diffusion coefficient of a Schroeder phase-grating diffuser from its
surface design, complementing the measurement-only scattering_diffusion
module. The new materials.diffuser_design module implements the
single-plane Fraunhofer (Fourier) far-field model of Cox and D'Antonio:
each rigid-bottom well of depth d_n contributes a pressure reflection
coefficient R_n = exp(-2 j k d_n) and the scattered pressure at each
reflection angle is the sum over the wells of the periodic surface, whose
predicted levels feed the existing directional_diffusion_coefficient.
- predict_diffuser_polar_response(): per-frequency polar response reduced
to the directional diffusion coefficient, from a well-depth sequence or
an explicit complex per-well reflection sequence.
- predicted_diffusion_spectrum(): predicted d(f), normalised band by band
against the same-footprint flat reference (Formula (7)).
- quadratic_residue_sequence() and qrd_well_depths() for QRD geometry
(s_n = n^2 mod N; d_n = s_n lambda_0 / (2 N)).
- DiffuserPolarResponse result with the per-angle levels, the coefficient
and a semicircular polar .plot() (English and Spanish).
Validated by the exact-by-physics anchors: a flat panel collapses to a
single specular lobe and normalises to zero, while a well-designed QRD
scatters far more evenly and sits well above it. Adds conformance rows,
the surface-scattering guide section (EN and ES) with a predicted-diffusion
figure, tests and the regenerated API reference, taxonomy and llms output.
* Modernize the diffuser-design additions for ruff 0.16 and regenerate references
* Hoist array construction out of the diffuser-design raise assertions
* Regenerate conformance and references after rebase
* Anchor ISO 16251-1 floor-covering improvement on a real measurement
Replace the illustrative improvement spectrum with a real textile-carpet
measurement digitized from the vector chart of Foret, Chene and
Guigou-Carter (Forum Acusticum 2011, CSTB), which rates to the paper's
published DeltaLw = 29 dB. Add a conformance row and a clean-room test that
assert the rating and its robustness to the digitization tolerance, refresh
the laboratory-insulation guide figure and worked example to the measured
carpet, and cite the source (mirrored in Spanish).
* Clarify the digitized draft-standard provenance and pin the tolerance test endpoints
* Regenerate the floor-covering example report and references
The Materials and Surfaces theory page derives surface diffusion and porous
absorber prediction, yet its references frontmatter listed only Cox & D'Antonio
and Allard & Atalla. Add three open-access works by Jiménez and co-workers that
the page already speaks to: the metadiffuser paper (Jiménez, Cox, Romero-García
and Groby, 2017, Scientific Reports) on the surface-diffusion section, and the
two critical-coupling perfect-absorption papers (Jiménez, Groby, Pagneux and
Romero-García, 2017, Applied Sciences; Jiménez, Romero-García and Groby, 2018,
Acta Acustica) on the material-characterisation section.
Reference the same works from the scattering_diffusion and porous_absorber
module docstrings, and regenerate the committed API reference.
Add the edited volume Acoustic Waves in Periodic Structures, Metamaterials, and
Porous Media (Jiménez, Umnova and Groby, Eds., 2021, Springer) to the
bibliography page as a metamaterials companion to Cox & D'Antonio.
Mirror the frontmatter and bibliography changes in the Spanish pages with
translated notes, keeping authors, titles and DOIs identical.
ruff 0.16 broadens the default rule set (pyupgrade, isort, comprehensions and
more). Bump the dev pin to ruff>=0.16.0 and apply the resulting fixes across
the tree: PEP 585 built-in generic annotations, unquoted annotations under
from-future annotations, sorted imports and the assorted small lint fixes. A
re-export shim that relied on redundant import aliases now declares __all__
explicitly. The api reference is regenerated for the modernized signatures.
No runtime behavior changes; the full test suite passes.
The second half of the Vibration theory page derives point mobilities and
radiation efficiency from Cremer, Heckl and Petersson (Structure-borne sound,
Eq. 5.23 and Table 5.1) and Hopkins (Sound insulation, Eqs 2.227-2.230), and
ties the results to ISO 7626-1 mobility measurement and the ISO 7849 radiation
factor, yet the references frontmatter only listed Griffin and Mansfield. Add
the missing bibliography entries for those works so the rendered References
section matches what the prose actually cites.
Add the two table-of-contents anchors the theory index was missing for this
page: the point-mobility and radiation-efficiency section and the multiple-
shock subsection.
Mirror both changes into the Spanish pages with translated notes and labels.
* Add ISO 9053-1 static airflow-resistance report via .report()
Add a one-page PDF material airflow-resistance test report to
StaticAirflowResult, laid out like an accredited ISO 9053-1:2018
static/direct airflow-method certificate.
The fiche carries a standard-basis line, an optional metadata header
(client, manufacturer, specimen, specimen thickness, test facility,
date, climate), a two-panel body with a metrics table (the evaluation
velocity, the fitted pressure difference, the airflow resistance R, the
specific airflow resistance R_s, the airflow resistivity sigma when a
thickness is available, and the through-origin fit coefficients a and b)
beside the fitted pressure-drop curve, and a boxed specific airflow
resistance R_s with R and sigma alongside, all read at the clause 7.5
reference velocity of 0.5 mm/s. ISO 9053-1 is a material
characterisation, so the fiche carries no pass/fail verdict.
The renderer reuses the shared report layout and metadata container, and
renders in English and Spanish. A worked example is registered in the
report generator and its rendered fiche committed alongside the others.
* Document the plot dependency and use a language-neutral precision note
The static airflow-resistance fiche embeds the fitted curve, so state that
rendering needs both reportlab and matplotlib (phonometry[report,plot]) in the
English and Spanish guides and the materials doc. Describe the evaluation-velocity
precision as one decimal place rather than with a locale-specific separator, and
regenerate the API page.
* Extract the shared material-test fiche scaffold
The dynamic-stiffness (EN 29052-1 / ISO 9052-1) and static airflow-resistance
(ISO 9053-1) fiches share the same one-page shape: title and basis line,
metadata identity grid, a metrics table beside the result's self-scaling plot,
a boxed single-number result with extended terms, and the footer. Move that
common scaffold into a single _material_fiche helper (the content dataclass, the
identity-grid builder, the standard-basis-line helper and the renderer) and have
both fiches supply only their own labels, metric rows and boxed statement. The
rendered output is unchanged.
* Add element-normalized intensity insulation report via .report()
Add IntensityElementNormalizedResult.report(): a one-page PDF fiche for
the element-normalized level difference DI,n,e of a small building element
measured with sound intensity (ISO 15186-1:2000, Clause 3.9, Formula (8)).
DI,n,e is a level difference rated by the ISO 717-1 airborne machinery, the
same as the sibling intensity sound reduction index RI, so the fiche is
driven through the shared insulation skeleton (render_insulation_fiche) with
the shared iso717_columns_builder. It lives in the existing iso15186.py
renderer beside the RI fiche, so no second near-identical renderer is added.
The sheet carries the standard-basis line, an optional metadata header, the
per-band table (16 one-third-octave or 5 octave bands) beside the
measured-versus-shifted-reference curve, the boxed rating DI,n,e,w (C; Ctr)
and the intensity-method statement. verbose=True shows the ISO 717 evaluation
per band; a metadata requirement adds a PASS/FAIL verdict (the element
insulation passes at or above the target); language="es" renders the Spanish
fiche.
The example is anchored to the ISO 717-1:2020 Annex C worked-example curve
read as a documented DI,n,e spectrum, pinning the rating to the published
30 (-2; -3) dB through the intensity path without a new numeric oracle. It is
registered in the report generator with its committed PDF and WebP preview,
and the guides and API reference are updated.
* Share the intensity-report request validation helper
Extract the engine, language, rating-presence and band-count guards the two
ISO 15186-1 intensity report methods share into a single
_validate_intensity_report helper, mirroring the flanking-transmission module.
It returns the validated non-None rating so each report method hands it
straight to the renderer, removing the duplicated guard block.
* Regenerate API reference after rebase
Keep the guides expanded on the desktop column, fold them on the phone drawer,
and scroll the highlighted row into view when a reader lands from a search
engine and nothing was restored.
The decisive measurement is not the height of the tree, it is that Starlight
restores the sidebar scroll position on desktop and skips it below the
breakpoint on purpose, so a 3534 px tree costs the desktop reader once per
session and a 3140 px drawer costs the phone reader on every hamburger tap.
Folding the drawer turns that into 762 to 984 px, all eleven domains on the
first screen, and the reader's own page visible with no scrolling.
The write-up carries the inventory and geometry, the cold-landing numbers, the
research with sources, the analysis of every candidate for both form factors,
the resolution of the group label that would otherwise navigate and toggle at
once, where the decision meets the API sidebar work, and the file-by-file
change list.
Twenty-three shots in toc-ux-shots/: the status quo, the responsive treatment
and the collapsed treatment, at 1440x900 and 390x844, in both themes and both
languages, plus the tapped and keyboard-focused states of the fold control.
Desktop shots are clipped to the sidebar column, which is the subject; phone
shots are the whole viewport, because there the tree is a drawer over the page.
The pa11y config for the drawer opens the hamburger before testing, which the
default run never does, so the folded tree and its controls are audited in the
state a phone reader actually sees.
The fold control keeps a 40px square target on the drawer, past the 24px floor
of WCAG 2.2 SC 2.5.8 and small enough that the eleven closed groups still fit
one screen, and it restates the two eyebrow-header rules that the row wrapper
would otherwise stop matching.
The tree ships exactly as it does today and the browser adds the controls, so
the server-rendered markup is unchanged and a reader with no JavaScript keeps
the fully expanded tree. Treatment comes from data-guides-nav on <html>:
expanded (status quo), responsive (expanded on desktop, folded on the mobile
drawer) and collapsed (folded on both). The branch holding the current page is
always open, and on desktop the tree scrolls the highlighted row into view when
nothing was restored, which is the case for a reader arriving from a search
engine.
Two rules hold whatever the treatment. The group label stays a link to the
section landing page and the fold lives on a sibling button with aria-expanded
and aria-controls, and a folded list is hidden with hidden="until-found"
rather than display:none so find-in-page still matches inside it.
The companion script reports what each treatment gives the reader, reading the
keyboard and accessibility numbers from checkVisibility() and the accessibility
snapshot rather than from bounding boxes.
The reader who arrives from a search engine has no session storage, so
Starlight restores nothing and the tree opens at the top. The harness now
records, for that case, where the highlighted row sits, how far the tree has
to be scrolled before it comes into view, and whether the top-level group that
contains the page carries any mark of its own.
Drives headless Chrome against a dev server and reports the real geometry of
the navigation tree: group and item counts, the rendered height of the tree at
desktop and inside the mobile menu, how much of it is above the fold, and how
far the last group sits. The candidate heights (all groups closed, only the
current group open, only the current branch open) are measured on the real
tree with a throwaway stylesheet rather than estimated.
INTEGRATION-NOTES.md now carries the cap, the chip landing, the theme toggle
and the desktop control I deliberately left alone, plus every gate result and
the two traps worth knowing: pa11y cannot use its own default port while the
dev server is up, and the managed dev server needed a --force restart before
it would see a frontmatter edit.
The header was lifting Starlight's theme select out of the hamburger, so the
mobile control was still a menu: open it, read three options, pick one. It is
a button now, and one tap flips light and dark.
Auto stays out of the cycle and stays implicit. With nothing stored the site
follows the system preference and keeps following it live, and the first tap
is what turns an inferred theme into a stored choice. Anyone who wants to
hand the decision back to the system still has Starlight's own select, with
its explicit Auto option, in the mobile menu.
No theme state is forked. The button sets the value on Starlight's select and
fires its change event, so the site lands in exactly the state it would have
been in had the reader picked that option from the menu: updatePickers, the
data-theme attribute and the starlight-theme key are all written by
Starlight's own handler. The button then reads its state back from data-theme,
which is the output of that same path, so it cannot disagree with the desktop
control or with the before-paint script.
The icon is the mode the tap would give you, not the mode you are in, and the
accessible name says the same in words ("Switch to dark theme"), updated from
the same attribute. The desktop select keeps showing the current mode
instead; the two are never on screen together, so I left it alone.
Driven in headless Chrome from both system preferences: initial state follows
the system, a live system flip is followed while nothing is stored, Enter on
the focused button flips it, a tap flips it back, a reload keeps it, a system
flip after a choice is ignored, and clearing storage returns to following the
system. Both Starlight selects agree with the button after every step.
Three things on the header chips.
The "+N more" chip pointed at the References heading, which had no landing
offset of its own, while the entries had one. Starlight's scroll-padding
clears the sticky chrome for both, so neither was hidden, but the heading came
to rest hard against the sticky bar while an entry came to rest a comfortable
way below it, and on a phone, where the mobile table of contents is part of
that chrome, the difference is the whole gap. The offset is now one custom
property in one rule covering the entries, the heading and its wrapper, so
the two targets cannot drift apart again. Both land at 168 px on a 1440 px
desktop and 200 px on a 390 px phone, EN and ES, with and without the
language bar.
The cap is nine chips for the run as a whole, with a tenth slot for the
"+N more" link, replacing five standards plus three works. The budget is not
split per category any more, with one guard: the named works hold a
reservation of up to three slots when the page has any, the standards take
the rest, and whatever the standards do not use goes back to the works. Nine
standards on a standards-only page, nine papers on a paper-only page, and a
page with fourteen standards and three papers shows six and three rather than
nine and none. The `primary: true` flag still orders the run first: flagging a
work that was past the old cap on the underwater page brings it to the front
of its run and into the visible nine.
Worth knowing: nine chips no longer fit the one line the run was designed
around. The densest page is four rows at 1440 px and six at 390 px. It reads
as a block under the H1 rather than a dateline, which is the trade the higher
cap buys.
scripts/check-page-chips.mjs is the new gate: the cap on seven pages in both
locales, and the landing of an entry chip against the more chip at four
viewport sizes, both themes, both locales and with the language bar up.
INTEGRATION-NOTES.md records what came from each branch, the gitignore rule
that kept the chip helper untracked, the colour retuning with its numbers,
the two language-bar defects and what is still open.
The chip branch's notes carried the old colours and the old ratios in prose;
they now point at the palette and at check-contrast.mjs, which is where the
numbers are produced rather than transcribed.
The bar is fixed across the whole bottom edge, but it was mounted in the
Footer override, which renders inside Starlight's `.main-pane`. That pane
sets `isolation: isolate`, so the bar got its own stacking context and
painted under the sidebar pane no matter what its z-index said: on a desktop
layout the sentence was cut mid-word by the sidebar and the dismiss button
was cut by the table of contents. Mounting it in a PageFrame override makes
it a sibling of the page frame, in the root stacking context, where its
z-index means what it reads. The frame renders on every route, splash
included, so nothing loses the bar.
The floating treatment switcher was landing on the dismiss button as well,
the two branches having each pinned something to that corner. The bar now
publishes its height on <html> while it is on screen and the switcher steps
over it.
check-lang-suggest.mjs gets the scenario that would have caught this: hit
test the bar over the sidebar, over the content and over the toc, plus the
dismiss button itself. The markup was always right; only the paint order
was wrong, so only a hit test can see it.
The chips were tuned against the old Starlight default palette, and against
the instrument palette they were doing two things I do not want. The
standards teal sat at OKLCH hue 181 with the accent at 216 to 225, close
enough to read as a near-miss of the accent rather than a category of its
own, and both chip hues carried more chroma than the accent itself (0.103
and 0.141 against 0.056 in the dark theme), so the header run out-weighed
the one colour this palette lets mean something.
So the standards chip now takes the palette accent itself, which is honest:
the standards are what the page is about and the chip is a link. The named
works take its warm mirror, same OKLCH lightness and chroma at hue 74 and 70,
which keeps the two categories far apart without introducing a second loud
colour. The tokens live in theme.css with the rest of the palette and the
chip stylesheet only references them.
check-contrast.mjs now audits the nine chip pairs per theme: text on the
page and on the hover fill at 4.5:1, and the pill border, which is what
carries the category, at 3:1. Everything clears, the tightest being the
"+N more" border at 3.04:1 dark and the standards border at 3.42:1 light.
Also add scripts/integration-shots.mjs, the capture matrix for this branch.
The notes described a matrix that no longer exists. They now describe
the single design: the instrument palette with its measured contrast
table, the front page, the mobile theme control and the language bar
with its kill switch. The two discarded palettes and the two discarded
language modes are recorded as history, with the commit they can be
read back from (1113e19c) and the exact paths.
New section on the clause-number defect and its fix, with what was
measured before (20 of 26 cases overlapping, in both locales) and the
three axes it failed on, since the enlarged-text one is the least
obvious and the one that reached the desktop.
The screenshots are regenerated for one palette: the ones showing the
discarded directions and the prototype switcher are gone, and four new
crops document the heading defect and the fix at 390 px in Spanish and
at 1440 px with a 24 px default text size.
Gates, all green on this branch: build 441 pages with every internal
link valid, html-validate clean, pa11y-ci 46/46 at WCAG2AA, i18n
parity 100/100, contrast 25 pairs with none below threshold, language
handling 9 scenarios, landing headings 40 layout cases. The two
browser-driven checks were run against the built site under astro
preview as well as against the dev server.
I have decided both questions this branch was carrying, so the code
should carry one answer and not three. The palette is "instrument",
cool steel greys with a deep cyan indicator, and the language handling
is the banner: it offers the other language and nothing ever navigates
on its own.
Gone with them: the floating prototype switcher, the blueprint and
graphite palettes, the `data-accent` attribute and the before-paint
script in Head.astro that stamped it, the `phonometry:accent` and
`phonometry:lang-mode` storage keys, the redirect and off language
modes with their per-session guard, and the bar-height custom property
that only existed so the switcher could sit clear of the bar.
src/styles/theme-directions.css becomes src/styles/theme.css, with the
winning direction promoted to the plain :root and
:root[data-theme='light'] rules.
The language feature keeps a kill switch, but as a build-time constant
rather than a control: ENABLED in LangSuggest.astro renders no markup
and no script when it is false. Nothing on the page and nothing in
storage can flip it.
Everything else about the behaviour is unchanged and still checked: an
explicit choice always wins, the picker and a dismissal both record
one, crawlers are skipped and the English-only API subtree renders no
bar at all. check-contrast.mjs now resolves the two themes of the one
palette (25 pairs, 0 below threshold) and check-lang-suggest.mjs runs
the nine scenarios that survive without modes.
The number beside each landing block was an absolutely positioned
::before, and the gutter it needs was reserved by a padding that only
existed inside a `min-width: 62rem` media query. Everywhere else the
number was painted straight onto the first letter of the title: the
teal "01" sat on the Q of "Qué es" at every width below 992px, at
200 % and 400 % browser zoom, and at any width at all once the reader
enlarges the default text size, because a rem media query resolves
against that size and 62rem stops matching a 1440px window as soon as
the default goes past 23px. I measured it at 20 of the 26 cases I
checked, in both locales.
The number is now a real box in the layout instead of an overlay. Each
block is a wrapping flex line of two items, the number and the block
body, so the space is reserved by the layout itself at every width:
when the line has room the number sits in its gutter beside the
heading, and when it does not the body wraps and the number ends up on
its own line above the title. There is no threshold left to get wrong,
and no state in which the two can occupy the same pixels.
scripts/check-home-headings.mjs proves it: 40 cases, both locales, 320
to 1920 px, 200 % and 400 % zoom, and default text sizes of 24 and 32
px. It asserts that no clause number overlaps any text of its block,
that no step number overlaps its step heading, that the list markers
fit the indent their item reserves, and that the document never
scrolls sideways.
I had assumed the dark matplotlib figures were transparent, so the page
ground would show through them. They are not. Their figure patch is emitted
as a <path> with no fill attribute, and SVG paints an unfilled path black, so
404 of the 506 dark figures are opaque black plates and the other 102 are the
diagrams' #0d1117 plates. Sampling the rendered page confirms it: inside a
dark figure the pixels are #000000, not the page colour.
That changes what the dark ground is for. It cannot help the ink, which sits
on the figure's own black; all it can do is stop the plate edge from reading.
So the three dark grounds move down into the band between #000000 and
#0d1117:
instrument #12181c -> #0d1114 plate seams 1.11:1 and 1.00:1
blueprint #0f1421 -> #0b0f1a 1.10:1 and 1.01:1
graphite #17150f -> #100e0a 1.09:1 and 1.02:1
against 1.22:1 and 1.10:1 for the current theme, so all three roughly halve
the visible edge and make the diagram plate disappear outright. Nav, sidebar
and card surfaces follow the ground down so the separation between them is
unchanged. Body text improves to 11.3-11.6:1; 75 pairs measured, 0 enforced
pair below AA.
The contrast script now measures both plate seams instead of ink that never
lands on the page, and the notes carry the corrected analysis, including the
observation that saving the figures with a transparent background would
remove the constraint altogether at the cost of regenerating all 1220 files.
The pill without fill is the chosen style, so it is the only one left: the
plain text run and the filled pill are deleted with their CSS, the
data-page-chips attribute is gone and the chips render whenever a page has
references. The switcher keeps only the API treatment and is labelled for it.
The pill outline was the one thing I had left below its accessibility target.
It now clears 3:1 against the page background in both themes, measured on the
composited colour: 50 % alpha dark for both categories, 80 and 90 % light,
where solid amber on white only reaches 3.99:1 to begin with. Nothing else
about the chip changed, so the approved size, padding, radius and spacing are
untouched.
Which sources survive the caps is no longer frontmatter order alone: a
reference marked primary: true is promoted within its category run, with
frontmatter order as the tiebreak, so a page with a long bibliography can
advertise the sources it leans on. The field is optional and documented in
the schema.
The ICAO Annex 16 entry on the aircraft-noise page carried an edition string
where its document number belongs, so that page derived no chip for the
standard it implements. Both language versions now name the document.
Front page. The landing was the README feature list as ten cards of 60 to 90
words: it stated everything and showed nothing, and its numbers were an
earlier release's (371 checks, 235 standards, against the 427 over 278 the
conformance report now produces). It is now five blocks that each answer one
question: the numbers with no adjectives, what this is with who it is for and
an explicit list of what it is not, one worked example beside the figure it
draws and one rating beside the PDF fiche it renders, the coverage of nine
areas as a table of standards, and a three-step path from nothing to a first
analysis. Text lives in src/data/home.ts, one object per locale sharing one
shape, so English and Spanish cannot drift apart; every number in it is
sourced from the repository and the file records where.
The splash column widens to 62rem, the sidebar gutter Starlight reserves is
zeroed so the page is centred, and the hero grid collapses to one column
since there is no hero image.
Mobile header. Below the md breakpoint Starlight hides the theme control
behind the hamburger. Header.astro now surfaces it next to the language
picker, in the desktop order, as an icon-and-caret stub. Starlight's own
updatePickers() already walks every theme select on the page, so the mobile
stub and the desktop control stay in sync with no extra code.
Language detection. LangSuggest.astro offers the other language on a first
visit, in banner mode by default, with redirect and off available from the
prototype switcher. An explicit locale in the URL wins, the language picker
records the choice, and once anything is stored nothing ever navigates on its
own again, so a deliberately opened English URL is never hijacked.
Redirection is guarded by a stored choice, a session flag and a target check,
so it cannot loop; crawlers are skipped by user agent; the English-only API
subtree opts out entirely; and none of it touches canonical or hreflang.
scripts/check-lang-suggest.mjs drives eleven scenarios in isolated browser
contexts: 0 failing.
Build with link check, html-validate, pa11y at WCAG2AA (46/46) and the EN/ES
parity gate all pass.
The sidebar keeps the tree and nothing else. The annotation experiment is
deleted rather than switched off: the data-toc-style dimension and its five
presentations, the annotation line, the hover card and its keyboard handling,
the page subtitle and the section landing tables all go, and with them the
attrs['data-chips'] classification in the sidebar config and in the API
generator, plus the shortened sidebar labels the chips had justified.
SidebarSublist.astro and Sidebar.astro are back to the smallest diff against
upstream Starlight that still gives me non-collapsible groups, the
overview-first convention where a group label links to its landing page, and
the fold toggle the API treatments need. Each file header now records what
differs from upstream and why, so a future Starlight upgrade is cheap to
reconcile.
Everything about what governs a page now lives in the page header run, which
takes its data from the frontmatter bibliography. That run gains two more
presentations to compare against the quiet text version: outlined pills, so
the chips look like the links they are, and the same pills with a faint
category tint. Both keep the eight chips of the densest page on one line and
clear 5.9:1 on their text in either theme.
The site rides Starlight's defaults today: hue 224 greys and a fully
saturated blue-violet accent that sits badly next to a library whose output
is calibration numbers, and that reads as part of the plot whenever a figure
puts a blue trace on the page.
Three complete palettes now live in src/styles/theme-directions.css, each
covering light and dark, selected with a data-accent attribute on <html>
that an inline script in Head.astro stamps before first paint and
AccentSwitcher.astro writes to localStorage:
instrument cool steel greys, deep cyan (default, also the plain :root
block, so the site is correct with JavaScript disabled)
blueprint drafting blue-greys, prussian ink, navy dark ground
graphite warm neutral greys, amber
The palettes are chosen around the committed figures, which do not change.
404 of the 506 dark figures are transparent matplotlib plots drawing in
white plus tab10, so every dark ground stays dark enough for #1f77b4 to
clear 3:1 (3.71, 3.81, 3.79). The other 102 are diagrams with an opaque
#0d1117 ground, so the ground is kept close to it: the plate seam goes from
1.10:1 today to 1.06, 1.03 and 1.04. Every light figure carries an opaque
white ground, which is why all three keep the light page white and tint the
surfaces instead.
scripts/check-contrast.mjs parses the stylesheet and measures all six
palettes: 87 pairs, 0 enforced pair below WCAG AA. scripts/redesign-shots.mjs
drives its own headless Chrome for a reproducible screenshot matrix.
The same information the sidebar experiment has been trying to fit into the
tree, placed under the H1 where there is horizontal room and no navigation to
pollute: one quiet line naming the standards that govern the page, then the
works its methods are attributed to.
It is derived from each page's own typed `references` block rather than from
a second hand-maintained list, so the header and the References section at the
bottom cannot disagree, and every chip links to its full entry there, which
now carries a stable id and highlights when jumped to. Standard designations
collapse to their family (ISO 10140-2:2010 becomes ISO 10140) and books and
articles become author and year; five standards and three works fit on one
line, anything beyond that folds into a single "+N more" link. Pages without
references render nothing.
It rides on its own `data-page-chips` attribute so it can be compared against
any sidebar variant, and it supersedes the hand-derived subtitle of the clean
variant, which now yields whenever the run is on.
The annotation line reused the sidebar link colour, so metadata carried
exactly the weight of the navigation labels; it now sits one step quieter in
both themes and still clears WCAG AA at 11px. The dotted marker of the hover
variant was invisible on the dark sidebar, so each theme gets its own value,
and the card border no longer disappears against the white light-theme
surface.
On a phone the three-column standards table pushed its last column off screen
and wrapped page names to four lines, so below 30rem it stacks one page per
block. The table also gained the tabindex the markdown tables get, since it is
a scrollable region.
Replaces the earlier ad-hoc captures with the ux-variants2 set.
The dotted marker no longer sits on the resting tree: almost every page here
is governed by something, so an always-on underline marked nearly every row
and read as texture rather than information. It now fades in at hairline
weight only while the pointer or the keyboard focus is inside the sidebar.
The floating card is anchored to the link's trailing edge, so it can no
longer push the sidebar into horizontal scroll at any nesting depth, Escape
dismisses it without moving the focus (WCAG 2.2 1.4.13), and on touch devices
it degrades to the always-visible annotation line because there is no hover
there. The topic back link pointed at a page that does not exist.
Every sidebar item already knows which standards it implements and which
named references its method is attributed to. That classification rides along
as `attrs['data-chips']` JSON, built by the `chips()` helpers in the sidebar
config and by the API generator, and is rendered as a run of badges inside
each link.
This adds five presentations of that run behind a `data-toc-style` attribute
on `<html>`: the annotation line for the current section only, the same line
on the whole tree, a floating card on hover or keyboard focus, a page level
subtitle plus a table on section landings, and nothing at all. It also adds
three treatments of the API reference tree behind `data-api-style`: expanded
in place, folded behind a caret, and a topic style split with a back link.
A floating switcher applies and persists the choice before first paint, so I
can compare all of them on the running site without a rebuild.
Prototype only. See TOC-REDESIGN-NOTES.md.
* Normalize Spanish docs prose to UNE acoustic terminology
Sweep the Spanish pages for the non-UNE forms "presion sonora",
"potencia sonora" and "intensidad sonora" and normalize the prose to
the wording used by the official Spanish editions of the standards:
"presion acustica" (UNE-EN ISO 16283-2:2016, UNE-EN ISO 10052:2005,
UNE-EN ISO 3746:2011, UNE-ISO 226:2013), "potencia acustica"
(UNE-EN ISO 3746:2011, UNE-EN ISO 4871:2010, UNE-EN ISO 9614-1:2010),
"intensidad acustica" (UNE-EN 61043:1999, UNE-EN ISO 9614-1:2010) and
"nivel de presion acustica de impactos" (UNE-EN ISO 12354-2:2018,
UNE-EN ISO 717-2:2013). 92 replacements across 32 pages, prose only:
code snippets keep the labels they share with the generated figures.
Rename the intensity guide to "Intensidad acustica (p-p)" and the
structure-borne power guide to "Potencia acustica estructural de
equipos (EN 15657)", updating every cross-link text and the theory
anchor intensidad-sonora-iec-61043 accordingly. The API sidebar group
label becomes "Potencia acustica e intensidad" via api_taxonomy.py
with the generated sidebar regenerated.
* Use decimal commas in Spanish docs prose quantities
Sweep every Spanish page for decimal points leaking into prose numbers
and switch them to the comma the locale convention calls for: the
rotorcraft validation prose (fourteen tolerances such as 0,08 dB(A) and
0,5 s), the insulation-prediction worked example (53,6 dB / 53,8 dB),
the tone-audibility Annex E tone (137,3 Hz), the Mackenzie check value
(1550,744 m/s), the EPNL, loudness and absorption fiche descriptions
(98,3 EPNdB; 8,2 sonos and 70,4 fonios; 0,60 y 0,55), the junction
constants table and the third-octave band-edge table in the signal
analysis theory page. Code snippets, inline code, math, clause numbers
and quoted source text keep their points.
* Pass language="es" in Spanish figure-reproduction snippets
The <details> blocks under each localized figure in the Spanish guides
are meant to reproduce the _es variant shown on the page, but 82 of the
one-line result .plot() calls omitted the language="es" argument and
would render English axis labels instead. Add the argument across 33
guides, matching the pattern the intensity field-indicators snippet
already used. The three lab-insulation results whose plot() does not
take a language argument (lab airborne, lab impact and the
element-normalized intensity difference) are left as they were.
* Extend the UNE terminology sweep to sound absorption
Normalize "absorción sonora" to "absorción acústica" across the
Spanish pages (36 occurrences in 14 pages, including the EN 12354-6
guide title and the theory headings), the wording of UNE-EN ISO
3746:2011 and of the equivalent absorption area in UNE-EN ISO
10140-3:2011, UNE-EN ISO 12354-2:2018 and UNE-EN ISO 16283-2:2016, and
the same term the accredited report strings already use. Also restore
the accent in the theory index anchor to the renamed intensity heading,
since heading slugs keep accented characters.
* Theory pages: illustrate each section family with existing result figures
The seven theory pages carried no figures at all. Embed sixteen of the
existing generated figures (light and dark variants, Spanish variants on
the Spanish mirrors) where the concept matches the prose: weighting
curves, time integration and the p-p intensity estimator in the signal
analysis page; equal-loudness contours, the specific-loudness pattern
and the hearing-threshold panels in perception; NC/RC rating, the
Schroeder decay and the ISO 717-1 shifted reference in rooms and
buildings; the Lden profile, ISO 9613-1 absorption and the three sound
power routes in environment and transport; the scattering coefficient
and ISO 11654 rating in materials and surfaces; and the Wk weighting and
the resonator mobility triplet in vibration. Each embed reuses the alt
text of the guide that owns the figure and adds a one-line caption.
Also give the wave-simulation section hub the reading-order paragraph
its siblings already have, linking the closed-form pages the FDTD solver
cross-checks, in both languages.
* Address review findings from the Spanish locale sweeps
Second pass fixes flagged by review: five terminology occurrences the
line-based sweep missed because the noun and adjective were split by a
line wrap or emphasis markup (sound-power, rotorcraft-noise and two
section hubs); six Spanish pages that still embedded the English figure
variant although the _es variant exists (junction-transmission,
aircraft-noise airport contour, miso-coherence,
objective-intelligibility, psychoacoustic-annoyance twice); and 19 more
language="es" arguments on figure-reproduction plot calls the first
pass missed, chained-call receivers such as room.noise_criterion(spl)
.plot() and the axes-creating first call of the two-profile
atmospheric-refraction snippet among them. The lab-insulation plot
wrappers forward keyword arguments to the rating plot, so the three
calls excluded earlier now take the argument too. All Spanish python
fences re-checked to compile and EN/ES parity re-verified.
* Carry the UNE terminology into the generated-asset Spanish strings
Apply the same evidence-based decisions to every Spanish string source
that feeds generated assets: the translation tables of
generate_graphs.py and generate_diagrams.py, the _STRINGS tables of the
plot modules and the report i18n values. 35 strings change: "presión
acústica" (UNE-EN ISO 16283-2:2016, 10052:2005, 3746:2011),
"potencia acústica" (UNE-EN ISO 3746:2011, 4871:2010, 9614-1:2010),
"intensidad acústica" (UNE-EN 61043:1999, 9614-1:2010), "absorción
acústica" (UNE-EN ISO 3746:2011) and "nivel de presión acústica de
impactos" (UNE-EN ISO 12354-2:2018). Strings the UNE editions keep are
untouched: "emisión sonora", "exposición sonora", "campo sonoro",
"fuente sonora". Only Spanish values change; the English keys stay.
The two plot-i18n test assertions that pin the axis label follow, and
the eight Spanish doc fences that deliberately mirrored the old figure
labels now mirror the new ones.
* Regenerate the Spanish figure variants with the UNE terminology
make graphs after the string-source change: 48 files, all of them _es
or _es_dark variants (24 figure/diagram pairs), picking up "presión
acústica", "potencia acústica", "intensidad acústica", "absorción
acústica" and "nivel de presión acústica de impactos" in titles and
axis labels. The English variants are untouched. The committed example
fiches need no regeneration: every example in generate_reports.py
renders in English, so no changed Spanish report string reaches them.
* Align the intensity-fiche Spanish assertions with the UNE terminology
* Record that ISO 10848-1:2017 corrects the 2006 critical-frequency misprint
The second edition prints the pi-free thin-plate critical frequency in its
Formula (5), confirming the spurious pi of the 2006 Formula (20) as a
misprint. The errata entry now records the upstream correction and the
docstring cites the 2017 formula alongside the existing evidence.
* Clarify the errata status sentence for the corrected misprint
* Add ResampledSignalResult.plot(): the delivered anti-alias filter against its design spec
* Rewrite the multichannel guide intro: concrete use cases, per-channel guarantees, Bendat & Piersol citations
* Give the levels page a real introduction: descriptor families, when-to-use guidance and survey references
* Time weighting: framing intro with detector history, ANSI S1.4/Bies references and sibling links
* Calibration: proper introduction (SPL vs dBFS choice, workflow framing) with Bies reference and sibling links
* Add eleven core-metrology figures: resampling spec, cepstrum variants, liftering, correlation, IR alignment, Hilbert envelope, cross-spectra, Golay recovery, TSA noise law, runs test
* Test signals: anti-alias figure with dual snippet, res.plot() mention, See also, docs mirror sync
* Cepstrum guide: variants and liftering figures with dual snippets, complex-cepstrum plot mention
* Correlation guide: normalization, IR-alignment and Hilbert-envelope figures with dual snippets
* Spectral analysis: cross-spectral density and coherent-output-SNR figures with dual snippets
* System measurement: Golay exact-recovery figure and plot mentions for the inversion workflow
* Synchronous averaging: sqrt(N) figure, diagnostics-tool guidance, See also, recording naming
* Data qualification: runs-test figure with dual snippet and peak-statistics plot mention
* MISO coherence: conditioning-order snippet, practical pitfalls and See also across the three trees
* Time-frequency: Harris window reference and See also across the three trees
* GUM uncertainty: map the domain pages that consume the clause-5 machinery, expand See also
* Filter banks: compliance plot mentions and recording naming in the crossover snippet
* Changelog entry for the core-metrology documentation figures and prose
* Regenerate llms-full.txt for the core-metrology documentation
* Drop the duplicated phase-axis translation key
* Add ModulationDistortionResult and per-band FieldIndicators .plot() with ISO 9614-1 figure
* Electroacoustics: modulation-sideband and piston-impedance figures, docs mirror gains IEC 60268-4/-5 report sections
* Noise control: side-branch, HVAC end-reflection and enclosure figures across guide, docs and Spanish mirrors
* Swept-sine distortion: phase-decomposition figure and Farina-method plot mention, docs mirror synced
* Programme loudness: gating and loudness-range figures with plot mentions across guide, docs and Spanish mirrors
* Sound power: report and declaration sections ported to the docs mirror, plot mentions and Spanish decimal fixes
* Regenerate API docs and llms exports, changelog entry for the emission and electroacoustics figures
* Add atmospheric-refraction and barrier insertion-loss documentation figures
Four new deterministic figures in the four language/theme variants:
the log-linear effective sound-speed profiles (downward vs upward
refraction), the downward-refraction ray fan, the GFPE relative level
versus range at the receiver height for three atmospheres, and the
Kurze-Anderson / exact half-plane / coherent-ground barrier
insertion-loss comparison on the guide geometry.
* Environment and underwater guides: figures embedded, model-selection prose, mirror sync
Embed the new figures with reproducible code blocks in the
atmospheric-refraction and ground-and-barriers pages (EN, ES and the
docs mirrors) and add the missing .plot() one-liners next to every
plottable snippet, including the outdoor-propagation usage and report
examples.
Prose: a when-does-refraction-matter introduction for the
atmospheric-refraction guide (Attenborough & Van Renterghem 2021,
Ch. 11); a Choosing-a-model section for the underwater-propagation
guide covering the validity ranges of the UNESCO / Del Grosso /
Mackenzie sound-speed equations and the Francois-Garrison /
Ainslie-McColm / Thorp absorption models, spreading-law guidance, the
ray / normal-mode / parabolic-equation selection table after Jensen
et al. (2011, Ch. 1) and a worked passive-sonar budget; and, for the
underwater-acoustics guide, the ISO 17208-1 measurement discipline
(CPA distance, +/-30 degree data window, four runs, averaging chain)
and the ISO 18406 regulatory context (scope, the 750 m reference
position) behind the closed forms.
Mirror sync: the outdoor-propagation docs mirror gains the missing
Prediction reports section, the ground-barriers and
atmospheric-refraction docs mirrors gain the figure code blocks, and
the ES mirrors are updated with equivalent text (decimal commas fixed
in three stray values). ISO 18405 added to the underwater-propagation
references; llms-full.txt regenerated.
* Fix the porous-absorbers link in the ground-barriers docs mirror and a stray comment label
* Regenerate llms-full.txt after the docs link fix
* Address review notes: shadow boundary in PE snippets, solver wording, Fresnel decades, barrier legend
The atmospheric_pe_range figure snippets now draw the closed-form
shadow-zone boundary they show; the underwater solver table describes
normal_modes as a finite-difference modal sum and the PE as marching
the range-independent profile all three solvers assume; the barrier
comparison claim is corrected to two decades of Fresnel number; and
the 5 dB grazing line is relabelled as the Kurze-Anderson limit in the
figure (regenerated, all four variants), the snippets and the alt
texts. llms-full.txt regenerated.
* Perception, hearing and speech guides: result figures and when-to-use prose
Add ToneAssessment.plot(), which draws an ECMA-418-1 tone against the
prominence criterion of its own method (recovered from criterion_db) over the
whole range of interest, with the deciding margin marked, and a
view="levels" option on ToneAudibilityResult.plot() that exposes the
level-versus-frequency view the ISO 1996-2 fiche already embedded.
Add eleven single-concept figures to the perception, hearing and speech
guides, each a realistic result drawn by its own .plot(): the tone prominence
verdict, the ISO/PAS 20065 tone levels, the ISO/PAS 1996-3 onset detection,
the ISO 532-2 specific loudness, the ECMA-418-2 specific tonality, the
specific fluctuation strength, the SII band audibility under a hearing loss,
the ISO 7029 fractile band, the ISO 1999 NIPTS audiogram, the STOI per-band
correlation and the IEC 60268-16 per-band MTI. Every figure is embedded in
the English guide, the Spanish mirror and the docs mirror with the dual
snippet block, and every plottable snippet on those pages now names its
.plot().
Prose: the tone-prominence guide gains a comparison of the four tonality
assessments the library implements and when each applies, the
hearing-threshold guide explains how the ISO 7029 fractiles feed the ISO 1999
age component and gains a See-also section, and the objective-intelligibility
guide gains the "which measure, and when" section that existed only in the
docs mirror, extended with the STOI/ESTOI, STI and SII choice.
Also label the STOI band axis at whole-hertz resolution and let the shaded
critical band of the tone-audibility levels plot follow the figure
background, so it reads on the white report page and on dark figures alike.
* Correct two figure alt texts against the plotted values
The ISO 7029 fractile figure describes the median and the 90 % fractile with
the values it actually draws (10 to 50 dB and 22 to 74 dB), and the
ECMA-418-2 specific tonality peak is at 9 Bark_HMS.
* Add .plot() to the ISO 717 enlarged-range rating results
ExtendedWeightedRatingResult and ExtendedImpactRatingResult now carry the
full enlarged-range band curve (band_centers, measured) and expose a
.plot() that draws the measured curve over the whole range against the
shifted ISO 717 reference on the 16 core bands, with the unfavourable
deviations shaded, the enlarged-range bands marked and every covered
Annex B / A.2.1 adaptation term in the title. EN/ES labels follow the
existing renderer conventions.
* Field insulation guide: enlarged-range, field-chain, facade and survey-impact figures
Four new deterministic figures (extended_insulation_rating,
field_airborne_insulation, facade_field_insulation,
survey_impact_insulation) generated in the four language/theme variants,
embedded next to their snippets in the EN guide, the ES mirror and the
docs mirror with the dual usage/hand-written code blocks. One-line
.plot() mentions added for the extended rating, the field airborne
result, the survey impact result and the ISO 12999-1 band uncertainty.
The docs mirror gains the previously missing ISO 10052 survey-reports
section, and the survey snippet now defines the li and l1_2m inputs it
uses. Multi-line figure info boxes are translated as whole artists, and
the review fixes (headroom, legend placement, proper unit and Greek
glyphs) are applied to the new generators.
* Laboratory insulation guide: ISO 10140, ISO 15186-1 element and ISO 10848 Dn,f figures
Embed the new lab_insulation_result, intensity_element_insulation and
flanking_level_difference figures next to their snippets in the EN guide,
the ES mirror and the docs mirror, each with the dual usage/hand-written
code blocks. Add the one-line .plot() mentions for the laboratory
airborne result, the element-normalized intensity result and the
floor-covering improvement result, and a note on when Dn,f plots (the
inline example uses 9 bands, below the 16/5-band rating threshold).
* EN 12354 prediction guide: impact-term and radiated-power figures
Embed the new impact_prediction_terms and radiated_power_outdoor figures
next to their snippets in the EN guide, the ES mirror and the docs
mirror, with the dual usage/hand-written code blocks, and add the
one-line .plot() mentions for the airborne prediction, the impact
prediction, the facade prediction and the radiated-power result.
* Panel insulation guide: rated Sharp prediction figure and plot mentions
Embed the new single_panel_rating figure (the 6 mm glass Sharp prediction
rated per ISO 717-1, coincidence dip marked) in the EN guide, the ES
mirror and the docs mirror with the dual code blocks, and add the
one-line .plot() mentions for the single-panel, double-wall and
radiation-efficiency results next to the existing concept figure.
* Junction transmission guide: Kij design chart, EN 12354 worked example and see-also
Add the junction_kij_thickness figure (wave-approach Kij versus the plate
thickness ratio for the X, T and L junctions, identical-plates value
marked) next to the coupling-loss-factor section, a res.plot() mention
in its snippet, a worked example that feeds the predicted K12 = 9.8 dB
of the 100/200 mm concrete X-junction into the EN 12354-1 flanking model
(R'w = 55.4 dB, direct path dominant), and a See-also section linking
the prediction, laboratory, structure-borne power and mobility guides.
EN guide, ES mirror and docs mirror in sync.
* Junction transmission docs mirror: sync the Kij chart, worked example and see-also
* Structure-borne power and installed-source guides: plot mentions, Hopkins citation and see-also
Add the one-line .plot() mentions for the reception-plate power result
(EN 15657) and the installed-source prediction (EN 12354-5) next to their
existing figures, add the one-line .plot() usage to both figures' code
blocks, cite Hopkins (2007) Section 2.8 for the point-excited-plate power
input behind the clause 4.4.3 coupling term with a prose paragraph
connecting the mobility chain, and give the EN 12354-5 page a proper
See-also linking the EN 15657, ISO 7626, junction and EN 12354 guides.
EN guides, ES mirrors and docs mirrors in sync.
* Vibration FRF guides: plot mentions and transfer-stiffness docs sync
Add the one-line .plot() usage and mentions for the ISO/TS 7849
sound-power-from-vibration result, the EN 29052-1 dynamic-stiffness
result and the ISO 10846 indirect transfer-stiffness result next to
their existing figures (EN guides, ES mirrors and docs mirrors), and
port the previously missing ISO 10846 test-report fiche section to the
transfer-stiffness docs mirror.
* Mechanical mobility guide: driving-point reading figure
Embed the new mobility_result_lines figure (|Y(f)| of the SDOF bundle
with the stiffness line, the mass line and the 1/c damping peak) next to
the MobilityResult snippet in the EN guide, the ES mirror and the docs
mirror, with the dual usage/hand-written code blocks and the one-line
res.plot() mention.
* Human vibration and multiple-shock guides: one-line plot mentions
Add the res.plot() one-liners next to the existing figures for the
ISO 8041-1 weighting response, the ISO 2631-1 weighted band spectrum, the
Directive 2002/44/EC daily exposure A(8) and the ISO 2631-5 multiple-shock
assessment, in the EN guides, the ES mirrors and the docs mirrors.
* Docs mirrors: restore missing report-fiche and concept sections
Sync the docs mirrors of the pages touched by this wave with their site
guides: the ISO 16283-3 facade report subsection on the field insulation
page, the EN 29052-1 and ISO 7626 fiche example snippets, and the panel
concept-figure code block.
* Changelog, API docs and llms regeneration; lint and typing fixes for the figure generators
* Fix nested emphasis in the survey-impact figure captions
* Restore the numerical-propagation rasters truncated by a mid-regeneration snapshot
* Reconcile the figure-script additions with the rooms and materials wave
* docs: regenerate the llms bundle after the figure-script reconciliation
* Rooms & materials: TransferMatrix.plot() and nine result figures in the EN guides
* Rooms & materials: sync the docs mirrors (figures, plot mentions, report sections, diffuser-prediction port)
* Rooms & materials: ES guide mirrors, changelog and generated api-docs/llms
* Docs mirrors: ISO 10534-2 fiche section, slow-sound and reflectogram figure snippets
* Fix vertical line collisions in fiche prose with sub/superscripts
reportlab shifts a <sub>/<super> fragment by half the font size but
spaces paragraph lines by the style's fixed leading alone: its
autoLeading modes compute line bounds from per-fragment font sizes and
ignore the script rise entirely, so a wrapped fiche line ending in deep
subscripts (e.g. the EN 12354-5 L_n,s,ij formula strip) overprinted the
ascenders and superscripts of the next line.
Clearing the default half-em shift with fixed leading alone would need
1.65x-em line spacing on every prose style, growing each block by about
a third and breaking the dense one-page fiche layout. Instead, every
fiche paragraph is now built through a shared _layout.fiche_paragraph
factory (imported by the renderers as Paragraph) that rewrites the bare
<sub>/<sup>/<super> openers to the typographic quarter-em rise; the
worst-case line extent (deepest subscript directly above the tallest
superscript) is then 1.24x the font size, inside the fixed leading of
every fiche prose style, and the scripts sit closer to their base text
as accredited lab reports typeset them.
* Regenerate the example fiches with collision-safe sub/superscripts
* Report the true 1/fs time axis from time_synchronous_average
The averaged samples sit on the 1/fs sampling grid (each block is aligned
back to it by the band-limited fractional delay before averaging), but the
result reported a time axis with T/M spacing. For a non-integer number of
samples per period the two grids drift apart across the period: with
100.37 samples per period the noiseless recovery error read against the
reported axis was 9.3e-2, versus 2.8e-6 on the true m/fs grid.
Return times = arange(M)/fs, document that the M samples cover the period
only approximately when fs*T is not an integer (McFadden 1987 revised
model: a block average on the sampling grid, no angular resampling), and
assert the non-integer-period tests on the true grid, with a regression
test pinning the 100.37-sample case on both grids. Also soften the comb
response [0, 1] claim (float cancellation beside a tooth can exceed 1 by
a few 1e-9) and mark the ~14 dB N = 32 figure as derived from Eq. 8
rather than printed in the paper.
* Detect inverting echoes and off-sample delays in echo_detection
echo_detection picked the maximum of the signed power cepstrum, so an
inverting reflection (a < 0), whose first rahmonic is a itself and hence
negative, was silently missed: with a = -0.4 at 313 samples the true
rahmonic c[313] = -0.42 was ignored and an unrelated 4739-sample peak
returned. Peak-pick on |cepstrum| within the searched band instead and
report the signed cepstrum value at the peak as the reflection
coefficient (the Mercator series ln(1 + a e^{-j theta}) holds for either
sign of a).
Also refine the reported delay by three-point parabolic interpolation of
|cepstrum| through the peak, so an off-sample delay (e.g. 313.5 samples)
is estimated to a fraction of a sample, and document the bin-splitting
caveat: at off-sample delays the peak-sample coefficient underestimates
|a| (about 65 % of it midway between bins). One-line convention note
distinguishing Milner's IDFT(ln|X|^2) power cepstrum from Bogert's
squared original. Tests cover a < 0 (exact impulse-echo closed form and
a noise source) and the interpolated half-sample delay.
* Validate parametric-EQ sections at the numerical extremes
Three validation gaps in the RBJ cookbook designer:
- A wide 'bw' at an f0 near Nyquist drives the w0/sin(w0) warping factor
into math.sinh overflow, escaping as a raw OverflowError; slightly less
extreme pairs produced a large but finite alpha whose rounded a2 landed
exactly at -1.0: poles on the unit circle designed silently. Wrap the
sinh with an informative ValueError and check the realized coefficients
against the stability triangle (|a2| < 1, |a1| < 1 + a2) after
normalization.
- A shelf slope beyond the gain-dependent bound (A + 1/A)/(A + 1/A - 2)
makes the cookbook's alpha complex and surfaced as a bare 'math domain
error'. Validate the bound at section construction with the limit in
the message (any slope is fine at 0 dB, where A = 1).
- Non-finite f0/gain_db/q/bw/slope passed validation silently (NaN
compares false against every bound) and produced NaN/inf coefficient
rows. Require finiteness explicitly.
Tests cover all three paths plus extreme-but-valid sections that must
keep designing stably.
* Scope the envelope-spectrum amplitude claim and add the band-pass front end
The docstring promised that a sinusoidal modulation 'reads out as a line
at its exact amplitude'; that holds only when the modulation frequency
falls on an analysis bin. Off-bin lines read low by the taper's
scalloping loss (about 1.42 dB, ~15 %, for the default Hann midway
between bins), like any single-record amplitude spectrum. State the
on-bin condition on the closed forms and the caveat in the module,
function and result docstrings, pinned by a midway-between-bins test.
Also add the optional band=(low, high) pre-filter to envelope_spectrum:
a zero-phase 4th-order Butterworth band-pass ahead of the detector, the
band-pass front end of Bendat & Piersol Figure 13.11 and the classical
bearing-envelope chain (isolate the excited resonance band, then
envelope it). The band travels with the result; a test shows it
recovering the exact modulation line under a strong out-of-band
interferer that swamps the raw envelope.
* Warn when a tone burst cannot close on the tone's zero crossing
IEC 60268-1 Clause A2.1 asks for an integral number of full periods, but
the gate closes after round(fs*cycles/frequency) samples: sample-exact
only when f and fs are commensurate. For 10 cycles of 997 Hz at 48 kHz
the burst spans 481.44 samples, gated at 481, and the waveform carries a
residual step at the gate edge. Document the condition and emit a
PhonometryWarning quantifying the realized residual (bounded by
amplitude*sin(pi*f/fs)) for incommensurate pairs; commensurate bursts
stay silent.
Also tighten the resampler's 'equiripple-bounded' wording: the Kaiser
window method bounds both bands by the same delta = 10^(-A/20), but its
ripple decays away from the band edges, unlike a true equiripple design.
* Document estimator conventions verified against their sources
Documentation and comment corrections, no behavior changes:
- random_data: keep the reverse-arrangement acceptance bounds on the
normal approximation deliberately and say why: B&P Table A.6 itself
follows it, and no percentage-point convention on the exact Mahonian
distribution reproduces the published entries (N = 40 and N = 100 at
a = 0.975 pin it down), so exact bounds would disagree with the table
users check against. The p-value stays exact; the one-count boundary
disagreement is now documented on TrendTestResult.
- time_frequency: the spectrogram energy-recovery statement was wrong as
written; integrating 'density' columns over frequency and summing over
time recovers the record energy only after multiplying by the hop
duration, up to the taper roll-off at the record edges (verified
0.988 vs 1 for Hann at 75 % overlap on white noise).
- spectra: note that the adaptive multitaper sigma^2 is mean(x^2) with no
mean removal (consistent with the documented no-detrending
calibration), and mark the 'P&W Eq. 369a' citation as pending
verification against the book.
- compliance + tests/reference_data: transcription note for the bare
'+3' at 20 Hz, Type 2, in ANSI S1.4-1983 Table V (read as upper-only;
+/-3 plausible; either way the verdict cannot change, the realized
deviation is -0.05 dB).
- room_ir: the Mueller & Massarani 'below 4 dB' crest-factor figure
assumes their slight magnitude smoothing, which shaped_sweep_signal
does not implement; measured sweeps here sit at 4.2-4.3 dB.
* Translate EQ section labels, complete the bibliography and changelog
- The per-section legend labels of the parametric-EQ plot printed the raw
English filter-type names in Spanish figures; route them through the
plot string table (peaking -> campana, shelves, band-pass, notch,
all-pass; lowpass/highpass were already translated for the lifter
plot). English output stays byte-identical.
- docs/references.md: add the Harris 1978 window paper and IEC
60268-1:1985, both cited by guide pages but missing from the collected
bibliography; link the IEC entry (and the one in test-signals.md) to
the IEC webstore page.
- Changelog entries for the DSP fixes; regenerate the API reference,
llms-full.txt and the cepstrum_echo figures (the detected-echo marker
moved by the sub-sample delay interpolation); the conformance report is
unchanged (419/419).
* Propagate the echo and envelope semantics to the site guides
Review follow-ups:
- The EN and ES site guides (cepstrum-echoes.mdx) still described the
old echo reading and the unqualified 'exact amplitude' claim; bring
them in step with docs/cepstrum-echoes.md: |cepstrum| peak-picking,
signed coefficient, interpolated delay, bin-splitting caveat, the
on-bin/scalloping condition and the new band= front end (the docs page
gains the same envelope-section text).
- State in the changelog and the result docstring that delay is no
longer exactly delay_samples/fs when the interpolated offset is
nonzero (delay_samples remains the whole-sample index the coefficient
is read at), and merge the duplicated Unreleased headings.
- Clearer stability message (poles 'do not lie strictly inside the unit
circle', full-precision coefficients) and an informative short-record
error for the envelope band-pass pre-filter instead of scipy's padlen
message.
- Sharper tone-burst warning test (asserts the quantified span and
residual) and a tighter interpolated-delay tolerance.
* Address review notes: cepstrum terminology, band-pass claims, edge validation
- Guides (docs and site, EN and ES): call the signed quantity what it is,
Milner's cepstrum of the log-power spectrum, and spell out that
Bogert's original 1963 power cepstrum squares once more and is
non-negative, so the signed rahmonics are the library's documented
convention; qualify the band-pass front end (out-of-band content is
strongly attenuated, not eliminated: finite Butterworth rejection and
small zero-phase edge transients).
- cepstrum: render reflection_coefficient as inline code in the
delay_samples docstring so the generated API page stops linking the
unrelated underwater seabed function.
- envelope: validate that 'band' is exactly two numeric edges before
indexing (a one-item tuple raised IndexError, extra entries were
ignored); the validated edges now travel to the result.
- equalizer: reject finite but unrepresentable gains through a shared
_gain_amplitude helper (10^(gain_db/40) overflowing, or underflowing
to zero and then dividing by zero in the designers); document the 0 dB
exception to the shelf-slope bound on the slope field.
- tone_burst: validate the repetition configuration before emitting the
incommensurate-frequency warning, so invalid setups raise even under
a warnings-as-errors filter.
- comb_filter_response: reject phase products that overflow to infinity
(sin(inf) returned NaN instead of the documented bounded response).
Tests for every new rejection path; API reference and llms regenerated.
* Test the representable gain amplitude as a positive finite bound
* Scope the first-rahmonic height claim to the source-free identity
* Correct the ISO 15186-1 Formula (8) N-term and complete the fiche's Clause 8 content
The printed Formula (8) subtracts its 10 lg(N) term, which cannot be
derived: ISO 10140-2:2010 Formula (6) adds the multi-unit correction and
ISO 15186-2:2010 Formula (12) prints the same relation without an N term.
intensity_element_normalized_difference() now returns the derivable
per-unit value (+10 lg N), warns whenever n > 1 deviates from the print,
and the defect is registered in docs/ERRATA.md.
Clause 8 completeness of the intensity fiches: the method statement now
states the test-object and measurement-surface areas S / Sm carried by
the result (Clause 8 g), and report() accepts optional per-band FpI and
pressure-residual intensity index spectra annexed as table columns
(Clause 8 i). The 6.4.2 qualification wording is corrected to 'must not
exceed 10 dB' (the clause flags FpI > 10 dB), the band-set captions now
cite ISO 717-1:2020 Clause 5.3 (statement of results) instead of 4.4,
and the Table B.1 Kc oracle gains the three printed rows 3150/4000/5000
Hz (0,1 dB each) so all 21 rows are pinned.
* Label the road-traffic facade fiche R'tr,s instead of R'45
FacadeInsulationResult now carries the measurement method
(loudspeaker or road_traffic), and the ISO 16283-3 r_prime fiche
selects its labels from it: the road-traffic apparent index is the
distinct quantity R'tr,s (Clause 3.13, -3 dB all-angle correction),
so its report no longer prints the R'45 loudspeaker basis line
(Clause 3.12) over road-traffic numbers.
* Make the ISO 10848 fiche part selectable and register the Formula (20) misprint
The Dn,f / Ln,f quantities are defined in the ISO 10848-1:2006 frame
document (Formulas (4)/(5)); which part governs a measurement depends on
the construction under test, so hardcoding 'ISO 10848-2:2006' in the
basis line mislabelled Part 3 and Part 4 measurements. report() now
accepts part=2/3/4 (default 2) and names ISO 10848-2:2006,
ISO 10848-3:2006 or ISO 10848-4:2010 accordingly, in both languages.
ISO 10848-1:2006 Clause 8.1.1 Formula (20) prints the thin-plate
critical frequency with a spurious extra pi in the denominator (the 1,8
constant already carries 2 pi / sqrt(12)); the implementation was
already pi-free, and the defect is now registered in docs/ERRATA.md
with a misprint note in the critical_frequency docstring, corroborated
by ISO 12354-1:2017 and Hopkins Eq. 2.201.
* Normalize the K24 mass-ratio convention and correct the K24 errata entry
EN 12354-1:2000 prints its two K24 lines in two different mass-ratio
conventions without saying so: the E.5 line follows the annex's own
per-path definition M = lg(m'perp,i/m'i), while the Figure E.9 (E.7)
line is stated in the figure-axis variable lg(m2/m1). Read with the
declared per-path M, the E.9 print contradicts its own curve;
ISO 12354-1:2017 E.3.5 prints the same relation consistently in the
per-path convention (+14,1 M). Re-deriving both editions against the
figures shows the earlier errata entry misread this recast as a 2017
sign misprint; the entry is rewritten accordingly.
junction_vibration_reduction() now applies the documented per-path
mass_ratio = m'perp,i/m'i to every branch: the E.7 double-leaf branch
takes leaf-over-homogeneous ratios (validity m2/m1 > 3 reads
mass_ratio < 1/3) and evaluates 3,0 + 14,1 M + 5,7 M^2, numerically
identical to the 2000 figure. Also cites Kij,min as Formula (29)
(Clause 4.4.2) and documents the ISO 12354-1:2017 both-negative
lining-combination rule next to the 2000 form combine_linings()
implements.
* Broadcast a single-number source level across the EN 12354-5 path bands
installed_source_prediction() derived its band count from the source
levels alone, so a single-number characteristic power with per-band
path data either raised a spurious band-count error or produced a
one-element installed_power_level that crashed the fiche table with an
IndexError. The band count is now the widest per-band input, every
per-band input is validated against it (one value broadcasts), and the
result's installed_power_level is broadcast to the band axis; a fiche
regression test covers the scalar-source case.
Also corrects the coupling-term docstrings: D_C is not guaranteed
non-negative, since near a mounting resonance with comparable,
opposite-phase source and receiver mobilities the |Ys + Yi|^2 numerator
collapses (counterexample in the docstring).
* Use a prediction-aware footer disclaimer on the EN 12354 fiches
The shared footer printed 'The results relate only to the tested
specimen.' on every fiche, including prediction sheets that have no
tested specimen. footer_flow() now takes predicted=True (also exposed
through the shared sound-power renderer) and prints 'Predicted result:
the values relate only to the modelled configuration, not to a tested
specimen.' instead; the EN 12354-1/-2/-3 and EN 12354-5 prediction
fiches use it, in both languages.
Also corrects the dated designations on the prediction basis lines:
the 2000 editions are EN 12354-1/-2/-3:2000 (there was no ISO 12354 in
2000), so 'EN/ISO 12354-x:2000' is replaced by 'EN 12354-x:2000'.
* Declare the actual band set on the ISO 10052 basis line and align ES impact terms
The survey fiches accept both the octave and the one-third-octave band
sets, but their standard-basis lines hardcoded '(survey method, octave
bands)'; the line now follows the band set the reported curve actually
carries, in both languages (ISO 717-1:2020 Clause 5.3 / ISO 717-2:2020
Clause 4.4 require stating it). The remaining comments citing the
band-set statement as ISO 717-1 Clause 4.4 now cite 5.3 (717-2 keeps
4.4), and the ISO 10140 fiche docstring cites the test-report clause as
Clause 9 (Clause 6 is the test arrangement).
Spanish terminology of the field impact quantities is aligned with the
UNE editions: UNE-EN ISO 16283-2:2016 defines 'nivel de presión
acústica estandarizado/normalizado de ruido de impactos' (3.13/3.14),
now used by the ISO 16283-2 and ISO 10052 basis lines; the ISO 10140-3
and ISO 12354-2 strings already match their own UNE editions
(UNE-EN ISO 10140-3:2011 3.2, UNE-EN ISO 12354-2:2018 3.1.1/3.1.2) and
stay unchanged.
* Qualify a background-limited ISO 16251-1 result as a lower bound
When any band sits at the 1,3 dB limit of measurement (Formula (2), the
per-band values already reported as > delta-L), the fiche now prints
the boxed delta-Lw with a >= qualifier and a note stating it is a lower
bound, in both languages. A manually built result without CI,delta
prints the bare delta-Lw instead of fabricating a (+0) adaptation term.
The ISO 717-2:2020 Annex C.2 errata entry is extended with the second,
independent print defect: the example's 'Ln,sum = 75,2527 = 75 dB'
reproduces to all printed digits only as the energy sum of the wrong
column over the wrong range (the measured floor with covering, 16 bands
100-3150 Hz), while the A.2.1 sum of the covered reference floor over
100-2500 Hz rounds to 76 dB with either 800 Hz value, so CI,r = -2,
CI,delta = -9 and delta-Llin = 6 dB; the earlier claim that the 800 Hz
misprint alone shifted the CI chain is corrected.
* Regenerate the conformance report, API docs and affected fiche examples
The Kc Table B.1 conformance row now covers all 21 printed rows, the
building API pages pick up the revised docstrings, and the EN 12354
prediction and ISO 15186-1 example fiches are re-rendered with the
prediction footer, the corrected designations and the Clause 8 areas
statement (kept to one page). CHANGELOG entries under [Unreleased].
* Validate the frequency axis and the facade method on the building results
The installed structure-borne prediction now rejects a frequency axis whose
length differs from the resolved band count, so a result can no longer carry
an inconsistent per-band table. FacadeInsulationResult validates its method
on construction, closing the direct-construction path that would render an
unknown method as the loudspeaker quantity. The band-count note also names
the characteristic level and coupling term as width-setting inputs.
A review pass over the psychoacoustics, hearing, speech, environmental and
noise-control fiches. The numerics held up; what did not were the statements
printed around them, plus one wrong table lookup.
ISO 1999 validated domain. nipts()/htlan() applied Formula (2) wherever they
were asked to: L_EX,8h = 130 dB over 60 years at the 0,99 fractile returned a
357,9 dB threshold shift with nothing marking it as an extrapolation. Since the
formula is quadratic in (L_EX,8h - L0) that number is meaningless, not merely
uncertain. Both now warn (NoiseInducedHearingLossWarning) outside the stated
ranges while still computing: durations outside 1-40 years (clause 6.3.1
validates Formula (2) over 10-40 and Formula (3) over 1-10), fractiles in the
tails clause 6.3.2 says "should not be estimated" (Q below 5 % or above 95 %),
and levels above the 100 dB of Annex D that the Scope's NOTE 4 restricts
validity to. The fiches print the caveat when the conditions sit outside.
ISO 1999 Q. The fiches printed the library's fractile under ISO 1999's symbol,
inverting its meaning: in Formulae (4)/(5) Q is the percentage with worse
hearing, so the most-susceptible tenth is Q = 10 %, not the "Q = 0.9" the fiche
showed while citing 6.3.2. They now print ISO's Q as ISO defines it; the guides
note the difference from the fractile argument. Two further disclosures on the
same fiches: the age component H is evaluated from ISO 7029:2017 (the edition
ISO 1999 references undated in 6.2.2), which departs from the illustrative
Table A.3 selection by up to about 7 dB at the median, and the 2/3/4 kHz
average is the user's own choice, the Scope's NOTE 1 specifying no frequency
combination. The footer states a population scope rather than "the results
relate only to the tested specimen", which describes a laboratory specimen.
ISO 1999 Annex C is wired in as an independent oracle: its worked example (male
population aged 50, 90 dB for 30 years, 1/2/4 kHz at Q = 10 %) reproduces the
Table D.2 shifts it consumes (0, 9, 19 dB), the 13,3 dB left of the 4 kHz shift
after the Formula (1) compression (C.5) and the 31,1 dB combined threshold
(C.11). The composition it exercises is now exposed as combine_age_and_noise(),
which clause 6.2 wants available on its own: 6.2.3 recommends a database B
collected on a control population of the country under consideration, and this
applies Formula (1) to whichever database the user brings.
Verdict precision. The STI and SII fiches printed the requirement with one
decimal beside a measured value at full precision, so a verdict could contradict
itself: "STI = 0.50, required >= 0.5 -> FAIL" against a 0,52 requirement, and an
SII minimum of 0,75 printed as "0.8" (the committed example said so). Both now
print the requirement at the quantity's own precision. IEC 60268-16 Annex M
joins the conformance report as an end-to-end oracle: its printed MTF matrix,
speech spectrum and ambient noise give back its published MTI row and
STI = 0,76, exercising the whole A.5.3 to A.5.8 chain.
Noise-control prediction framing. The enclosure, silencer and HVAC fiches
compute design models but read as measurements: no prediction statement,
measurement headers, a footer scoped to a tested specimen, and committed
examples citing instrumentation that never existed ("Two-microphone
transfer-matrix bench"). They now follow the EN/ISO 12354 prediction exemplar
already in the repo, with a prediction-basis line, a statement naming what each
model does not represent, and a prediction footer. Their verdicts also decided
on the half-away-from-zero display rounding while printing Python's
round-half-to-even, so 0,25 printed 0.2 against a verdict taken on 0,3; both
now use the display rounding.
ISO/PAS 1996-3. With every onset rate at or below 10 dB/s the fiche withheld
the adjustment on the onset-rate gate (clauses 4.5/8) but justified it with "no
prominent impulse is present (governing P = 6.01 <= 5)", contradicting its own
boxed P. The note now states whichever gate governs. The assessment period is
read from the result instead of being hardcoded: 30 min is the Clause 5
*default*, now the default of the new assessment_period_min argument.
Smaller corrections:
- the HVAC elbow insertion loss took the wrong row of Bies Table 8.11 for a
W/lambda exactly on a bin edge; the rows read "a <= W/lambda < b", so an edge
opens its row (1 dB at 0,14, not 0 dB);
- the wind-turbine tonality fiche cited "subclauses 9.5.2-9.5.5" of
IEC 61400-11 for a chain running through 9.5.6 to 9.5.8 (Formulae 31-34);
- the ISO 1996-2 tonal fiche notes that Table J.1 keys on the mean audibility
of the J assessed spectra (ISO/PAS 20065 Clause 5.3.9), which a
single-spectrum assessment stands in for;
- the shared 10-90 % fractile band carried a hardcoded English legend in
Spanish figures, and the Spanish silencer and HVAC fiches printed the device
kind and duct-element label in English.
docs/ERRATA.md records two more source defects found on the way: ISO/PAS
1996-3:2022 Clause 5 swaps the 3.4/3.5 cross-references of the onset rate and
the level difference (its own units contradict the clause numbers printed with
them), and ISO 9613-2:1996 Table 2 prints 4,1 dB/km at 15 C / 80 % / 1 kHz
where ISO 9613-1 gives 4,1511, the neighbouring cells rounding correctly. The
library computes that coefficient from ISO 9613-1 directly, so it is unaffected.
Committed example fiches, the conformance report, the API reference and the
llms files are regenerated.
* Return an infinite bottom loss at intromission zeros without a warning
At an angle of intromission the Rayleigh reflection coefficient vanishes
and BL = -20 lg|R| is legitimately infinite (total transmission into the
sediment). The unguarded log10 emitted a RuntimeWarning when |R| reached
exactly zero; wrap it in np.errstate(divide="ignore") in both
bottom_reflection_loss and seabed_reflection and document the inf value.
Tests cover the closed-form intromission angle for a slow mud bottom
(rho2 = 1300, c2 = 1400: 27.585 deg) and an exact |R| = 0 case, asserting
no warning leaks.
* Skip the phon contour labels when the plotted range excludes 1 kHz
The ISO 226 equal-loudness plot annotates each contour at its 1 kHz
crossing (SPL = phon by definition) with the text placed at 1180 Hz. On a
frequency subset that excludes 1 kHz the labels landed outside the axes;
they are now drawn only when the plotted range includes 1 kHz. The
default full-range figure is unchanged.
* Scope piston directivity plot kwargs to single-ka results
plot_piston_directivity applied user plot kwargs to every curve, so a
single color or label collapsed the multi-ka family styling and its
legend. User kwargs now restyle the curve only when the result holds a
single ka and are ignored for a family; both docstrings state the
behavior. The committed multi-ka figure passes no kwargs and is
unchanged.
* docs: regenerate the API reference pages for the updated piston and seabed docstrings
* Fix slit-radiation sign, generalize Kirchhoff obliquity, record paper errata
The slit-to-free-air radiation correction of the slow-sound absorber was
transcribed with the papers' printed -iw sign, which in the e^{+jwt}
convention of the library acts as a negative mass and raises the slit-panel
resonance (378.6 to 386.8 Hz for the documented base geometry) where an
added radiation mass must lower it (370.8 Hz). The term is now conjugated
like the Stinson duct series of the same model, with the direction and the
shifted peak pinned by a regression test, and both source misprints (the
sign and the 4-vs-64 duct-series constant) are recorded in the errata
registry. The committed slow-sound figures are regenerated; the
critical-coupling design they show self-compensates to alpha = 1, but the
solved cavity length moves from 48.3 mm to 44.7 mm.
The diffuser far-field predictor now uses the symmetric Kirchhoff obliquity
factor (cos theta + cos psi)/2 for oblique sources, which reduces exactly
to the Cox and D'Antonio Eq. (9.32) normal-incidence form at psi = 0, so
all normal-incidence anchors are unchanged.
Also: the EN 29052-1 low-resistivity warning now echoes the clause 8.2
requirement to state the estimated error in the test report, and the
ISO 9053-2 volume-flow helper documents the normative 6.2 form against the
Annex A.2 rms variant.
* Update the porous-absorbers misprint count for the new errata entries
The page's sources now have five recorded misprints (Maa Eq. (5b), two in
Attenborough & Van Renterghem 2e, and the two Jimenez et al. entries); the
English and Spanish guides are kept in step.
The committed 37-receiver polar arc in tests/reference_data.py is now
generated by the library's own Fraunhofer far-field model for a published
geometry: the N = 7 QRD, 6 periods, 3.6 m wide, 0.2 m deep row of
Cox & D'Antonio, Acoustic Absorbers and Diffusers, 3rd ed., Appendix B
(the commercial N = 7 QRD of Hargreaves, Cox, Lam & D'Antonio, JASA 108(4),
1710-1720, 2000, Table I). The arc is an arithmetic oracle for ISO 17497-2
Formulas (5)/(7): d = 0.1099, flat reference d = 0.0049, d_n = 0.1055,
recomputed exactly from the committed rounded levels.
A new external third-party anchor pins the model's one-third-octave
band-averaged normalised diffusion to the published Appendix B 2D BEM values
for that row at normal incidence in the 200-400 Hz bands (agreement within
0.01, asserted at +/-0.015) in the tests and in four new conformance rows.
Across the full published 100-5000 Hz range the model-vs-BEM mean absolute
deviation is about 0.09, documented everywhere as a low-band anchor.
The previous in-house COMSOL simulation arc and its numbers are removed from
the tests, the conformance report, the surface-scattering guide (EN/ES), the
theory pages and the diffusion_polar figure, which now all use the same
published-geometry prediction.
- ISO 3741:2010, 9.1.2/9.1.3: apply the background correction at each
microphone position (K1i, Eq. 14), correct the position levels first
(Eq. 15) and only then energy-average (Eq. 16). Pre-averaged 1D spectra
keep the single-K1 path, documented as an approximation.
- ISO 9614-2:1996, 10.6 b: omit the bands failing criteria 1 and/or 2 from
the A-weighted total, flag them in the new a_weighting_omitted_bands field
and name them in the fiche basis strip (EN/ES); warn when the criteria
inputs are absent and the screening cannot run.
- ISO 9614-3:2002, C.1.6.2: a band satisfying criterion 5 qualifies as a
final result even if FS(2) >= 2.
- ISO 3745:2012: bands above 10 kHz no longer abort the determination; only
the A-weighted total (outside the ISO 3744 Annex E table) is NaN.
- ISO 3744:2010, 8.2.3 / ISO 3746:2010, 8.3.3: K1 = 0 only strictly above
the upper criterion; Equation (16) still applies at exactly 15 dB / 10 dB.
- ISO 4871:1996, 3.16/6.2: verify dual-number declarations against the sum
of the separately rounded declared values (verified_dual), and lay the
dual-number fiche out exactly as Annex B.2 (no derived L_WAd row).
- Cite clause 8.4 for the apparent directivity indices and note that the
4.3.2 validity limit concerns K2A (per-band check kept, conservative).
Hand-derived oracles cover each correction; the ISO 4871 and ISO 9614-2
example fiches, the API reference pages and the llms text are regenerated.
Rate NC spectra by the standard's two-step procedure (5.2.2): the speech
interference level (clause 3.2, the average of the 500/1000/2000/4000 Hz
octave-band levels) selects the NC-(SIL) curve and the spectrum is
designated NC-(SIL) unless an octave band exceeds it, in which case the
tangency method (5.2.3) applies. NCResult gains sil, tangency_rating,
method, out_of_range and a label property; the tangency rating stays
available on every in-family result. Spectra outside the NC-15 to NC-70
family of Table 1 are flagged out of range instead of clamping the
interpolation to fabricated NC-71/NC-14 designations: above the family
the governing band is the maximum exceedance over the NC-70 curve and the
label reads ">NC-70 (band)"; below it, "<NC-15". Fiche, plot and
verdict render the flagged designation in English and Spanish.
On the RC Mark II side, warn when the clause D.4 minimum band set
(31.5 Hz through 4000 Hz) is incomplete, flag ratings outside the
tabulated RC-25 to RC-50 family (Table D.1) with an extrapolation note on
the fiche, and document the combined RH tag as a diagnostic extension
beyond the clause D.3.5 letters (N, R, H, RV; the RV vibration/rattle
classification needs the Table 6 test and is not implemented).
On the ISO 3382-1/-2 fiche, label the mid-frequency descriptor honestly
for one-third-octave data (only the 500 Hz and 1 kHz one-third-octave
bands are averaged, so the octave "500-1000 Hz" claim is dropped and the
bands are named), name the band of a fallback T30/EDT descriptor when the
range does not span both mid bands, and choose the EDT_mid vs EDT label
from EDT's own band coverage instead of T30's. Fiche verdicts (ISO 3382
and S12.2) now compare display-rounded values so the printed numbers can
never contradict the verdict at a tolerance boundary.
Correct two citations in the room-acoustics docstrings: the 5 %
reverberation-time JND is ISO 3382-1:2009 Table A.1 (ISO 3382-2:2008
Table A.1 tabulates the uncertainty constants G and H) and the -20 dB
onset trigger is ISO 3382-1:2009 A.3.4, with a note on the at-edge
sample choice. English ISO 3382-3 strings use point decimals
(STI = 0.50/0.20); the Spanish strings keep the comma.
Docs (EN/ES), API reference, conformance check, changelog and the
affected example fiches are updated accordingly.
Directive 2002/44/EC bases the whole-body daily exposure A(8) on the
highest of the frequency-weighted axis values 1,4*a_wx, 1,4*a_wy, a_wz
(Annex, Part B, point 1), not on the ISO 2631-1 Eq. (10) vector total
a_v; the hand-arm side keeps the Part A vector total a_hv.
- Relabel the whole-body magnitude of the daily-exposure fiche as
a_w,max with a printed note stating the Part B basis (EN/ES); add
vibration.wbv_exposure_basis() returning the dominant-axis value and
document in daily_vibration_exposure which magnitude each kind must
be fed with; state the same basis in the human-vibration guide
(EN/ES) and the measurement-chain diagram.
- Derive the boxed exposure zone of the fiche from the same
displayed-rounded comparisons as the assessment rows and the verdict,
so an A(8) printing exactly at a threshold cannot show a zone one
step below its own Exceeded row.
- ISO 10848 Kij fiche: distinguish an empty single-number mean caused
by every in-range band being bracketed (M < 0,25) from a spectrum
with no bands in the Annex A range (EN/ES).
- Spanish ISO 2631-5 outputs: translate the subject sex in the
injury-probability plot title and the word Formula in the fiche's
clause references.
- Correct the running_rms comment: the linear window divides by the
full window length (zero-padded front), it does not average over the
available samples.
The ruff 0.16 adoption applied the PLC0414 and F401 unsafe fixes to
src/phonometry/_plotting.py and deleted all 82 redundant-alias re-exports,
leaving only the docstring. The module is documented as a silent re-export
of the plot renderers (moved to phonometry._plot) for one deprecation
cycle, so from phonometry._plotting import plot_impulse_response and every
sibling raised ImportError. The sibling shim scripts/fdtd2d.py got the
explicit __all__ treatment in the same commit; this one was missed.
Bring back all 82 re-exports as grouped plain imports per source module
and declare an explicit __all__ so the lint fixers keep them (every target
still exists in phonometry._plot, none dropped). Keep the module out of
_compat._MOVED on purpose: that table maps one old path to one relocated
module and warns on attribute access, while _plotting fans out to thirteen
domain modules and is documented as silent.
Harden tests/test_deprecated_aliases.py so this cannot regress silently:
every pre-move module path must now expose a non-empty public surface, and
a new test pins the frozen 82-name _plotting re-export snapshot, asserting
__all__ matches and each name resolves to a callable without warnings.
Pure structural refactor of the two functions flagged by SonarCloud S3776,
with no numeric change:
- ISO 532-1 _calc_slopes: the two segment kinds of the slope state machine
move to _masked_segment/_flat_segment and the Table A.9 range searches to
_rns_index_of/_next_rns_index.
- Osses 2016 _analyze: the per-frame excitation front-end, generalised
modulation depth and neighbour cross-covariance become _band_envelopes,
_modulation_depth and _neighbour_covariance.
Outputs verified byte-for-byte identical on AM/FM tones, AM noise and
stationary/time-varying loudness cases; the committed conformance report
regenerates unchanged.
* Add slow-sound slit and Helmholtz-resonator perfect absorbers
Introduce materials.slow_sound_absorber, a transfer-matrix model of a
rigid panel of thin closed slits loaded by an array of Helmholtz
resonators. The resonators slow the sound inside the slit and pull its
resonance into the deep-subwavelength regime, and the visco-thermal
losses of the sub-millimetre slit and of the square resonator necks and
cavities make perfect absorption possible.
slit_helmholtz_absorber predicts the oblique-incidence absorption of the
panel, returning a SlitResonatorAbsorberResult with the surface
impedance, reflection factor, absorption, retrieved effective parameters
and the full chain matrix, plus a .plot() of alpha(f) with |R| overlaid.
critical_coupling_design solves the inverse problem: it tunes the cavity
length and the slit height so the reflection zero sits on the real
frequency axis, giving perfect absorption at a chosen frequency and
angle. The building blocks slit_effective_properties,
rectangular_duct_properties and helmholtz_resonator_impedance and the
HelmholtzResonator geometry are exported alongside.
The model is pinned to its exact analytic anchors: perfect absorption at
the design frequency, the Poiseuille resistivity limits 12 eta / h^2
(slit) and 28.454 eta / w^2 (square duct), and the loss-free
effective-parameter limits (rho -> rho0, kappa -> kappa0). Adds a
conformance section, an example figure, tests, an API-reference row set,
and EN/ES guide sections.
* Keep the Sonar gate clean for the slow-sound absorber
Exempt materials.slow_sound_absorber from S107 the same way the other
scientific APIs are: its functions carry the slit and resonator geometry
alongside the shared keyword-only air state, matching the porous-absorber
and JCA signatures, so the parameter count reflects the physics rather
than incidental complexity.
Extract a shared alpha(f) + |R| overlay renderer for the layered-absorber
and slit-resonator plots so the two oblique-incidence absorber renderers
no longer duplicate their body. Output is byte-identical.
* Make the slow-sound absorber files Ruff 0.16 clean
Use dict literals for the shared air-state mappings and drop the
redundant quoted type annotations (the module already imports
annotations from __future__). Keeps the new feature files clean under
the widened Ruff 0.16 default rule set without touching unrelated files.
* Modernize the slow-sound absorber additions for ruff 0.16 and regenerate references
* Silence the unused figure handle in the slow-sound graph helper
* Clarify slow-sound docstrings and cover the optional-correction branches
Fix the trailing space in a docstring code span, state the design warning is
emitted rather than raised, correct the deep-subwavelength depth ratio in the
figure docstring, and add a test exercising the end_correction and
slit_radiation False branches.
* Regenerate API reference and llms for the slow-sound docstring fixes
* Regenerate conformance and references after rebase
* Translate the slow-sound figure strings and require convergence in the check
Add the Spanish figure-string translations for the slow-sound absorber plot
(legend, design annotation, title and the panel-depth pattern) so the ES
variant is not left in English, and make the critical-coupling conformance
check fail unless the design solver converged.
* Regenerate conformance and references after rebase
* Use a real polar response for the ISO 17497-2 diffusion oracle
Replace the hand-made four-level diffusion anchor with a real free-field
polar response: a boundary-element (COMSOL) prediction of an N = 7
quadratic-residue diffuser and its flat reference panel at 1000 Hz, normal
incidence, reduced to the standard 37-receiver single-plane semicircle
(5 deg spacing, -90 to +90 deg) in the plane of maximum diffusion. The 37
arc points are coplanar to 3.4e-3 of unit radius, so the equal-area
Formula (5) applies exactly; levels are L_i = 20 lg |p_i|.
- reference_data: commit the 37 QRD and flat-reference arc levels and the
directional diffusion coefficients d_theta = 0.7572 (QRD) and 0.1391
(flat) and the normalised d_theta,n = 0.7180 (Formula (7)), with full
provenance. For context the full dense hemisphere gives 0.5007/0.1487/
0.4135 (energy) and reproduces the source's own value under the amplitude
convention.
- conformance: three real-data rows (Formula (5) twice, Formula (7)),
reproduced to 1e-6; regenerate docs/CONFORMANCE.md (412/412 pass).
- tests: clean-room checks that re-derive Formula (5) from the levels
independently of the library kernel.
- Surface Scattering guide (English and Spanish, plus the GitHub mirror)
and the theory anchor now use the cited real QRD example; regenerate the
polar figure (d = 0.76) and llms-full.txt.
* Regenerate conformance and references after rebase
* Correct the diffusion oracle provenance to an in-house COMSOL FEM simulation
The single-plane QRD field is the maintainer's own COMSOL finite-element
free-field simulation cross-validated against an independent MATLAB
implementation of ISO 17497-2 Formula (5), not a boundary-element method and
not a third-party published measurement; drop the unverifiable thesis
citation and the mislabelled method across the guide, its mirror and the
oracle provenance.
* Make the diffusion-oracle provenance consistent in the test module
Match the reference-data provenance: in-house COMSOL finite-element simulation
cross-validated against an independent MATLAB implementation, not a
boundary-element 2018 thesis and not a measurement; rename the tests to
_matches_simulation_oracle.
* Add diffuser-design far-field prediction of the diffusion coefficient
Predict the far-field polar response and the ISO 17497-2 directional
diffusion coefficient of a Schroeder phase-grating diffuser from its
surface design, complementing the measurement-only scattering_diffusion
module. The new materials.diffuser_design module implements the
single-plane Fraunhofer (Fourier) far-field model of Cox and D'Antonio:
each rigid-bottom well of depth d_n contributes a pressure reflection
coefficient R_n = exp(-2 j k d_n) and the scattered pressure at each
reflection angle is the sum over the wells of the periodic surface, whose
predicted levels feed the existing directional_diffusion_coefficient.
- predict_diffuser_polar_response(): per-frequency polar response reduced
to the directional diffusion coefficient, from a well-depth sequence or
an explicit complex per-well reflection sequence.
- predicted_diffusion_spectrum(): predicted d(f), normalised band by band
against the same-footprint flat reference (Formula (7)).
- quadratic_residue_sequence() and qrd_well_depths() for QRD geometry
(s_n = n^2 mod N; d_n = s_n lambda_0 / (2 N)).
- DiffuserPolarResponse result with the per-angle levels, the coefficient
and a semicircular polar .plot() (English and Spanish).
Validated by the exact-by-physics anchors: a flat panel collapses to a
single specular lobe and normalises to zero, while a well-designed QRD
scatters far more evenly and sits well above it. Adds conformance rows,
the surface-scattering guide section (EN and ES) with a predicted-diffusion
figure, tests and the regenerated API reference, taxonomy and llms output.
* Modernize the diffuser-design additions for ruff 0.16 and regenerate references
* Hoist array construction out of the diffuser-design raise assertions
* Regenerate conformance and references after rebase
* Anchor ISO 16251-1 floor-covering improvement on a real measurement
Replace the illustrative improvement spectrum with a real textile-carpet
measurement digitized from the vector chart of Foret, Chene and
Guigou-Carter (Forum Acusticum 2011, CSTB), which rates to the paper's
published DeltaLw = 29 dB. Add a conformance row and a clean-room test that
assert the rating and its robustness to the digitization tolerance, refresh
the laboratory-insulation guide figure and worked example to the measured
carpet, and cite the source (mirrored in Spanish).
* Clarify the digitized draft-standard provenance and pin the tolerance test endpoints
* Regenerate the floor-covering example report and references
The Materials and Surfaces theory page derives surface diffusion and porous
absorber prediction, yet its references frontmatter listed only Cox & D'Antonio
and Allard & Atalla. Add three open-access works by Jiménez and co-workers that
the page already speaks to: the metadiffuser paper (Jiménez, Cox, Romero-García
and Groby, 2017, Scientific Reports) on the surface-diffusion section, and the
two critical-coupling perfect-absorption papers (Jiménez, Groby, Pagneux and
Romero-García, 2017, Applied Sciences; Jiménez, Romero-García and Groby, 2018,
Acta Acustica) on the material-characterisation section.
Reference the same works from the scattering_diffusion and porous_absorber
module docstrings, and regenerate the committed API reference.
Add the edited volume Acoustic Waves in Periodic Structures, Metamaterials, and
Porous Media (Jiménez, Umnova and Groby, Eds., 2021, Springer) to the
bibliography page as a metamaterials companion to Cox & D'Antonio.
Mirror the frontmatter and bibliography changes in the Spanish pages with
translated notes, keeping authors, titles and DOIs identical.
ruff 0.16 broadens the default rule set (pyupgrade, isort, comprehensions and
more). Bump the dev pin to ruff>=0.16.0 and apply the resulting fixes across
the tree: PEP 585 built-in generic annotations, unquoted annotations under
from-future annotations, sorted imports and the assorted small lint fixes. A
re-export shim that relied on redundant import aliases now declares __all__
explicitly. The api reference is regenerated for the modernized signatures.
No runtime behavior changes; the full test suite passes.
The second half of the Vibration theory page derives point mobilities and
radiation efficiency from Cremer, Heckl and Petersson (Structure-borne sound,
Eq. 5.23 and Table 5.1) and Hopkins (Sound insulation, Eqs 2.227-2.230), and
ties the results to ISO 7626-1 mobility measurement and the ISO 7849 radiation
factor, yet the references frontmatter only listed Griffin and Mansfield. Add
the missing bibliography entries for those works so the rendered References
section matches what the prose actually cites.
Add the two table-of-contents anchors the theory index was missing for this
page: the point-mobility and radiation-efficiency section and the multiple-
shock subsection.
Mirror both changes into the Spanish pages with translated notes and labels.
* Add ISO 9053-1 static airflow-resistance report via .report()
Add a one-page PDF material airflow-resistance test report to
StaticAirflowResult, laid out like an accredited ISO 9053-1:2018
static/direct airflow-method certificate.
The fiche carries a standard-basis line, an optional metadata header
(client, manufacturer, specimen, specimen thickness, test facility,
date, climate), a two-panel body with a metrics table (the evaluation
velocity, the fitted pressure difference, the airflow resistance R, the
specific airflow resistance R_s, the airflow resistivity sigma when a
thickness is available, and the through-origin fit coefficients a and b)
beside the fitted pressure-drop curve, and a boxed specific airflow
resistance R_s with R and sigma alongside, all read at the clause 7.5
reference velocity of 0.5 mm/s. ISO 9053-1 is a material
characterisation, so the fiche carries no pass/fail verdict.
The renderer reuses the shared report layout and metadata container, and
renders in English and Spanish. A worked example is registered in the
report generator and its rendered fiche committed alongside the others.
* Document the plot dependency and use a language-neutral precision note
The static airflow-resistance fiche embeds the fitted curve, so state that
rendering needs both reportlab and matplotlib (phonometry[report,plot]) in the
English and Spanish guides and the materials doc. Describe the evaluation-velocity
precision as one decimal place rather than with a locale-specific separator, and
regenerate the API page.
* Extract the shared material-test fiche scaffold
The dynamic-stiffness (EN 29052-1 / ISO 9052-1) and static airflow-resistance
(ISO 9053-1) fiches share the same one-page shape: title and basis line,
metadata identity grid, a metrics table beside the result's self-scaling plot,
a boxed single-number result with extended terms, and the footer. Move that
common scaffold into a single _material_fiche helper (the content dataclass, the
identity-grid builder, the standard-basis-line helper and the renderer) and have
both fiches supply only their own labels, metric rows and boxed statement. The
rendered output is unchanged.
* Add element-normalized intensity insulation report via .report()
Add IntensityElementNormalizedResult.report(): a one-page PDF fiche for
the element-normalized level difference DI,n,e of a small building element
measured with sound intensity (ISO 15186-1:2000, Clause 3.9, Formula (8)).
DI,n,e is a level difference rated by the ISO 717-1 airborne machinery, the
same as the sibling intensity sound reduction index RI, so the fiche is
driven through the shared insulation skeleton (render_insulation_fiche) with
the shared iso717_columns_builder. It lives in the existing iso15186.py
renderer beside the RI fiche, so no second near-identical renderer is added.
The sheet carries the standard-basis line, an optional metadata header, the
per-band table (16 one-third-octave or 5 octave bands) beside the
measured-versus-shifted-reference curve, the boxed rating DI,n,e,w (C; Ctr)
and the intensity-method statement. verbose=True shows the ISO 717 evaluation
per band; a metadata requirement adds a PASS/FAIL verdict (the element
insulation passes at or above the target); language="es" renders the Spanish
fiche.
The example is anchored to the ISO 717-1:2020 Annex C worked-example curve
read as a documented DI,n,e spectrum, pinning the rating to the published
30 (-2; -3) dB through the intensity path without a new numeric oracle. It is
registered in the report generator with its committed PDF and WebP preview,
and the guides and API reference are updated.
* Share the intensity-report request validation helper
Extract the engine, language, rating-presence and band-count guards the two
ISO 15186-1 intensity report methods share into a single
_validate_intensity_report helper, mirroring the flanking-transmission module.
It returns the validated non-None rating so each report method hands it
straight to the renderer, removing the duplicated guard block.
* Regenerate API reference after rebase