# 2. Basic Primitives

Primitives are the building blocks of haptic feedback. Each one produces a waveform that drives the actuator. You choose a primitive based on the feel you want—a short click, a sustained buzz, a sweep from low to high—and combine them into sequences for richer feedback.

**Common parameters across primitives:**
- **Intensity** — Amplitude of the vibration. 0.0 = off, 1.0 = full strength.
- **Duration** — How long the effect lasts, in milliseconds (ms).
- **Sharpness** — Shape of the waveform envelope. 0.0 = smooth sine-like feel; 1.0 = spiky, sharper haptic sensation.

The sections below describe each primitive, when to use it, and how to set its parameters.

---

## 2.1 PULSE

A single, soft impulse. Used for pulses, bumps, wobbles, and motion forces.

**Syntax:** `PULSE(duration, intensity, sharpness)`

| Param     | Type  | Range        | Default      |
|-----------|-------|--------------|--------------|
| duration  | float | 1–200        | 1000/Resonant_freq* |
| intensity | float | 0.0–1.0      | 0.7          |
| sharpness | float | 0.0–1.0      | 0.5          |

```cpp
vh.play(PULSE(50, 0.8, 0.5));   // 50ms pulse, 80% intensity, medium sharpness
```

*Resonant_freq is a parameter that can be configured in VHUtilities.h that controls the default values of pulse, tick, and vibrate primitives.

---

## 2.2 TICK

A single, sharp impulse. Used for clicks, textures, taps, and impacts.

**Syntax:** `TICK(duration, intensity, sharpness)`

| Param     | Type  | Range        | Default      |
|-----------|-------|--------------|--------------|
| duration  | float | 1–200        | 1000/Resonant_freq |
| intensity | float | 0.0–1.0      | 0.7          |
| sharpness | float | 0.0–1.0      | 1.0          |

TICK defaults to maximum sharpness (1.0) for a crisp click.

```cpp
vh.play(TICK(30, 0.9, 1.0));   // 30ms tick, 90% intensity, full sharpness
```

---

## 2.3 VIBRATE

A continuous vibration with configurable frequency, intensity, and sharpness. Used for alerts, textures, and sustained effects.

**Syntax:** `VIBRATE(duration, intensity, frequency, sharpness)`

| Param    | Type  | Range          | Default      |
|----------|-------|----------------|--------------|
| duration | float | 1–10000        | 625 (FRQ_TO_DUR(160)) |
| intensity| float | 0.0–1.0        | 0.7          |
| frequency| float | 0–1000 Hz      | Resonant_freq (160 Hz) |
| sharpness| float | 0.0–1.0        | 0.0          |

```cpp
vh.play(VIBRATE(300, 0.7, 160.0, 0.3));   // 300ms vibration at 160Hz
```

---

## 2.4 PAUSE

Pauses haptic output for a specified duration. Use between primitives in a sequence to add silence.

**Syntax:** `PAUSE(duration)`

| Param    | Type  | Range    | Default |
|----------|-------|----------|---------|
| duration | float | 1–10000  | 500     |

```cpp
vh.play(PAUSE(100));   // 100ms silence
```

PAUSE is most useful in sequences—for example, two pulses with a pause between them.

---

## 2.5 ERM

Constant-intensity vibration compatible only with a classic ERM (eccentric rotating mass) motor on dual-pin H-bridge channels. No frequency or sharpness—just strength and duration, negative intensity reverses polarity.

**Syntax:** `ERM(duration, intensity)`

| Param    | Type  | Range      | Default |
|----------|-------|------------|---------|
| duration | float | 1–10000    | 1000    |
| intensity| float | -1.0–1.0   | 1.0     |

```cpp
vh.play(ERM(500, 0.6));    // 500ms constant vibration at 60%
vh.play(ERM(1000, -0.5));  // On dual-pin: 1 second, reverse direction
```

---

## 2.6 SWEEP

A smooth transition of intensity, frequency, and sharpness from one set of values to another. You choose start and end values and an easing function for each parameter.

**Syntax:** `SWEEP(duration, startIntensity, startFrequency, startSharpness, endIntensity, endFrequency, endSharpness, transIntensity, transFrequency, transSharpness)`

| Param          | Type           | Range       |
|----------------|----------------|-------------|
| duration       | float          | ms          |
| startIntensity | float          | 0.0–1.0     |
| startFrequency | float          | Hz          |
| startSharpness | float          | 0.0–1.0     |
| endIntensity   | float          | 0.0–1.0     |
| endFrequency   | float          | Hz          |
| endSharpness   | float          | 0.0–1.0     |
| transIntensity | TransitionType | Easing enum |
| transFrequency | TransitionType | Easing enum |
| transSharpness | TransitionType | Easing enum |

```cpp
vh.play(SWEEP(
    1000,                              // 1 second
    0.3, 80.0, 0.0,                    // Start: 30% intensity, 80Hz, no sharpness
    1.0, 300.0, 0.8,                   // End: full intensity, 300Hz, 80% sharpness
    TransitionType::EaseInOutCubic,     // Intensity eases in and out
    TransitionType::Linear,             // Frequency ramps linearly
    TransitionType::EaseOutQuad         // Sharpness eases out
));
```

Common easing options: `Linear`, `EaseInQuad`, `EaseOutQuad`, `EaseInOutQuad`, `EaseInCubic`, `EaseOutCubic`, `EaseInOutCubic`, `EaseInSine`, `EaseOutSine`, `EaseInOutSine`, `EaseOutBounce`, etc. See [API_REFERENCE.md](../API_REFERENCE.md) for the full list.

---

## 2.7 PCM

Play raw 8-bit unsigned PCM waveform data. Use this when you have a precomputed waveform or data from a tool. 127 is silence; 0 is minimum, 255 is maximum. Sample rate is configurable via `VH_Pcm::setSampleRate()` (default 8000 Hz).

**Syntax:** `PCM(pcm_array, size)`

| Param     | Type             | Description                                      |
|-----------|------------------|--------------------------------------------------|
| pcm_array | const uint8_t*   | Pointer to PCM data (8-bit unsigned, 127=silence)|
| size      | uint16_t         | Number of samples                                |

```cpp
const uint8_t myWaveform[] = { 127, 140, 166, 200, 255, 200, 166, 140, 127 };
vh.play(PCM(myWaveform, sizeof(myWaveform)));
```

---

## 2.8 AUDIO_STREAM

Route live audio (e.g. from Bluetooth A2DP) to haptic output. The audio stream is processed and converted to haptic drive. Requires the AudioStreaming extension and a configured ring buffer. See [05-audio.md](05-audio.md) for setup and usage.

**Syntax:** `AUDIO_STREAM(durationMs, intensity [, audioChannel])`

| Param        | Type   | Range              | Default | Description                            |
|--------------|--------|--------------------|---------|----------------------------------------|
| durationMs   | float  | ms, -1 = infinite  | —       | Use `VH_AUDIO_STREAM_INFINITY` for infinite |
| intensity    | float  | 0.0–1.0            | 1.0     | Volume scaling                         |
| audioChannel | uint8_t| optional           | 0       | Audio channel index (multi-buffer mode)|

---

## 2.9 Effect Sequences

Play multiple primitives back-to-back by passing a list to `play()`:

```cpp
vh.play({
    PULSE(50, 0.8, 0.5),
    PAUSE(100),
    TICK(30, 1.0, 1.0),
    PAUSE(100),
    VIBRATE(200, 0.6, 160.0, 0.2)
});
```

`play()` runs the sequence in order and blocks until it finishes.

You can also store a sequence in a variable and reuse it:

```cpp
std::initializer_list<std::unique_ptr<IVhEffect>> effectList = {
    VIBRATE(1000, 0.3, 100.0, 0.0),
    PULSE(50, 0.8, 0.5),
    PAUSE(1000),
    TICK(30, 1.0, 1.0)
};

void loop() {
    vh.play(effectList);   // Reuse the same sequence
    delay(500);
}
```

---

## 2.10 Customizing Parameters in Real Time

You can build parameters from sensor input or UI state and pass them into primitives. The example below adjusts intensity and duration based on a button press:

```cpp
const int buttonPin = 19;

void setup() {
    pinMode(buttonPin, INPUT_PULLUP);
    channels.add(1, {YOUR_OUTPUT_PIN}, {"main"});
    vh.init(&board, {&channels});
}

void loop() {
    int buttonState = digitalRead(buttonPin);
    float intensity;
    int duration;

    if (buttonState == LOW) {
        intensity = 1.0f;    // Button pressed: 100% strength
        duration = 1000.0f;  // 1 second
    } else {
        intensity = 0.3f;    // Button released: 30% strength
        duration = 300.0f;   // 300ms
    }

    vh.play({
        VIBRATE(duration, intensity, 100.0f, 0.0f),
        PULSE(50, intensity, 0.8f),
        PAUSE(500),
        TICK(30, intensity, 1.0f)
    });
    delay(200);
}
```

---

## 2.11 Customizing Defaults

You can omit parameters and use class defaults, or change defaults globally:

```cpp
VHPulse::setDefault(50, 0.8, 0.5);   // duration, intensity, sharpness
VHVibrate::setDefault(300, 0.5f, 160.0f, 0.2f);  // duration, intensity, frequency (Hz), sharpness

vh.play(PULSE());     // Uses 50ms, 0.8, 0.5
vh.play(VIBRATE());   // Uses 160Hz and other defaults
```

For min/max limits and more configuration options, see [09-primitive-manager.md](09-primitive-manager.md).
