CLAUDE.md 24 KB

Church Acoustic Analysis Suite — Project Reference

Project Purpose

A React single-file web application for acoustic analysis of a Plymouth Brethren Christian Church (PBCC) "Universal Hall" — specifically the MS6 ceiling speaker system. The tool models the hall geometry, computes speaker coverage per row, checks Haas window compliance, analyses flutter echo risk, connects to REW (Room EQ Wizard) API for measurements, and sends all data to a local Ollama LLM (or Claude API) for structured acoustic recommendations.


Hall Geometry — Confirmed Measurements

Critical geometry rule — read carefully

The CEILING IS FLAT. It is one horizontal plane at a constant absolute height of 4300mm above the centre floor datum across the entire main seating zone.

The FLOOR IS DISHED (bowl shape). It is lowest at the centre (communion table area) and rises outward toward the perimeter.

The apparent "ceiling height" (floor-to-ceiling clearance) decreases toward the perimeter solely because the floor rises. There is no slope in the ceiling itself over the main seating area.

Plan dimensions

Parameter Value Source
Seating plan 20,914mm × 20,914mm SK-303 + confirmed
Half-width (centre to wall) 10,457mm 20914 / 2
Row 1 front edge (inner radius) 2,210mm from centre Confirmed
Row pitch (FFL to FFL) 883mm Confirmed
Number of main rows 8 Confirmed
Row 8 back face 8,411mm from centre SK-303 drawing
Back aisle width 2,046mm 10457 − 8411 = 2046 ✓
Corner rows per corner 4 rows Confirmed
Corner row pitch 883mm (same as main) Confirmed
Corner row 1 front ~620mm from edge of circular seating Confirmed

Row mid-bench radii (ear position):

Row Front edge (mm) Mid-bench ear (mm)
1 2,210 2,651
2 3,093 3,534
3 3,976 4,418
4 4,859 5,301
5 5,742 6,184
6 6,625 7,067
7 7,508 7,950
8 8,391 8,833 → back face 8,411

Section — ceiling and floor

Flat ceiling (main seating zone):

  • Absolute height: 4300mm above centre floor datum (0mm reference)
  • This is constant across the entire main seating zone
  • Floor-to-ceiling clearance at any radius = 4300 − floorRise(r)

Dished floor:

  • Centre (communion table zone, r ≈ 0–2210mm): 0mm (lowest, flat zone)
  • Rises linearly from row 1 outward to row 8
  • Total dish rise from centre to row 8 back (r=8411mm): 700mm
  • Dish slope: 700mm over 8411mm ≈ 1:12 (approximately 1:8 per SK-303, measured from row 1)
  • Rise per row ≈ 87.5mm

Floor-to-ceiling clearance per row (flat ceiling @ 4300mm):

Row Floor rise (mm) Floor-to-ceiling (mm)
Centre 0 4,300
Row 1 mid ~55 ~4,245
Row 2 mid ~140 ~4,160
Row 3 mid ~220 ~4,080
Row 4 mid ~310 ~3,990
Row 5 mid ~400 ~3,900
Row 6 mid ~480 ~3,820
Row 7 mid ~565 ~3,735
Row 8 mid ~650 ~3,650
Row 8 back 700 3,600

Corner area ceiling:

  • The ceiling drops in the corner zones due to building structure (lower roof soffit)
  • Floor-to-ceiling behind last corner row: 2,950mm
  • This is a structural change, not a continuation of the main flat ceiling

Corner geometry — 45° chamfer:

  • The building corners are cut at 45° behind the last corner seating row
  • The perimeter wall in each corner is not a right-angle but a diagonal plane at 45° to the two adjacent side walls
  • This means corner listeners face the hall centre across the chamfered geometry, not across a square corner
  • Acoustic implication: the 45° angled surface redirects reflections diagonally inward rather than straight back — less flutter risk than a 90° corner, but creates oblique reflections toward the main seating zone
  • The geometry code currently approximates corner rows by radial position only; the 45° chamfer is not yet explicitly modelled in plan view

Corner staging void (HVAC):

  • Below the corner seating there is a structural void — the corner seating is raised staging
  • This void is used as a plenum/duct for the HVAC ventilation system
  • The surfaces of this void (floor and underside of staging) are insulated to reduce sound transmission into and out of the ventilation path
  • Acoustic implication: the insulation damps what would otherwise be hard reflective surfaces; effectively the corner underfloor is acoustically treated
  • HVAC noise ingress from this void into the seating area is a risk factor — any REW measurement showing low-frequency noise floor elevation in the corner zone should be investigated against HVAC operating state

Ear height above local floor: 1,100mm (seated listener, consistent throughout)

Ceiling tile grid: 600mm × 600mm acoustic tiles — constrains speaker positions to 600mm increments from the grid


Speaker System

Layout — all flush mounted, NO drop rods

All speakers sit flush with the ceiling (acoustic face at ceiling level). There are no drop rods anywhere.

Ring Count Radius from centre Ceiling height at radius Notes
Centre 1 0mm ~4,300mm Over communion table
Inner ring 8 5,720mm ~4,060mm Between rows 4–5
Outer ring 8 8,600mm ~3,580mm Just inside row 8 back (8,411mm)
Corner 8 (2 per corner) 10,310mm ~2,950mm 1,500mm from side walls

Total: 25 speakers, all flush with ceiling.

Speaker height above centre floor datum = ceiling height at that radius (since drop rod = 0).

Corner speaker position

  • Radius: 10,310mm from centre
  • 1,500mm from each side wall (wall at 10,457mm → one axis coordinate = 8,957mm)
  • Cartesian: approximately (8,957mm, 5,107mm) from centre (solving 10,310² = 8,957² + y²)

MS6 Speaker — Technical Specification (ISS 1, June 2024, Dudley Wilkin)

Drivers:

  • LF: Visaton FRS8M — 8Ω, 30W, 88dB/1W/1m, resonant freq 125Hz, response 100Hz–20kHz
  • HF: Visaton G20SC — 8Ω, 20mm soft dome tweeter, 88dB/1W/1m, response 1,200Hz–30kHz
  • Driver spacing: 90mm centre-to-centre

Crossover innovation (key design element):

  • Conventional crossovers cause destructive interference lobes at 2–5kHz — exactly the consonant intelligibility band (P, T, F, S)
  • MS6 uses MIDAS MR12 digital mixer as active crossover with a hard frequency gap
  • LF driver hard cut-off: 2,792Hz
  • HF driver starts: 3,225Hz
  • Gap: 433Hz wide, centred around 3kHz — inaudible but eliminates driver overlap
  • Result: zero horizontal lobes, near-circular polar response to 55° off-axis

Polar response (measured):

  • At 30° off-axis: near-perfect circular across 200Hz–10kHz
  • At 55° off-axis: still excellent circular response
  • Maximum coverage angle: 55° off-axis from directly below

Signal chain:

Mono audio source
  → MIDAS MR12 Ch1 (Acoustic TEQ insert for room EQ)
  → Bus 1 → AUX1 → LF PEQ (crossover cut) → LF amplifier → 100V line → LF transformers
  → Bus 2 → AUX2 → HF PEQ (crossover cut) → HF amplifier → 100V line → HF transformers

MR12 AUX1 (LF driver) PEQ crossover settings:

  • Low: −15dB @ 3k09, Q=10
  • Low2: −15dB @ 3k31, Q=10
  • LoMid: −15dB @ 3k43, Q=10
  • HiMid: +4dB @ 2k79, Q=10

MR12 AUX2 (HF driver) PEQ crossover settings:

  • Low: −15dB @ 2k79, Q=10
  • Low2: −15dB @ 2k99, Q=10
  • LoMid: +5.5dB @ 3k20, Q=10
  • HiMid: +0.5dB @ 3k55, Q=10

Acoustic EQ (Channel 1 TEQ insert): Applied full-range before the crossover split, corrects room acoustics per installation.

Measurement protocol:

  • Use ARTA software with impulse window (minimum 10ms window)
  • Never use TrueRTA or any continuous-signal method
  • Mic minimum 2m from speaker (far field), at 90° horizontal so both drivers are equidistant

Historical system context (Dudley Wilkin documentation, 1970–2024)

  • 1970s: 4 corner column speakers — poor coverage, excited all room echoes
  • 1982: First ceiling ring — 8 speakers at row 5 radius + 4 diagonal + corner staging speakers
  • 1986: Anti-phasing breakthrough — quadrant anti-phasing creates null lines along aisle centrelines; each listener hears only one speaker segment → major intelligibility improvement
  • 2010: Proposed new layout — centre speaker (rows 1–3) + main ring (rows 4+) + outer ring fill; reduces count from 21 to 13
  • MS6 (2024): New bespoke two-driver speaker with gap crossover replacing earlier drivers

Anti-phasing principle: Adjacent speaker segments operate in opposite polarity. Null lines form along aisle centrelines. A listener on either side of an aisle hears only the speaker from their segment. This eliminates cross-segment interference and dramatically improves STI (Speech Transmission Index).


Acoustic Analysis Objectives

Key metrics (in priority order)

  1. STIPA — Speech Transmission Index for PA systems (IEC 60268-16); the single number that captures all intelligibility-degrading effects; target ≥ 0.75 (Excellent)
  2. Direct-to-Reverberant ratio per seat — primary intelligibility driver; improved by anti-phasing (each listener hears only their nearest segment)
  3. Haas window compliance — speakers reaching a listener more than 30ms after the nearest speaker cause echo/colouration; 10–30ms causes tonal colouration; both degrade STI modulation depth
  4. Off-axis angle per speaker per row — must be within 55° MS6 coverage cone
  5. SPL at ear per row — from inverse square law using speaker sensitivity and power
  6. Flutter echo — flat ceiling + rising floor creates parallel surfaces at decreasing clearance toward perimeter; harmonics in 500–4kHz band directly degrade STI

STIPA measurement

STIPA (Speech Transmission Index for PA systems) is the simplified single-measurement STI method defined in IEC 60268-16. It uses a pre-recorded test signal with specific modulation at frequencies across seven octave bands (125 Hz–8 kHz), played through the live PA system and measured with a handheld analyser.

IEC 60268-16 rating scale:

Rating STI range Colour
Excellent 0.75–1.00 Green
Good 0.60–0.75 Blue
Fair 0.45–0.60 Amber
Poor 0.30–0.45 Orange
Bad 0.00–0.30 Red

Target for this hall: ≥ 0.75 (Excellent)

Measurement equipment: NTI Acoustilyzer AL1 or equivalent STIPA-capable analyser.

Key degradation mechanisms in this hall (computed risk factors in Analysis tab):

  • Haas echo rows (>30ms) — directly reduce modulation depth in all STI bands
  • Off-axis coverage gaps (outside 55° cone) — reduces signal level, lowering D/R ratio
  • Flutter harmonics in 500–4kHz speech band — reduce modulation depth at those frequencies
  • Tile diffraction at ~571Hz — may introduce a spectral dip in a critical STI band
  • HVAC noise via corner staging void — raises background noise floor in corner seating

STIPA is valid for this hall type (it uses a modulated test signal, not raw SLM levels). Standard pink noise SLM readings are still invalid for checking individual speaker coverage due to anti-phase null lines.


How to measure STIPA in this hall

Equipment

  • STIPA analyser — NTI Acoustilyzer AL1 (or XL2 with STIPA option), or any IEC 60268-16 compliant handheld STI meter. The analyser contains the standardised STIPA test signal internally; no separate playback source is required.
  • Measurement microphone — the analyser's own omni mic capsule at ear height (1,100 mm above local floor level, which rises with the dish).

Operating conditions before measuring

  1. All speakers active — every ring that will be in use during a meeting must be powered on. STIPA measures the whole system as the listener experiences it.
  2. Anti-phasing active — the MIDAS MR12 must be configured with the normal quadrant (or per-speaker) anti-phasing wiring. Do not bypass or disable anti-phasing for the measurement.
  3. HVAC running — measure with the ventilation system in its normal operating state. This captures the real background noise contribution from the corner staging voids and ductwork. Record a separate background noise level (HVAC on, no audio signal) as reference.
  4. Hall otherwise empty — audience absorption changes the reverb time; an occupied-hall measurement requires people in seats.
  5. Signal level — set the PA to normal meeting operating level before starting. The STIPA signal is output at whatever level the system is set to, so level calibration matters.

Measurement positions

The anti-phasing null lines run along the aisle centrelines. A microphone placed exactly on a null line will read an artificially low STI — this is acoustically correct at that point (it is genuinely poor) but is not representative of normal seating positions. Measure between null lines.

Recommended positions (minimum set):

Zone Mic position Speaker dominant Notes
Centre zone Row 2, mid-bench, left of aisle Centre (×1) Single speaker — no null lines
Inner ring Row 5, mid-bench, between two inner speakers Inner ring Avoid aisle centreline
Outer ring Row 8 mid, between two outer speakers Outer ring Near row 8 back face
Back aisle At back aisle centreline, between outer speakers Outer ring Check rear coverage
Corner C1 Corner row 2, mid-bench Corner (×2/corner) One side of null between corner pairs
Corner C2 Corner row 2, opposite corner Corner (×2/corner) Verify symmetry

For a full survey, take at least two readings per row (one each side of the aisle centreline nearest to that seating position) to confirm the null-line boundary is where the geometry predicts it.

Step-by-step procedure

  1. Set up the PA at normal operating level. Confirm all rings are active and anti-phasing is correct.
  2. Start the HVAC, wait 2 minutes for the system to stabilise, then measure and record the background noise level (A-weighted dBA and NC curve if possible) at each measurement position. This is used by the STIPA algorithm internally but is worth recording separately.
  3. Position the mic at Row 2 mid-bench, ear height (local floor + 1,100 mm). Start the STIPA measurement. Wait for the analyser to complete its averaging period (typically 15 seconds for STIPA). Record the STI value and IEC rating.
  4. Move to each position in the table above, repeating the measurement. Allow 15 seconds settling time after moving before starting each measurement.
  5. At any position rated Fair or below, take a second measurement 300–400 mm to the left and right of the original position to distinguish between a genuine coverage gap and an accidental null-line hit.
  6. Record all results in a log: position, STI value, IEC rating, HVAC state, PA level, date/time, operator.
  7. Photograph the MR12 fader positions, AUX send levels, and PEQ settings at the time of measurement.

Interpreting results

  • Excellent (≥ 0.75) everywhere — system is performing correctly.
  • One zone consistently Good (0.60–0.75) — likely a coverage or level issue in that zone; check off-axis angles and SPL in the Analysis tab.
  • Fair or below in any seat — investigate: Haas violations (check delay values in Coverage tab), off-axis angles, flutter (compare flutter harmonic frequencies against any spectral dip visible in REW), or background noise.
  • Corner seating consistently lower than main seating — HVAC noise ingress via staging void is the most likely cause; re-measure with HVAC off to isolate.
  • STI varies widely left-to-right at same row — null-line boundaries may be misaligned with seating positions; review anti-phasing wiring orientation.

What STIPA does not replace

STIPA gives a single-number intelligibility score but does not identify the cause. Use it alongside:

  • The Coverage tab to diagnose Haas violations and off-axis angles
  • REW impulse measurements (ARTA method, minimum 10 ms window) to see the actual frequency response and early reflection pattern at each position
  • The Flutter analysis section to correlate any spectral dips with flutter harmonic frequencies

Haas window rules applied in code

  • Primary: nearest speaker to that row (0ms excess) — green
  • OK: 0–10ms Haas excess — blue (slight colouration, acceptable)
  • Warn: 10–30ms Haas excess — amber (audible colouration, degrades STI)
  • Danger: >30ms Haas excess — red (distinct echo, significantly degrades STI)

Flutter echo analysis

  • Flutter frequency at each zone = speed of sound / (2 × floor-to-ceiling clearance at ear level)
  • At row 8 (clearance ≈ 2,500mm ear-to-ceiling): flutter fundamental ≈ 69Hz, harmonics at 137Hz, 206Hz — in speech fundamental range
  • 600mm tile grid creates diffraction grating effect at ~571Hz
  • Harmonics landing in 500–4kHz range are flagged as STI risk factors in the Analysis tab

Codebase

Files

File Description
church-acoustic-suite.jsx Complete single-file React application — all components, geometry engine, UI
CLAUDE.md This project reference document
CLAUDE_CHAT.md Full transcript of the original design chat (historical reference)
README.md Repository readme

App structure (church-acoustic-suite.jsx)

Constants:

  • C — colour palette (dark theme)
  • HALL_DEFAULTS — confirmed hall geometry (20,914mm sq, 8 rows, dish floor, flat ceiling)
  • SPEAKER_DEFAULTS — 4 rings: centre, inner (×8, r=5720mm), outer (×8, r=8600mm), corner (×8, r=10310mm)
  • SETTINGS_DEFAULTS — Ollama/Claude API config, REW host/port, system prompt

Geometry engine:

  • ceilHeightAtRadius(r, hall) — returns floor-to-ceiling clearance at radius r
  • floorHeightAtRadius(r, hall) — returns floor rise above centre datum at radius r
  • computeHallGeometry(hall) — builds rowData array with per-row geometry
  • computeCoverage(hall, speakers, rowData) — computes slant distance, delay, Haas status, off-axis angle, SPL per row×ring combination
  • computeFlutter(hall) — calculates flutter echo fundamentals and harmonics

Components:

  • SectionDiagram — SVG cross-section showing ceiling, floor dish, speaker positions and coverage rays
  • CoverageHeatmap — SVG grid (rows × speaker rings) colour-coded by Haas status
  • HallTab — Hall dimension inputs, ceiling/dish inputs, row geometry table, flutter analysis
  • SpeakerTab — Per-ring editor (radius, power, sensitivity, phase, drop rod)
  • CoverageTab — Coverage heatmap + full data table
  • AnalysisTab — REW API connection + Ollama/Claude analysis with streaming
  • SettingsTab — LLM provider selection (Ollama or Anthropic), REW host/port, system prompt
  • App — Root with tab navigation and log

Known code issue to fix

The ceilHeightAtRadius() function currently stores three separate ceiling heights (ceilAtRow1Front: 4338, ceilAtRow8Back: 3600, ceilAtCornerBack: 2950) and interpolates between them — this incorrectly models a sloped ceiling.

Correct model: ceiling is flat at a single absolute height (ceilFlat = 4300mm) above centre datum. Floor-to-ceiling at any radius = 4300 − floorHeightAtRadius(r). The corner area ceiling (2950mm) is a separate structural change that should be applied only in the corner zone.

The HALL_DEFAULTS and geometry engine need to be refactored to:

ceilFlat: 4300,         // flat ceiling, mm above centre floor datum
cornerCeilClearance: 2950,  // floor-to-ceiling in corner zone (structural drop)
dishRise: 700,          // floor rise from centre to row 8 back face

REW API Integration

Endpoints used

Endpoint Purpose
GET /application Connection test
PUT /roomsim/room-size Push hall dimensions to REW room simulator
GET /roomsim/frequency-response?micposition=Main&ppo=24 Simulated frequency response (Base64 big-endian float32)
GET /measurements List all saved measurements (returns array with uuid, title)
GET /measurements/:id/rt60?octaveFrac=1 Per-octave RT60 values for STI calculation
GET /measurements/:id/impulse-response Raw impulse response (Base64 big-endian float32)
GET /measurements/:id/frequency-response Frequency response for a saved measurement

STI from REW impulse response

REW can calculate STI internally (via its STI panel, IEC 60268-16:2020 indirect method). The app implements the same calculation:

  1. Connect to REW — the app fetches the saved measurement list automatically
  2. Select a measurement from the dropdown in the Analysis tab
  3. Click Compute STI — the app calls GET /measurements/:id/rt60?octaveFrac=1 and runs the IEC 60268-16 Schroeder/Houtgast–Steeneken calculation
  4. The result shows overall STI, per-band Transmission Index (TI), and per-band T60

Calculation method (implemented in src/sti.js):

  • For each of the 7 STI octave bands (125 Hz–8 kHz) and 14 modulation frequencies (0.63–12.5 Hz):
    • MTF from T60: 1 / √(1 + (2π × Fm × T60 / 13.8)²) (Schroeder formula)
    • Optional noise correction: MTF_eff = MTF × 10^(SNR/10) / (1 + 10^(SNR/10))
    • Apparent SNR: 10 × log10(MTF_eff / (1 − MTF_eff)), clamped to [−15, +15] dB
    • Band TI: (SNR_apparent + 15) / 30, averaged over 14 modulation frequencies
  • STI = Σ(α_k × TI_k) − Σ(β_k × √(TI_k × TI_{k+1})) using male speech weights (IEC Table B.1)
  • No background noise correction is applied unless per-band SNR is provided

Important: The RT60 method gives a theoretically exact STI only for a diffuse reverberant field. In this hall with anti-phasing, each listener hears primarily one speaker, so the effective RT60 driving that listener's intelligibility is different from the room RT60. Treat the REW-derived STI as an upper bound; compare with measured STIPA at representative seat positions.


LLM Integration

Ollama (preferred — local/private):

  • Requires OLLAMA_ORIGINS=* ollama serve for CORS
  • Models: pull e.g. ollama pull llama3.2, mistral, qwen2.5
  • Endpoint: POST /api/chat with streaming

Anthropic Claude (fallback):

  • API key entered in Settings tab
  • Endpoint: POST https://api.anthropic.com/v1/messages
  • Default model: claude-sonnet-4-20250514

Analysis prompt: Sends complete hall geometry, per-row coverage data (slant, delay, Haas, off-axis, SPL), flutter echo analysis, and optional REW frequency data. Requests structured sections: hall assessment, coverage issues, speaker position optimisation, Haas/delay analysis, anti-phasing assessment, MR12 EQ recommendations, ARTA measurement protocol.

Analysis prompt: You are an expert acoustic consultant specialising in distributed ceiling speaker systems for large assembly halls. You have deep knowledge of accoustic ceiling speaker layout and design, including the MS6 speaker (Visaton FRS8M + G20SC drivers), the MIDAS MR12 active crossover with its 433 Hz gap at 3 kHz, anti-phasing techniques, and the Haas effect in multi-speaker environments. Analyse the provided coverage data and give specific, technical, actionable recommendations.


Build / Run

This is a standalone React JSX file intended for use in a CodeSandbox, Vite React project, or similar. It uses only React hooks (useState, useCallback, useRef, useEffect) with no external dependencies beyond React itself.

To run locally:

  1. Create a Vite React project: npm create vite@latest -- --template react
  2. Replace src/App.jsx with church-acoustic-suite.jsx
  3. npm run dev
  4. Start REW on same machine with API enabled
  5. Start Ollama with CORS enabled: OLLAMA_ORIGINS=* ollama serve