# Speaker Analysis

*Loudspeaker enclosure functions (ac_*) and the report-only analysis node*

**Names:** `driver` `analysis` `ac_sealed_fc` `ac_sealed_qtc` `ac_port_length` `ac_helmholtz_fb` `fs` `qts` `vas` `sd` `xmax` `re` `sensitivity_db` `tolerance` `drive_watts` `port_diameter` `ports` `tuning` `range` `points`

## When to use

You are modelling a speaker cabinet and want its tuning or its response to follow the box.
Two separate things:

- **`ac_*` functions** are CEL builtins. They run before geometry, so a result may feed a
  dimension: a port length for a target tuning, the Fc of a sealed box.
- **`driver` and `analysis` nodes** are a report. They never change a solid, a cut list or a
  cache key; `ww analyze FILE` prints the response, the -3 dB point, port air speed and cone
  excursion, and `ww pack` writes the same into `analysis.json`.

Units are fixed and never written next to the number: **volumes in litres, frequencies in Hz,
port lengths and diameters in mm**. A bare number elsewhere in the file is still millimetres,
so a volume in a litre argument is the one place to check twice.

## Syntax

Functions (every argument finite and positive; `np` a whole number 1 to 16):

| Function | Arguments | Returns |
| --- | --- | --- |
| `ac_sealed_fc(fs, vas, vb)` | Hz, litres, litres | sealed-box resonance, Hz |
| `ac_sealed_qtc(qts, vas, vb)` | none, litres, litres | sealed-box total Q |
| `ac_port_length(vb, fb, dv, np)` | litres, Hz, mm, count | length of each port, mm, end correction (0.732 d) included |
| `ac_helmholtz_fb(vb, dv, lv, np)` | litres, mm, mm, count | tuning, Hz |

`driver Name { ... }` (top level; values are children, a number or a CEL expression):
`fs` Hz, `qts`, `vas` litres, `sd` cm2, `xmax` mm, `re` ohms, `sensitivity_db` dB at 1 W 1 m,
`tolerance` a fraction 0 to under 1 (default none). There is no `unit=`.

`analysis Name kind=sealed|ported driver=Name volume=<litres> [drive_watts=W] { range LO HI;
points N }`. `kind=ported` also needs `port_diameter=` (mm) and exactly one of `port_length=`
(mm, reports the tuning it makes) or `tuning=` (Hz, reports the length it needs), plus
optional `ports=` (count, default 1). `points` is at most 512 (default 256); more is an error,
not clamped. `range` defaults to 20 to 2000 Hz.

## Example

```kdl
project name="Sealed monitor" units=mm schema_version=1 {
    params {
        net_litres 20.0 min=5.0 max=80.0 step=1.0 label="Net volume" category="Acoustics"
        qtc "ac_sealed_qtc(0.35, 45.0, net_litres)"
    }
    materials {
        material mdf type=sheet
    }
    driver Woofer {
        fs 32.0
        qts 0.35
        vas 45.0
        sd 132.0
        xmax 6.0
        re 5.5
        sensitivity_db 88.0
        tolerance 0.15
    }
    analysis Response kind=sealed driver=Woofer volume=net_litres {
        range 20.0 2000.0
        points 128
    }
    component Panel role=side {
        stock mdf 300.0 400.0 18.0
    }
    assembly Root {
        part Side instance=Panel {
            by top_sw
            pos x=0.0 y=0.0 z=0.0
        }
    }
}
```

## Pitfalls and diagnostics

- **It is an estimate.** Every report has a `fidelity` object (`grade: "estimate"`) listing
  what is modelled and not: baffle step, diffraction, room, port compression, cabinet
  resonances, crossover, and leakage beyond an assumed Ql of 7. Say so when you quote a
  figure. The tolerance band is the min and max over the 2^3 corners of fs, qts and vas.
- **f3 is the half-power point**, -3.0103 dB, not -3 dB.
- **`port_length` cannot be derived from `ac_port_length`** when the net volume depends on
  the port's own displacement: that is a cycle and a hard error. Keep `port_length` authored
  and use the function in an advisory parameter, as `examples/speaker_ported.kdl` does.
- **`kind=transmission_line` is not built** (`not yet` diagnostic), nor are horns, passive
  radiators and band-pass boxes: this is why the file is `partial`. `volume=` cannot name a
  metrics volume yet.
- The inline `1.2 r` end correction in an older file differs from the library's `0.732 d`
  by about 1 Hz on a 33 Hz tuning.
- A function name is `ac_sealed_fc`, never `ac.sealed_fc`: method calls are refused.

## See also

- `docs/KDL_AUTHORING.md` section 18, `docs/adr/0037-acoustic-functions-and-analysis.md`.
- [`project-and-params`](project-and-params.md) for CEL and the function table.
- `examples/speaker_sealed.kdl`, `examples/speaker_ported.kdl`.

## Names

Every name this page declares, how the example library uses it, and the shortest real use.

### `driver`

project-level node, word-valued property; node in 2 of 116 examples (2 uses); property in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
driver Woofer {
    fs   "driver_fs"
    qts  "driver_qts"
    vas  "driver_vas"
    sd   132.0           // cm^2: a 6.5 in cone's effective piston area
    xmax 6.0             // mm, one-way linear excursion
    re   5.5             // ohms, DC resistance
    sensitivity_db 88.0  // dB SPL, 1 W / 1 m
    tolerance 0.15       // +/-15 % on Fs, Qts and Vas
}
```

### `analysis`

project-level node; node in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_sealed.kdl`:

```kdl
analysis Response kind=sealed driver=Woofer volume=vb_net_liters drive_watts=5.0 {
    range 20.0 2000.0
    points 256
}
```

### `ac_sealed_fc`

expression function; not used by any example yet.

### `ac_sealed_qtc`

expression function; not used by any example yet.

### `ac_port_length`

expression function; not used by any example yet.

### `ac_helmholtz_fb`

expression function; not used by any example yet.

### `fs`

node in 4 of 116 examples (4 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
fs   "driver_fs"
```

### `qts`

node in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
qts  "driver_qts"
```

### `vas`

node in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
vas  "driver_vas"
```

### `sd`

node in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
sd   132.0
```

### `xmax`

node in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
xmax 6.0
```

### `re`

node in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
re   5.5
```

### `sensitivity_db`

node in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
sensitivity_db 88.0
```

### `tolerance`

property; node in 2 of 116 examples (2 uses); property in 3 of 116 examples (36 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
tolerance 0.15
```

### `drive_watts`

property; property in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_sealed.kdl`:

```kdl
analysis Response kind=sealed driver=Woofer volume=vb_net_liters drive_watts=5.0 {
    range 20.0 2000.0
    points 256
}
```

### `port_diameter`

property; property in 1 of 116 examples (1 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
analysis Response kind=ported driver=Woofer volume=vb_net_liters \
    port_diameter="2.0 * port_radius" port_length=port_length drive_watts=5.0 {
    range 15.0 2000.0
    points 256
}
```

### `ports`

not used by any example yet.

### `tuning`

not used by any example yet.

### `range`

node in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
range 15.0 2000.0
```

### `points`

node in 2 of 116 examples (2 uses).

Shortest real use, from `speaker_ported.kdl`:

```kdl
points 256
```

