# Sound Report

> What a sound was read as, in a dialog: its loudness and true peak, its key and tempo when it has them, and each of ACX's audiobook requirements with whether the sound meets it.

## Installation

**CLI**

```bash
npx shadcn@latest add @audio-ui/sound-report
```

**Manual**

**1. Install the following dependencies:**

```bash
npm install @audio-ui/core @audio-ui/engine
```

**2. Copy and paste the following code into your project.**

```tsx title="components/audio-ui/sound-report.tsx"
import { pitchClassName } from "@audio-ui/core";
import {
  ACX,
  type AcxCheck,
  type SoundReport as Report,
} from "@audio-ui/engine/analysis";
import { formatLevel, formatTempo } from "@audio-ui/engine/format";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";

interface SoundReportProps {
  onOpenChange: (open: boolean) => void;
  /** What the sound was read as: `null` closes the dialog. */
  report: Report | null;
}

/** One reading and what it is. */
function Row({ label, value }: { label: string; value: string }) {
  return (
    <div className="flex items-baseline justify-between gap-4 border-b py-1.5 last:border-b-0">
      <dt className="text-muted-foreground text-sm">{label}</dt>
      <dd className="font-mono text-sm tabular-nums">{value}</dd>
    </div>
  );
}

/** One ACX requirement: what it asks, the level measured, and whether it holds. */
function Requirement({
  check,
  label,
  want,
}: {
  check: AcxCheck;
  label: string;
  want: string;
}) {
  return (
    <Row
      label={`${label}, ${want}`}
      value={`${check.value === null ? "Not measurable" : formatLevel(check.value, "dBFS")}, ${check.pass ? "passes" : "fails"}`}
    />
  );
}

/**
 * What a sound was read as, in a dialog: its loudness and peaks, its key and
 * tempo when it has them, and each of ACX's audiobook requirements with
 * whether the sound meets it. Controlled: it is open while `report` is set.
 */
export function SoundReport({ onOpenChange, report }: SoundReportProps) {
  return (
    <Dialog onOpenChange={onOpenChange} open={report !== null}>
      <DialogContent className="sm:max-w-md">
        <DialogHeader>
          <DialogTitle>Analysis</DialogTitle>
          <DialogDescription>
            Read from the sound as edited, effects left out.
          </DialogDescription>
        </DialogHeader>
        {report ? (
          <div className="flex flex-col gap-4">
            <dl>
              <Row
                label="Loudness"
                value={formatLevel(report.loudness.lufs, "LUFS")}
              />
              <Row
                label="True peak"
                value={formatLevel(report.loudness.truePeak, "dBTP")}
              />
              <Row
                label="Key"
                value={
                  report.key
                    ? `${pitchClassName(report.key.root)} ${report.key.name}`
                    : "None found"
                }
              />
              <Row
                label="Tempo"
                value={report.tempo ? formatTempo(report.tempo) : "None found"}
              />
            </dl>
            <div>
              <h3 className="font-medium text-sm">
                ACX audiobook: {report.acx.pass ? "passes" : "does not pass"}
              </h3>
              <dl>
                <Requirement
                  check={report.acx.rms}
                  label="RMS"
                  want={`${ACX.minRms} to ${ACX.maxRms} dBFS`}
                />
                <Requirement
                  check={report.acx.peak}
                  label="Peak"
                  want={`at most ${ACX.maxPeak} dBFS`}
                />
                <Requirement
                  check={report.acx.noiseFloor}
                  label="Noise floor"
                  want={`at most ${ACX.maxNoiseFloor} dBFS`}
                />
              </dl>
            </div>
          </div>
        ) : null}
      </DialogContent>
    </Dialog>
  );
}
```

**3. Update the import paths to match your project setup.**

**Agent**

Give your agent the skill, and it picks the component, installs it and wires it to your audio:

```bash
npx skills add ouestlabs/audio-ui
```

Then ask it:

```text
Add the sound report from audio/ui to my project and wire it to my audio code.
```

To let the agent read the docs live, connect the [MCP server](/docs/mcp). More in [Agent Skill](/docs/agents).

## Usage

`SoundReport` is controlled: it is open while `report` is set, and `onOpenChange(false)` is where you clear it. The report is the one `reportSound` of [`@audio-ui/engine/analysis`](/docs/engine/analysis) gives, held by [`useSoundReport`](/docs/hooks/use-sound-report).

```tsx
import { SoundReport } from "@/components/audio-ui/sound-report";

<SoundReport onOpenChange={(open) => !open && clear()} report={report} />;
```

| Prop | Type | |
| --- | --- | --- |
| `report` | `SoundReport \| null` | What the sound was read as. `null` closes the dialog. |
| `onOpenChange` | `(open: boolean) => void` | The dialog was dismissed. |
