コンテンツにスキップ
JA

Project Structure

kit(Studio が作る内容)とアプリのコードを分けると、コードは event id を play するだけになります。この分担をどうファイルに落とすか、web と node それぞれの 構成を示します。

何を持つ誰が作る
kit manifest<kit>-manifest.jsonkit の内容: intensity / clip / command か clip かHapbeat Studio が自動生成
アプリのコードいつ・どこで発火するか(play(id) / target開発者が書く

manifest は「kit の中身の記述」であり、どの端末/部位に出すか(target)は持ちません。 発火タイミングと target はアプリの都合なので、コード側で決めます。詳しくは EventMap reference を参照。

JS には Python の触覚ファイル overlay は無い

Section titled “JS には Python の触覚ファイル overlay は無い”

Python SDK には haptics.json(kit を参照して target/gain を上乗せする overlay)が ありますが、JS SDK にはありません。JS の EventMapmanifest 専用で、 各イベントの intensity / clip を運ぶだけです。target の決め方は次の 2 つです。

  • 呼び出し側で指定: hb.play("sample-kit.sine_100hz", { target: "player_1/chest" })
  • 既定値: connect({ defaultTarget: "*/chest" })(呼び出し側 target が優先)

target を省略すると ""(全台ブロードキャスト)になります。

web レイアウト(静的配信 + fetch)

Section titled “web レイアウト(静的配信 + fetch)”

manifest と clip WAV を静的アセットとして配信し、clipBase を URL prefix として fetch します。ブラウザ既定の clipLoaderfetch です。

my-web-app/
├── src/
│ └── main.ts ← 呼ぶ側(connect() + play(id))
└── public/
└── my-kit/
├── my-kit-manifest.json ← Studio 生成。fetch → EventMap.fromManifest
└── stream-clips/
└── rain.wav ← clip モードで SDK がストリームする WAV(16 kHz PCM16)
import { connect, EventMap } from "@hapbeat/sdk";
const manifest = await fetch("/my-kit/my-kit-manifest.json").then((r) => r.json());
const hb = await connect({
appName: "MyWebXR",
eventMap: EventMap.fromManifest(manifest),
clipBase: "/my-kit/stream-clips/", // clip 参照は clipBase + manifest 内のファイル名
});
hb.play("sample-kit.sine_100hz"); // command モード(デバイス上のクリップを再生)
hb.play("rain.loop"); // clip モード(public 配下の WAV を fetch してストリーム)

clip 参照は clipBase + <manifest の clip フィールド> の単純連結で解決します。 clipBase の末尾スラッシュに注意してください。

node レイアウト(ディレクトリ + fs.readFile)

Section titled “node レイアウト(ディレクトリ + fs.readFile)”

clipBaseディレクトリパスです。node 既定の clipLoaderfs.readFile で、 ディスクから WAV を読み込みます。

my-node-app/
├── index.mjs ← 呼ぶ側(connect() + play(id))
└── kits/
└── my-kit/
├── my-kit-manifest.json ← Studio 生成
└── stream-clips/
└── rain.wav ← clip モード WAV(16 kHz PCM16)
import { readFile } from "node:fs/promises";
import { connect, EventMap } from "@hapbeat/sdk";
const manifest = JSON.parse(
await readFile("kits/my-kit/my-kit-manifest.json", "utf8"),
);
const hb = await connect({
appName: "MyApp",
eventMap: EventMap.fromManifest(manifest),
clipBase: "kits/my-kit/stream-clips/", // ディレクトリパス
});
hb.play("sample-kit.sine_100hz"); // command モード
hb.play("rain.loop"); // clip モード(kits 配下の WAV を fs.readFile でストリーム)

clipBase + 既定ローダーで足りない場合(バンドル済みアセット・IndexedDB・CDN など)は clipLoader を差し替えます。clipBase + def.clip を引数 ref として受け取り、 ArrayBuffer | Uint8Array を返す関数です。

// IndexedDB から clip を読む例(Browser)
const hb = await connect({
eventMap: EventMap.fromManifest(manifest),
clipBase: "", // キー前置が不要なら空でよい
clipLoader: async (ref) => {
const buf = await idbGet(ref); // ref は clipBase + 'rain.wav' 等
return buf; // ArrayBuffer
},
});
// バンドラ経由でインポートした WAV を使う例
import rainWavUrl from "./kits/my-kit/stream-clips/rain.wav?url";
const hb = await connect({
eventMap: EventMap.fromManifest(manifest),
clipBase: "",
clipLoader: async (ref) => {
const url = ref === "rain.wav" ? rainWavUrl : ref;
return fetch(url).then((r) => r.arrayBuffer());
},
});

clip モードの WAV は 16 kHz PCM16 が必要です。SDK はリサンプルしません(非 16 kHz は 警告)。clip モードの詳細は Command vs Clip を参照。

  1. Hapbeat Studio で kit を編集(クリップ・強度・command/clip)。
  2. kit フォルダ(manifest + stream-clips/)をアプリに置く(web: public/、node: 任意の dir)。
  3. manifest を fetch(web)/ readFile(node)で読み、EventMap.fromManifest(manifest)connect に渡す。
  4. clip モードを使うなら clipBase を WAV の置き場所(URL prefix / dir)に向ける。
  5. コードは hb.play("event.id") を呼ぶだけ。target は呼び出し側か defaultTarget で。