Getting Started
JavaScript / TypeScript から Hapbeat を駆動する SDK です(npm @hapbeat/sdk)。
WebXR・three.js / Babylon.js・p5.js・jsPsych 実験・Electron・Node サーバーなど向け。
起点(いつ・どこで鳴らすか)と触覚の編集(何を・どう鳴らすか)を分け、event id
だけで結びます。
1 つの API、3 つのトランスポート
Section titled “1 つの API、3 つのトランスポート”connect() は 1 つですが、実行環境に応じてトランスポートが自動で切り替わります
(パッケージの exports マップで判定)。
- Node(Electron / サーバー / CLI / クリエイティブコーディング)→ Wi-Fi UDP を直接送ります。
- React Native(Android / iOS のモバイルアプリ)→ スマホはブラウザのように
サンドボックス化されていないため、実 UDP ソケットを開けます。
react-native-udp経由で Wi-Fi UDP を直接送り、hapbeat-helper は不要です。 - Browser(WebXR / three.js / p5.js / React / jsPsych)→ ブラウザは生の UDP
ソケットを開けないため、ローカルで動く hapbeat-helper
に WebSocket(
ws://localhost:7703)で中継します。
どれでもコードは同じ(connect() → play(id))です。トランスポートの違いと制約は
Transports — Node UDP / React Native UDP / Browser helper を参照してください。
インストール
Section titled “インストール”npm install @hapbeat/sdkESM 専用("type": "module")です。Browser パスを使う場合は helper デーモンが
必要です。
pip install hapbeat-helper # 一度入れてhapbeat-helper # 起動しておくSDK 本体は npm の標準的な方法で確認・更新します。ライブラリの import 時に外部へ
問い合わせることはありません(CI やオフライン環境での副作用を避けるため)。
npm outdated @hapbeat/sdk # 新しい版が出ているか確認npm install @hapbeat/sdk@latest全ツールの最新版は Changelog にまとまっています。
最初のイベント(Node)
Section titled “最初のイベント(Node)”import { connect } from "@hapbeat/sdk";
const hb = await connect({ appName: "MyApp" }); // UDP ソケット + keep-alivehb.play("sample-kit.sine_100hz", { gain: 0.3 }); // event id で発火(gain は 0..1)hb.play("sample-kit.sine_100hz"); // gain 省略 → kit / EventMap の既定値hb.stopAll();await hb.close();connect()が UDP ソケットを開き、keep-alive(5 秒間隔の PING + アプリ名の CONNECT_STATUS)を送ってデバイス OLED にアプリ名(appName、最大 16 文字)を 表示します。送信は PING に応答したデバイスへの unicast が標準で、1 台も応答 していない間だけブロードキャストになります(詳細は Transports — Node UDP / React Native UDP / Browser helper)。play(eventId, opts)は再生指示を送る fire-and-forget な呼び出しです。gainは 0..1(SDK 側で clamp)。省略すると後述の EventMap が既定値(kit の intensity)を 補います。- 終了時は必ず
await hb.close()。アプリが離れたことをデバイスに伝え、再生中の ストリームをキャンセルします。
"sample-kit.sine_100hz" は、デバイスに配備した kit(Hapbeat Studio
で書き込み)に含まれる event id である必要があります。SDK は指示を送るだけで、
波形はデバイス上の kit にあります(command モード。波形を SDK から送る clip モードは
Command vs Clip を参照)。
最初のイベント(Browser)
Section titled “最初のイベント(Browser)”ブラウザでもコードは同じです。ただし helper が起動している必要があります
(connect() は ws://localhost:7703 に届かないと reject します)。
import { connect } from "@hapbeat/sdk";
const hb = await connect({ appName: "MyWebXR" }); // → ws://localhost:7703 (helper)hb.play("sample-kit.sine_100hz", { gain: 0.5 });バンドラーが browser ビルドを自動で選び、UDP 送信は helper が代わりに
行います。helper が落ちたときに反応するには onConnectionLost を渡します。ブラウザ
固有の制約(clip 再生は helper が知る全台に届く・targetTimeUs は無視)は
Transports — Node UDP / React Native UDP / Browser helper にまとめてあります。
React でも同じです。connect() を 1 回だけ呼び(effect やモジュールの singleton で)、
あとはイベントハンドラから hb.play(...) を呼ぶだけです。
最初のイベント(React Native)
Section titled “最初のイベント(React Native)”Android / iOS のモバイルアプリでもコードは同じで、react-native-udp 経由で
helper なしに UDP を直送します(Android 実機・RN 0.86 / Hermes で検証済み)。
const hb = await connect({ appName: "MyApp" });hb.play("sample-kit.sine_100hz", { gain: 0.5 });セットアップ(react-native-udp / fast-text-encoding の導入、metro.config.js の
resolver、Android / iOS の権限)は
Transports — Node UDP / React Native UDP / Browser helper を参照してください。
デバイスを探す
Section titled “デバイスを探す”for (const d of await hb.discover(1500)) { console.log(d.ip, d.address, d.firmwareVersion);}discover(timeoutMs = 1500) はブロードキャスト PING / PONG でデバイスを集めます
(mDNS ではありません)。
起点と編集を分ける(EventMap)
Section titled “起点と編集を分ける(EventMap)”強度などの「触覚の調整値」を発火コードに書かず、**kit manifest(= EventMap)**に
まとめます。play("id") がそこから既定値を解決します。
import { connect, EventMap } from "@hapbeat/sdk";
const manifest = await fetch("/my-kit/my-kit-manifest.json").then((r) => r.json());const hb = await connect({ eventMap: EventMap.fromManifest(manifest) });hb.play("sample-kit.sine_100hz"); // kit manifest の intensity で発火「いつ鳴らすか(コード)」と「どれくらいの強さか(kit)」を独立して差し替えられます。 詳しくは EventMap reference を参照。
ターゲットを指定する
Section titled “ターゲットを指定する”hb.play("sample-kit.sine_100hz", { target: "player_1/pos_neck" }); // 1 台hb.play("sample-kit.sine_100hz", { target: "*/pos_neck" }); // 同じ位置の全台hb.play("sample-kit.sine_100hz", { target: "*/*/group_2" }); // group 2hb.play("sample-kit.sine_100hz", { target: "" }); // 全台(既定)ターゲットの解決順は「呼び出し時の target」→「connect() の defaultTarget」。
"" は全台です。照合は位置ベースなので、group だけを指定するときは
"*/*/group_2" のように前のスロットを * で埋めます("group_2" 単独は player
スロットと比較され一致しません)。詳細は
Address の仕組み を参照。
- Transports — Node UDP / React Native UDP / Browser helper — Node(UDP)と Browser(helper WS)の違い・制約
- Command vs Clip — command と clip の使い分け(Fire と Clip の違い)
- EventMap reference — kit manifest から既定強度を解決する(Event ID と Kit)
- Project Structure — kit と clip をプロジェクトに置く構成
- Examples — 動くサンプルの歩き方