コンテンツにスキップ
JA

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-helperWebSocketws://localhost:7703)で中継します。

どれでもコードは同じ(connect()play(id))です。トランスポートの違いと制約は Transports — Node UDP / React Native UDP / Browser helper を参照してください。

npm install @hapbeat/sdk

ESM 専用("type": "module")です。Browser パスを使う場合は helper デーモンが 必要です。

pip install hapbeat-helper # 一度入れて
hapbeat-helper # 起動しておく

SDK 本体は npm の標準的な方法で確認・更新します。ライブラリの import 時に外部へ 問い合わせることはありません(CI やオフライン環境での副作用を避けるため)。

npm outdated @hapbeat/sdk # 新しい版が出ているか確認
npm install @hapbeat/sdk@latest

全ツールの最新版は Changelog にまとまっています。

import { connect } from "@hapbeat/sdk";
const hb = await connect({ appName: "MyApp" }); // UDP ソケット + keep-alive
hb.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" は、デバイスに配備した kitHapbeat Studio で書き込み)に含まれる event id である必要があります。SDK は指示を送るだけで、 波形はデバイス上の kit にあります(command モード。波形を SDK から送る clip モードは Command vs Clip を参照)。

ブラウザでもコードは同じです。ただし 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(...) を呼ぶだけです。

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 を参照してください。

for (const d of await hb.discover(1500)) {
console.log(d.ip, d.address, d.firmwareVersion);
}

discover(timeoutMs = 1500) はブロードキャスト PING / PONG でデバイスを集めます (mDNS ではありません)。

強度などの「触覚の調整値」を発火コードに書かず、**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 を参照。

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 2
hb.play("sample-kit.sine_100hz", { target: "" }); // 全台(既定)

ターゲットの解決順は「呼び出し時の target」→「connect()defaultTarget」。 "" は全台です。照合は位置ベースなので、group だけを指定するときは "*/*/group_2" のように前のスロットを * で埋めます("group_2" 単独は player スロットと比較され一致しません)。詳細は Address の仕組み を参照。