> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reactor.inc/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Reactor hosts multiple models, each with its own connect slug (modelName) and command/event schema. The catalog of every model — slug, typed SDK package, and links to its schema — is at /model-api-reference/overview. Some models expose one slug per experience (e.g. HappyOyster); always take the slug from the model's own pages, never guess it.
> Fastest path to a working app: `npx create-reactor-app my-app --model=<slug>` scaffolds a complete app with secure auth wired up. Typed TypeScript SDKs are published as @reactor-models/<model>; Python uses the base reactor-sdk package.
> Auth: exchange an API key (rk_...) for a JWT via POST https://api.reactor.inc/tokens from your server. Never put the API key in client-side code.
> Append .md to any docs URL for clean Markdown. Search these docs via the MCP server at https://docs.reactor.inc/mcp.

# Audio devices

> Speaker and Microphone, in a target nothing links by accident

Real audio devices are an **opt-in extra in a target of their own**. The core is pinned to a
synthetic audio module and cannot be talked out of it: nothing on the mandatory path can open a
microphone, so a model that happens to declare a sendonly audio track never puts a live microphone
on the wire.

```cmake theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
find_package(reactor-sdk REQUIRED)
target_link_libraries(app PRIVATE reactor::sdk reactor::sdk_audio)
```

```cpp theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
#include <reactor/audio_devices.hpp>

reactor::audio::Speaker speaker{client.track("main_audio")};
speaker.start();
```

Linking `reactor::sdk` alone brings in no audio library, no device enumeration and none of this.

<Warning>
  This is the one part of the SDK with a dependency the archive does not carry, and only on Linux:
  the backend is loaded at run time from whichever of `libasound.so.2` (ALSA), `libpulse.so.0` or
  `libjack.so.0` is present. A slim container image usually has none, and `start()` then throws
  rather than playing silence — `apt install libasound2` or `dnf install alsa-lib` is the fix. macOS
  and Windows use the system frameworks and need nothing installed.
</Warning>

***

## `Speaker`

Plays what arrives on a recvonly audio track.

```cpp Definition theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
class Speaker {
 public:
  explicit Speaker(Track track);

  void start();
  void stop();

  std::uint64_t dropped_ms() const noexcept;
  std::uint64_t under_runs() const noexcept;

  void submit(Samples pcm, std::uint32_t sample_rate, std::uint32_t channels);
};
```

Constructing one attaches to the track without starting playback. Throws
[`InvalidStateError`](/sdk-reference/cpp/types#reactorerror) if the track is not a recvonly audio
track — a speaker on anything else would play nothing, forever, without saying why.

Neither copyable nor movable: the device callback holds a pointer to it.

### `start()` / `stop()`

```cpp Signature theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
void start();
void stop();
```

Both idempotent, and `stop()` is safe to call from a handler.

The device is opened on the **first frame** rather than in `start()`, because its sample rate and
channel count come from the audio itself — guessing them and reopening on the first mismatch is
audible.

### `dropped_ms()` / `under_runs()`

```cpp Signature theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
std::uint64_t dropped_ms() const noexcept;
std::uint64_t under_runs() const noexcept;
```

The queue inside a `Speaker` is a jitter buffer, and that is the whole design: audio arrives in \~10
ms frames on the library's thread, and a playback device asks for samples on *its* clock. Neither
waits for the other, so the buffer absorbs the difference — and when it cannot, the two failure
modes are worth telling apart.

|                |                                                                                                       |
| -------------- | ----------------------------------------------------------------------------------------------------- |
| `dropped_ms()` | Milliseconds thrown away because the buffer was full — **the device is slower than the stream**       |
| `under_runs()` | Times the device asked for samples the buffer did not have — **the stream is slower than the device** |

A single "glitches" counter would hide which of the two you have.

### `submit()`

```cpp Signature theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
void submit(Samples pcm, std::uint32_t sample_rate, std::uint32_t channels);
```

Push PCM in directly, without a track — for a caller mixing their own audio. This is what the track
handler calls.

***

## `Microphone`

Captures from a real microphone and pushes into a published sendonly track.

```cpp Definition theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
class Microphone {
 public:
  explicit Microphone(Track track);

  void start();
  void stop();

  std::uint64_t blocks_sent() const noexcept;
  std::uint64_t blocks_refused() const noexcept;
};
```

Nothing here starts by itself: constructing a `Microphone` opens no device, and `start()` is the
only thing that does. Throws `InvalidStateError` if the track is not a sendonly audio track.

<Note>
  The track has to be [published](/sdk-reference/cpp/track#publish) before `start()`. Publishing is
  what puts a sender behind the slot, and a push before it is refused rather than dropped — which is
  what `blocks_refused()` counts.

  ```cpp theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  auto mic_track = client.track("microphone");
  mic_track.publish().get();

  reactor::audio::Microphone microphone{mic_track};
  microphone.start();
  ```
</Note>

### `blocks_sent()` / `blocks_refused()`

```cpp Signature theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
std::uint64_t blocks_sent() const noexcept;
std::uint64_t blocks_refused() const noexcept;
```

Blocks of PCM handed to the track, and captured blocks the track refused. A refusal is recorded
rather than thrown: this runs on the device's own thread, where there is nobody to catch it.

A `blocks_refused()` climbing with `blocks_sent()` stuck at zero means the track was never
published.

***

## `devices_available()`

```cpp Signature theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
bool devices_available() noexcept;
```

Whether this build can open real devices. `false` in a build made without the audio backend — the
classes above then throw on `start()` rather than silently playing nothing.
