Synthesized sine streaming
Synthesize a 16 kHz mono PCM16 sine on the MCU and stream it to the Hapbeat. No stored audio, and frequency / intensity are live.
One-shot
Section titled “One-shot”hb.playSine(160.0f, 0.7f, 400); // 160 Hz, intensity 0.7, 400 msplaySine blocks for durationMs. Good for short haptic feedback.
Continuous (hold / live control)
Section titled “Continuous (hold / live control)”To buzz while a button is held, or to drive frequency from a sensor, use
beginSine / pumpSine / endSine. Call pumpSine() often from loop() to keep
the device ring buffer filled.
void onPressDown() { hb.beginSine(160.0f, 0.8f); // start a continuous sine}
void loop() { // ... handle input ... if (hb.sineActive()) hb.pumpSine(); // call every loop (prevents dropouts)}
void onPressUp() { hb.endSine(); // stop}pumpSine()sends a bounded number of chunks per call, so as long as you don’t blockloop()for ~160 ms+, it self-paces.- To change frequency / intensity mid-stream, call
endSine()and thenbeginSine()again with the new values.
Reducing choppiness
Section titled “Reducing choppiness”Synthesized sine is sent over UDP, so Wi-Fi conditions can cause dropouts. To fix:
- Call
WiFi.setSleep(false)after connecting (kills ESP32 modem-sleep jitter). - Use unicast: broadcast UDP has no MAC-layer ACK / retry, so loss becomes
audible gaps.
discover()a single device and unicast to it, and the radio’s MAC ACK + retry makes it far smoother (see Discovery & targeting). - Call
pumpSine()frequently fromloop(). If heavy work stallsloop(), the device ring (~256 ms) starves and you hear gaps.