Skip to main content

Acoustic calculator

Audience: developers & AI agents · Scope: the public acoustic/reverberation calculator · Last reviewed: 2026-07-25

TL;DR — A public tool where a visitor describes a room and gets an acoustic result back. The site owns the form, the validation and the reference data; the calculation itself runs on an external Google Cloud endpoint that the site calls with a Google identity token. The reference data (materials, products, room targets) is not in the database — it is three JSON files uploaded through Custom Settings.

Entry point: index.php?option=com_configbox&view=customview&viewname=bcacousticcalculator — linked from the Resources menu and from the configurator page.

What it is & why it exists

Specifiers want to know whether a space will sound right, and which Beta-Calco acoustic products get it there. Rather than embed acoustics maths in the site, the model is delegated to a separately-deployed service; the site is the front door, the validator, and the owner of the product/material catalogue the model works from.

How it fits together

visitor fills the form (room size, space function, materials, products)


ConfigboxControllerBcacousticcalculator
├─ callWarmup() → wakes the endpoint (cold starts are expected)
└─ getCalculationResponse()


ConfigboxModelBcacousticcalculator
├─ getDataFromRequest() – marshal the form
├─ getValidationMessages() – server-side validation
├─ getAccessToken() – Google ID token for the endpoint
└─ getCalculationResponse() – POST JSON → the Cloud endpoint

getMaterials() / getProducts() / getRoomTargets() ← three uploaded JSON files
FileRole
data/customization/views/bcacousticcalculator/the public page and its form (default.php also holds the YouTube video-guide thumbnail and the agent-only chatbot include)
data/customization/controllers/bcacousticcalculator.phpcallWarmup() and getCalculationResponse() — the two AJAX tasks
data/customization/models/bcacousticcalculator.phpreference-data loading, validation, auth, and the outbound call
data/customization/templates/configuratorpage/complete_page.phpthe per-product ACOUSTIC CALCULATOR button, shown when acoustic_calculator_link is set

Admin settings

All in Custom Settings → Acoustic Calculator.

SettingLabelTypeBlank / off
acoustics_endpoint_urlAcoustic Calculator Endpoint URLstringNo calculation is possible — the call has nowhere to go. It doubles as the token's audience, so it must be the exact endpoint. It is also the chatbot's endpoint — see the warning below.
acoustics_materials_jsonMaterials JSON FilefileNo materials to choose from
acoustics_products_jsonProducts JSON FilefileNo products to choose from
acoustics_room_targets_jsonRoom Targets JSON FilefileNo target curves to compare against

The three JSON files are stored in the private data store under private/custom_media/acoustics_calculator/{materials,products,room_targets}/. Each upload is capped at 50 KB, restricted to .json / application/json, and carries FILENAME_TO_RECORD_ID SAVE_FILENAME NODELETEFILE — so the stored file is named after the settings row (1.json live, 3.json dev) and the form offers Replace file but no delete. Live's products file is already ~40 KB, so the cap is within reach.

Per product, not in this group: acoustic_calculator_link ("Enable Link to Acoustic Calculator", a boolean in the product form's Acoustic Calculator group, stored in #__configbox_external_product_appends) renders the ACOUSTIC CALCULATOR button on that product's configurator page. It links to the plain calculator route — no product is pre-selected. 28 products have it set (counted 2026-07-25 in the DDEV copy of live data).

Credentials are not configured here. The calculator authenticates with the site-wide Google service account (reps_file) — see Google Sheets → credentials. That single key also backs Sheets, Drive, BigQuery and the chatbot.

Control / data flow

  1. The page loads the materials, products and room targets from the uploaded JSON files. This is why updating the catalogue is a file upload, not a data migration.
  2. On submit, the model marshals the request and runs server-side validation (getValidationMessages()) — e.g. "Space function is required". Validation is not delegated to the endpoint.
  3. getAccessToken() builds a Google client from the service-account key, sets the scope to the endpoint URL, calls fetchAccessTokenWithAssertion() and takes the id_token (not the access token) — the standard way to call a private Cloud Run/Functions service, where the token's audience must equal the service URL.
  4. callWarmUp() posts {"warm_up": true} first, then the real payload is POSTed as JSON.
  5. The endpoint's response is returned to the page.

Data model

None on the site side. Nothing is persisted: no request log, no result table. The reference data lives in files, the answer is computed remotely and rendered. A submitted calculation leaves no trace beyond the logs and metrics below.

Deployment runbook (manual steps)

  1. Deploy/obtain the acoustics endpoint and note its exact URL.
  2. Grant the site's Google service account permission to invoke it (for Cloud Run, the Invoker role).
  3. Custom Settings → Acoustic Calculator → set Acoustic Calculator Endpoint URL.
  4. Upload the three JSON files: materials, products, room targets.
  5. Smoke test: open the calculator, submit a room, confirm a result. A first call after idle may be slow — that is the cold start the warm-up exists for.

Turning it off: clear the endpoint URL (the tool then cannot calculate) or unpublish the menu items linking to it — there are two published entries, resources/acoustic-calculator in mainmenu and an SEO alias. Neither removes the per-product ACOUSTIC CALCULATOR buttons, which are driven by acoustic_calculator_link. There is no dedicated on/off switch.

Monitoring

SignalWhere
Log typecustom_acoustic_calc
CloudWatch namespaceAcoustic-Calculator — see monitoring.md

Gotchas & caveats

  • The endpoint URL is also the token audience. Changing the URL without re-issuing the token, or a trailing slash mismatch, produces an auth failure rather than a routing error — misleading if you're debugging.
  • The chatbot posts to the same endpoint, distinguished only by an ai_request: true flag in the body. Changing this setting moves both features, and nothing in the settings UI says so. Re-test the chatbot after any change here.
  • Cold starts are real. The explicit warm-up call exists because the endpoint is serverless; if the warm-up is skipped or fails, the first real call may time out.
  • Reference data updates are file uploads. Nobody edits materials or products in the admin UI — a stale catalogue means a stale JSON file. There is no validation that the JSON matches what the endpoint expects.
  • A missing or unparseable file degrades silently — it does not error. getMaterials() and friends wrap everything in try/catch and rethrow, but with a missing file the chain is file_get_contentsfalsejson_decodenull → array-offset/foreach warnings, not throwables, so an empty array comes back. Verified on DDEV by renaming materials/3.json away and by truncating it: the page still returned HTTP 200 with the full form, and the six surface dropdowns rendered with only Select — no error anywhere on the page or for the visitor. An empty dropdown is the only symptom, and it points at exactly one of the three files.
  • Only a material's first category is honoured. The model sets $item['category'] = $item['categories'][0] and the view matches that single value against Ceilings / Floors / Walls, so a material listed for several surfaces appears in one dropdown only. The live catalogue works around this with duplicate entries (e.g. separate drywall rows for walls and ceilings); the 40 materials split 25 / 10 / 5.
  • show: false in the products file hides a product from the series dropdown without removing it — check that before concluding the file is wrong.
  • Product thumbnails are absolute https://betacalco.com/images/acoustic-calculator/… URLs inside the products file, so dev and staging load them from live, and a new product needs its image uploaded to live's image folder separately.
  • It shares the site-wide Google credential. Rotating reps_file affects this tool along with Sheets, BigQuery and the chatbot.
  • Nothing is stored, so there is no way to reproduce a visitor's reported result after the fact — ask for their inputs.
  • The result panel's "Send Feedback" link is a hard-coded Google Form URL in tmpl/result.php. Visitor feedback lands there, not anywhere in the site — worth knowing when someone asks where the responses go.

Testing

No dedicated spec. The practical check is submitting a calculation on the page after any change to the endpoint, the credential, or the JSON files.

Possible follow-ups

  • Validate the uploaded JSON against a schema at upload time, so a bad file fails at the point of change rather than in front of a visitor.
  • Log submissions (even anonymously) so reported results can be reproduced.

For operators → admin-guide/products-configurator/update-the-acoustic-calculator.md.