# 3. Channels

Channels define where haptic output is sent. Each channel maps to one or more GPIO pins. You create channels with `channels.add()` and target them in `vh.play()` or `vh.queue()` calls.

**Syntax:** `channels.add(channelId, {GPIO pins}, {Tags}, {Allowed primitives});`

Tags and allowed primitives are optional.

**Channel ID:** Channel ID is the internal ID assigned. It can be used to refer to that channel. Channel ID can be between 1-255. It cannot be 0.

**GPIO pins:** GPIO pins are defined in a vector as the second argument. A single pin assumes a linear amplifier or low-side drive. If two pins are defined, the library assumes an H-bridge motor driver (dual-pin drive).

**Tags:** Tags are a way to describe a channel with a descriptive label, such as "Left" or "Body". They can be used to target the channel (More on that below).

**Allowed Primitives:** Specific primitives can be assigned to a channel. This restricts the channel to only playing these specific primitives. Not specifiying this argument assumes all primitives are allowed.

**Targeting channels:** You can target by channel ID (integer), channel tag (string), or an explicit channel vector `{0, 1}`. More on that below.

---

## 3.1 Target by Channel ID

Play on a specific channel number:

```cpp
channels.add(1, {PIN_LEFT}, {"left", "motor"});
channels.add(2, {PIN_RIGHT}, {"right", "motor"});
channels.add(3, {PIN_CENTER}, {"center"});

vh.init(&board, {&channels});

vh.play(PULSE(50, 0.8, 0.5), 1);   // Channel 1 only (left)
vh.play(PULSE(50, 0.8, 0.5), 2);   // Channel 2 only (right)
```

---

## 3.2 Target by Channel Tags

Assign string tags to channels and use tag expressions to target one or more channels. Boolean operators work within the string; the string is whitespace-sensitive.

```cpp
channels.add(1, {PIN_LEFT}, {"left", "hand", "motor"});
    channels.add(2, {PIN_RIGHT}, {"right", "hand", "motor"});
    channels.add(3, {PIN_CENTER_A, PIN_CENTER_B}, {"center", "body"});

vh.init(&board, {&channels});

vh.play(VIBRATE(200, 0.7, 160.0, 0.3), "motor");      // All channels tagged "motor" (1 and 2)
vh.play(PULSE(50, 1.0, 0.8), "left | center");        // Channels tagged "left" OR "center" (1 and 3)
vh.play(TICK(30, 0.9, 1.0), "left & motor");          // Channels tagged "left" AND "motor" (1 only)
vh.play(VIBRATE(500, 0.5, 100.0, 0.0), "hand");      // All channels tagged "hand" (1 and 2)
```

**Supported Boolean operators:**

| Operator | Name | Description | Example |
|----------|------|-------------|---------|
| `&` | AND | Targets channels that match both tags | `"left & motor"` → channel 1 only |
| `\|` | OR | Targets channels that match either tag | `"left \| right"` → channels 1 and 2 |

---

## 3.3 Target by Channel Vector

Play on multiple specific channels:

```cpp
vh.play(ERM(300, 0.5), {1, 3});   // Channels 1 and 3 only
```

---

## 3.4 Restrict Primitives per Channel

When adding a channel, you can specify which primitives are allowed. Only those primitives will play on that channel. If none are specified, all primitives are allowed. This avoids sending unsupported effect types to certain actuators (e.g. an ERM motor that cannot reproduce PCM or VIBRATE).

```cpp
channels.add(1, {PIN_LEFT}, {"left", "hand"}, {PULSE | TICK});           // Only PULSE and TICK
channels.add(2, {PIN_RIGHT}, {"right", "hand"}, {VIBRATE});              // Only VIBRATE
channels.add(3, {PIN_CENTER_A, PIN_CENTER_B}, {"center", "body"});       // All primitives (-1 or omit)
```

**Example behavior:**
- `vh.play(TICK(30, 1.0, 1.0), 1)` — plays on channel 1 (TICK allowed)
- `vh.play(VIBRATE(100, 0.5, 500, 0), 1)` — does **not** play on channel 1 (VIBRATE not allowed)
- `vh.play(VIBRATE(100, 0.5, 500, 0), 2)` — plays on channel 2 (VIBRATE allowed)
