# 11. Playback Status and Channel Callbacks

You can monitor haptic playback in two ways: **polling** playback status, or **registering callbacks** for idle and wake events. Use these to coordinate application logic, manage power (e.g. shut down amplifiers when idle), or react when effects finish.

---

## 11.1 Playback Status Polling

### Per-channel status

Use `vh.playbackStatus(channelNo)` to check whether a specific channel is playing or idle:

```cpp
VHPlaybackState ch1 = vh.playbackStatus(1);
VHPlaybackState ch2 = vh.playbackStatus(2);

if (ch1 == VHPlaybackState::VH_PLAYING) {
    Serial.println("Channel 1: playing");
} else if (ch1 == VHPlaybackState::VH_IDLE) {
    Serial.println("Channel 1: idle");
}
```

| State                          | Meaning                                   |
|--------------------------------|-------------------------------------------|
| `VHPlaybackState::VH_IDLE`     | Channel is idle; no effect is playing     |
| `VHPlaybackState::VH_PLAYING`  | Effect is actively playing on that channel|
| `VHPlaybackState::VH_DISABLED` | Channel is disabled                       |
| `VHPlaybackState::VH_UNKNOWN`  | State could not be determined             |

### Overall device status

Call `vh.playbackStatus()` with no arguments to check the global state:

```cpp
VHPlaybackState overall = vh.playbackStatus();
if (overall == VHPlaybackState::VH_IDLE) {
    Serial.println("All channels idle — ready for next effect");
}
```

This is useful when you want to know if *any* channel is still playing (e.g. before transitioning to a new UI state).

### Example — coordinate logic with status

```cpp
void setup() {
    channels.add(1, {PIN_LEFT}, {"left"});
    channels.add(2, {PIN_RIGHT}, {"right"});
    vh.init(&board, {&channels});

    vh.queue({VIBRATE(2000, 0.8, 160.0, 0.3)}, 0);  // queue to all channels
}

void loop() {
    if (!vh.queueIsEmpty(0)) {
        vh.renderQueue(0);
    }

    VHPlaybackState ch1 = vh.playbackStatus(1);
    VHPlaybackState ch2 = vh.playbackStatus(2);

    if (ch1 == VHPlaybackState::VH_PLAYING) Serial.println("Channel 1: playing");
    if (ch1 == VHPlaybackState::VH_IDLE)    Serial.println("Channel 1: idle");
    if (ch2 == VHPlaybackState::VH_PLAYING) Serial.println("Channel 2: playing");
    if (ch2 == VHPlaybackState::VH_IDLE)    Serial.println("Channel 2: idle");

    VHPlaybackState overall = vh.playbackStatus();
    if (overall == VHPlaybackState::VH_IDLE) {
        Serial.println("All channels idle — ready for next effect");
    }

    delay(100);
}
```

---

## 11.2 Channel Idle and Wake Callbacks

Instead of polling, you can register callbacks that fire when a channel becomes idle or starts playing. This is useful for:

- **Power management** — turn off an amplifier when a channel goes idle
- **Logging** — notify when effects finish or start
- **UI feedback** — update LEDs or indicators when playback state changes

### Enable idle detection

Before attaching callbacks, enable idle detection on the channel. **Must be called before `vh.init()`** — the idle handler task is created at init based on this setting. Use `false` to disable on specific channels (or `0` for all).

```cpp
channels.enableIdleHandler(1, true);   // enable for channel 1 (default)
channels.enableIdleHandler(2, false);  // disable for channel 2
```

### Attach callbacks

```cpp
// Callback when channel goes idle (no effects playing)
channels.attachCallback(1, CHANNEL_IDLE, [](uint8_t channelNo) {
    Serial.printf("Channel %d is now IDLE\n", channelNo);
    // Good place: enter low-power mode, turn off amplifier, etc.
});

// Callback when channel wakes up (effect starts playing)
channels.attachCallback(1, CHANNEL_WAKE, [](uint8_t channelNo) {
    Serial.printf("Channel %d is now ACTIVE\n", channelNo);
    // Good place: enable amplifier, wake from sleep, etc.
});
```

| Event type    | When it fires                             |
|---------------|-------------------------------------------|
| `CHANNEL_IDLE`| Channel finishes its last effect and goes idle |
| `CHANNEL_WAKE`| Effect starts playing on that channel     |

### Example — power management

```cpp
void setup() {
    Serial.begin(115200);

    channels.add(1, {YOUR_OUTPUT_PIN}, {"main"});
    channels.enableIdleHandler(1, true);  // Must be before init
    vh.init(&board, {&channels});

    channels.attachCallback(1, CHANNEL_IDLE, [](uint8_t channelNo) {
        Serial.printf("Channel %d is now IDLE\n", channelNo);
        // Turn off amplifier, enter low-power mode, etc.
    });

    channels.attachCallback(1, CHANNEL_WAKE, [](uint8_t channelNo) {
        Serial.printf("Channel %d is now ACTIVE\n", channelNo);
        // Wake amplifier, exit low-power mode, etc.
    });
}

void loop() {
    vh.play(VIBRATE(300, 0.7, 160.0, 0.3));
    delay(2000);  // Idle callback fires during this gap
}
```

---

## 11.3 Dual-pin callbacks

For dual-pin (H-bridge) channels, you can attach callbacks that receive both the channel number and pin index:

```cpp
channels.attachCallback(1, CHANNEL_IDLE, [](uint8_t chnl, uint8_t pin) {
    // Dual-pin channel went idle; chnl = channel ID, pin = which pin
});
```

Use this when you need per-pin control (e.g. disabling each half of an H-bridge separately).

---

For more examples, see [CODE_EXAMPLES.md § Channel Idle/Wake Callbacks](../CODE_EXAMPLES.md) and [CODE_EXAMPLES.md § Playback Status Polling](../CODE_EXAMPLES.md).
