見出し画像

Audio Linkコントローラー(簡易)

AudioLinkとは

そもそもAudioLinkとはなんぞや?という話ですが、音に合わせて自動でいろいろ動かせる!という仕組みです。
例えばこんなことができます!
・ワールド内の音楽に反応してオブジェクトが出たり消えたりする
・サブ音声の音楽に反応してライトの光が点滅する
などです!
これはあくまでも例ですが、出たり消えたり、光ったりという他にも形が変わる、色が変わるといったこともできそうです!
導入方法や使い方に関しては公式よりガイドが公開されています。

この記事に書いてある通りに実行すればとりあえず動かせます。
サンプルも豊富なのでいろいろ遊べます。Boothなどにもすでに自作のAudioLink対応シェーダーを公開してる方もいます。

コントローラーを作ろう

ひととおり遊んでみた方はわかるかもしれませんが、出力する音によって反応が良かったり悪かったりしませんか?
そういう場合はUnityの設定を変更してアップロードすれば反応のしやすさなどを変更できます。

AudioLinkの設定画面

しかし、設定を変更するたびに毎回アップロードするのは不便ですね!
そこで公式ガイドでは「パラーメータ変更用スクリプト」というのも同時に公開しています。
これだと、アップロードしなくてもワールド内で設定変更が可能です。
が、この方法だと決められた数値にしか変更できません。
それならば、ワールド内で好きなように数値を変更できるコントローラーがあれば便利なのでは!?というわけでコントローラーを作りましょう!

ちなみに、AudioLinkコントローラーはパッケージにも同梱されていますが他のプラットフォーム用なのでclusterでは動きません。

AudioLinkパッケージに同梱のコントローラー
シーン内に出せますが機能しません

コントローラーの特徴

スイッチを押すと数値入力画面が出てきます。
数値を入力すると設定が変更されます。
スイッチの色やデザインは自由に変更可能です。
記事内のコードをコピーするだけなのでスクリプトがわからなくても作れます。

数値入力画面

注意事項

スクリプトはAIによって生成しています。
筆者はスクリプトについてはさっぱりわかりません💦
なので、内容についての解説はできません💦
ご意見、ご質問も受けられません💦
エラー修復のコードが含まれているようです👍

コントローラーの構造

コントローラーの構造はおおまかにこのようになっています。
スイッチに数値が入力されると、AudioLink本体とHolderに数値が出力されます。
AudioLinkでは設定が変更され、音への反応が変わります。
Holderは数値を一時保存しておくところです。ここに入った数値はテキストとして目に見える数字が表示されます。

おおまかな概念図

Unityでの全体図はこのようになります。
Gainなどのワールド内の表示をしてますが無くてもわかる人はつけなくてもOKです。

かるら改でのコントローラーと、そのHierarchy
(AudioLinkはコントローラーに組み込まない)

作成例では7項目の設定ですが、ワールドの設計によっては必要な項目だけでもOKです。

錬獄ではGain(反応)とThreshold0(閾値)のみです

AudioLink本体の設定

AudioLink本体はコントローラーとは別の階層でもOKです。
AudioLink本体に「Scriptable Item」を追加します。

以下のスクリプトをコピペします。

///<reference types="@clustervr/cluster-script-types"/>

/* ─────────────────────────────────────────────
   AudioLinkHub.js   2025-06 改訂
   ・受信メッセージ "SetAudioLinkParams"(args は連想オブジェクト)
   ・該当する setter だけ呼び出し → 最後に applySettings()
   ・タイポ修正済み(Threshold1/2/3)
   ───────────────────────────────────────────── */
const DEBUG = false;

const al = $.audioLink();
if (!al) {
  $.log("AudioLinkHub: AudioLink コンポーネントが見つかりません");
}

/* 受信設定
   { player:true, item:true } =
   他アイテム・他プレイヤーからの送信を両方受け取る */
$.onReceive((messageType, args, sender) => {
  if (messageType !== "SetAudioLinkParams") return;
  if (!al) return;

  if (DEBUG) $.log("AudioLinkHub: args=" + JSON.stringify(args));

  if (args.gain        != null) al.setGain(args.gain);
  if (args.bass        != null) al.setBass(args.bass);
  if (args.treble      != null) al.setTreble(args.treble);

  if (args.x0          != null) al.setCrossover0(args.x0);
  if (args.x1          != null) al.setCrossover1(args.x1);
  if (args.x2          != null) al.setCrossover2(args.x2);
  if (args.x3          != null) al.setCrossover3(args.x3);

  if (args.threshold0  != null) al.setThreshold0(args.threshold0);
  if (args.threshold1  != null) al.setThreshold1(args.threshold1);  // ← 修正
  if (args.threshold2  != null) al.setThreshold2(args.threshold2);  // ← 修正
  if (args.threshold3  != null) al.setThreshold3(args.threshold3);  // ← 修正

  if (args.fadeLength      != null) al.setFadeLength(args.fadeLength);
  if (args.fadeExpFalloff  != null) al.setFadeExpFalloff(args.fadeExpFalloff);

  if (args.autogainDerate != null) {
    al.setEnableAutogain(true);
    al.setAutogainDerate(args.autogainDerate);
  }

  al.applySettings();
}, { player: true, item: true });

スイッチの構造

スイッチの構造は基本的にこのようになります。
▽項目名(GainやBassなどのパラーメーター名)
  ValueHolder(数値を保管するところ。)
 ▽Canvas
    Text(数値を表示するところ)
  Input(スイッチ本体。コライダー必要。)

項目名は設定するパラーメーター名です。その中のValue HolderやInputにも他の項目と混同しないように名前をつけるとわかりやすいです。
なお、KeyやIDは書いてある通り入力しないとギミックが動きません。

Gain

全体的な反応のしやすさのパラメーターです。
調整範囲は0.0~2.0、初期値は1.0です。

GainValueHolder

空のゲームオブジェクトを作成します。 
Scriptable Item」を追加します。
以下のコードをコピペします。

///<reference types="@clustervr/cluster-script-types"/>
const MIN = 0.0;
const MAX = 2.0;
let value = 1.0;

function save(v) {
  value = +(Math.min(MAX, Math.max(MIN, v)).toFixed(2));
  $.setStateCompat("this", "gain", value);
}

$.onStart(() => save(value));

$.onReceive((msg, arg) => {
  if (msg === "SetValue") {
    const v = parseFloat(arg);
    if (!isNaN(v)) save(v);
    else $.send("DisplayError", "無効な数値(例: 1.5)");
  } else if (msg === "Reset") {
    save(1.0);
  }
});

Canvas(Text)

Textを作成するとCanvasができるのでそのまま使います。作成方法はこちら
Textに「Set Text Gimmick」を追加します。
以下のように設定します。

Textには「--」と入力。全項目共通。(好きな文字列でよい)

GainInput

CubeやQuadなどの見た目とコライダーがあるオブジェクトを作成します。
Scriptable Item」を追加します。
以下のコードをコピペします。

///<reference types="@clustervr/cluster-script-types"/>
let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("Gainholder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(player => {
  if (!holder || !audioLinkHub) fetchHandles();
  player.requestTextInput("gainInput", "Gainを入力(0.0〜2.0)");
});

$.onTextInput((text, meta) => {
  if (meta !== "gainInput") return;
  const v = parseFloat(text);
  if (isNaN(v)) {
    $.send("DisplayError", "無効な数値(例: 1.5)");
    return;
  }

  let attempts = 0;
  while ((!holder || !holder.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (holder && holder.exists()) {
    holder.send("SetValue", v.toString());
  } else {
    $.send("DisplayError", "エラー: Gain設定失敗。ワールドを再読み込み");
  }

  attempts = 0;
  while ((!audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { gain: v });
  } else {
    $.send("DisplayError", "エラー: AudioLink設定失敗。ワールドを再読み込み");
  }
});

World Item Reference List」を追加します。
以下のように設定します。

Bass

低音の反応のしやすさです。
調整範囲は0.0~2.0、初期値は1.0です。
これ以降は基本的にGainと同じなので、変更箇所だけを記載します。

BassValueHolder

///<reference types="@clustervr/cluster-script-types"/>
const MIN = 0.0;
const MAX = 2.0;
let value = 1.0;

function save(v) {
  value = +(Math.min(MAX, Math.max(MIN, v)).toFixed(2));
  $.setStateCompat("this", "bass", value);
}

$.onStart(() => save(value));

$.onReceive((msg, arg) => {
  if (msg === "SetValue") {
    const v = parseFloat(arg);
    if (!isNaN(v)) save(v);
    else $.send("DisplayError", "無効な数値(例: 1.5)");
  } else if (msg === "Reset") {
    save(1.0);
  }
});

Canvas(Text)

BassInput

///<reference types="@clustervr/cluster-script-types"/>
let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("Bassholder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(player => {
  if (!holder || !audioLinkHub) fetchHandles();
  player.requestTextInput("bassInput", "Bassを入力(0.0〜2.0)");
});

$.onTextInput((text, meta) => {
  if (meta !== "bassInput") return;
  const v = parseFloat(text);
  if (isNaN(v)) {
    $.send("DisplayError", "無効な数値(例: 1.5)");
    return;
  }

  let attempts = 0;
  while ((!holder || !holder.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (holder && holder.exists()) {
    holder.send("SetValue", v.toString());
  } else {
    $.send("DisplayError", "エラー: Bass設定失敗。ワールドを再読み込み");
  }

  attempts = 0;
  while ((!audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { bass: v });
  } else {
    $.send("DisplayError", "エラー: AudioLink設定失敗。ワールドを再読み込み");
  }
});

Treble

高音の反応のしやすさです。
調整範囲は0.0~2.0、初期値は1.0です。

TrebleValueHolder

///<reference types="@clustervr/cluster-script-types"/>
const MIN = 0.0;
const MAX = 2.0;
let value = 1.0;

function save(v) {
  value = +(Math.min(MAX, Math.max(MIN, v)).toFixed(2));
  $.setStateCompat("this", "treble", value);
}

$.onStart(() => save(value));

$.onReceive((msg, arg) => {
  if (msg === "SetValue") {
    const v = parseFloat(arg);
    if (!isNaN(v)) save(v);
    else $.send("DisplayError", "無効な数値(例: 1.5)");
  } else if (msg === "Reset") {
    save(1.0);
  }
});

Canvas(Text)

TrebleInput

///<reference types="@clustervr/cluster-script-types"/>
let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("Trebholder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(player => {
  if (!holder || !audioLinkHub) fetchHandles();
  player.requestTextInput("trebleInput", "Trebleを入力(0.0〜2.0)");
});

$.onTextInput((text, meta) => {
  if (meta !== "trebleInput") return;
  const v = parseFloat(text);
  if (isNaN(v)) {
    $.send("DisplayError", "無効な数値(例: 1.5)");
    return;
  }

  let attempts = 0;
  while ((!holder || !holder.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (holder && holder.exists()) {
    holder.send("SetValue", v.toString());
  } else {
    $.send("DisplayError", "エラー: Treble設定失敗。ワールドを再読み込み");
  }

  attempts = 0;
  while ((!audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { treble: v });
  } else {
    $.send("DisplayError", "エラー: AudioLink設定失敗。ワールドを再読み込み");
  }
});

Low

ここからの4項目はしきい値(Threshold)です。しきい値とは「この数値以上になったら反応する」という値です。
Lowは低音域のしきい値です。
パタメーター名はThreshold0です。
調整範囲は0.00~1.00、初期値は0.45です。小数点以下が1桁増えます。

T0ValueHolder

///<reference types="@clustervr/cluster-script-types"/>
const MIN = 0.0;
const MAX = 1.0;
let value = 0.45;

function save(v) {
  value = +(Math.min(MAX, Math.max(MIN, v)).toFixed(2));
  $.setStateCompat("this", "threshold0", value);
}

$.onStart(() => save(value));

$.onReceive((msg, arg) => {
  if (msg === "SetValue") {
    const v = parseFloat(arg);
    if (!isNaN(v)) save(v);
    else $.send("DisplayError", "無効な数値(例: 0.5)");
  } else if (msg === "Reset") {
    save(0.45);
  }
});

Canvas(Text)

T0Input

///<reference types="@clustervr/cluster-script-types"/>
let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("T0holder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(player => {
  if (!holder || !audioLinkHub) fetchHandles();
  player.requestTextInput("threshold0Input", "Threshold0を入力(0.0〜1.0)");
});

$.onTextInput((text, meta) => {
  if (meta !== "threshold0Input") return;
  const v = parseFloat(text);
  if (isNaN(v)) {
    $.send("DisplayError", "無効な数値(例: 0.5)");
    return;
  }

  let attempts = 0;
  while ((!holder || !holder.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (holder && holder.exists()) {
    holder.send("SetValue", v.toString());
  } else {
    $.send("DisplayError", "エラー: Threshold0設定失敗。ワールドを再読み込み");
  }

  attempts = 0;
  while ((!audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { threshold0: v });
  } else {
    $.send("DisplayError", "エラー: AudioLink設定失敗。ワールドを再読み込み");
  }
});

Low Mid

中低音域のしきい値です。
パタメーター名はThreshold1です。
調整範囲は0.00~1.00、初期値は0.45です。

T1ValueHolder

///<reference types="@clustervr/cluster-script-types"/>
const MIN = 0.0;
const MAX = 1.0;
let value = 0.45;

function save(v) {
  value = +(Math.min(MAX, Math.max(MIN, v)).toFixed(2));
  $.setStateCompat("this", "threshold1", value);
}

$.onStart(() => save(value));

$.onReceive((msg, arg) => {
  if (msg === "SetValue") {
    const v = parseFloat(arg);
    if (!isNaN(v)) save(v);
    else $.send("DisplayError", "無効な数値(例: 0.5)");
  } else if (msg === "Reset") {
    save(0.45);
  }
});

Canvas(Text)

T1Input

///<reference types="@clustervr/cluster-script-types"/>
let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("T1holder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(player => {
  if (!holder || !audioLinkHub) fetchHandles();
  player.requestTextInput("threshold1Input", "Threshold1を入力(0.0〜1.0)");
});

$.onTextInput((text, meta) => {
  if (meta !== "threshold1Input") return;
  const v = parseFloat(text);
  if (isNaN(v)) {
    $.send("DisplayError", "無効な数値(例: 0.5)");
    return;
  }

  let attempts = 0;
  while ((!holder || !holder.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (holder && holder.exists()) {
    holder.send("SetValue", v.toString());
  } else {
    $.send("DisplayError", "エラー: Threshold1設定失敗。ワールドを再読み込み");
  }

  attempts = 0;
  while ((!audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { threshold1: v });
  } else {
    $.send("DisplayError", "エラー: AudioLink設定失敗。ワールドを再読み込み");
  }
});

High Mid

中高音域のしきい値です。
パタメーター名はThreshold2です。
調整範囲は0.00~1.00、初期値は0.45です。

T2ValueHolder

///<reference types="@clustervr/cluster-script-types"/>
const MIN = 0.0;
const MAX = 1.0;
let value = 0.45;

function save(v) {
  value = +(Math.min(MAX, Math.max(MIN, v)).toFixed(2));
  $.setStateCompat("this", "threshold2", value);
}

$.onStart(() => save(value));

$.onReceive((msg, arg) => {
  if (msg === "SetValue") {
    const v = parseFloat(arg);
    if (!isNaN(v)) save(v);
    else $.send("DisplayError", "無効な数値(例: 0.5)");
  } else if (msg === "Reset") {
    save(0.45);
  }
});

Canvas(Text)

T2Input

///<reference types="@clustervr/cluster-script-types"/>
let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("T2holder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(player => {
  if (!holder || !audioLinkHub) fetchHandles();
  player.requestTextInput("threshold2Input", "Threshold2を入力(0.0〜1.0)");
});

$.onTextInput((text, meta) => {
  if (meta !== "threshold2Input") return;
  const v = parseFloat(text);
  if (isNaN(v)) {
    $.send("DisplayError", "無効な数値(例: 0.5)");
    return;
  }

  let attempts = 0;
  while ((!holder || !holder.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (holder && holder.exists()) {
    holder.send("SetValue", v.toString());
  } else {
    $.send("DisplayError", "エラー: Threshold2設定失敗。ワールドを再読み込み");
  }

  attempts = 0;
  while ((!audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { threshold2: v });
  } else {
    $.send("DisplayError", "エラー: AudioLink設定失敗。ワールドを再読み込み");
  }
});

High

高音域のしきい値です。
パタメーター名はThreshold3です。
調整範囲は0.00~1.00、初期値は0.45です。

T3ValueHolder

///<reference types="@clustervr/cluster-script-types"/>
const MIN = 0.0;
const MAX = 1.0;
let value = 0.45;

function save(v) {
  value = +(Math.min(MAX, Math.max(MIN, v)).toFixed(2));
  $.setStateCompat("this", "threshold3", value);
}

$.onStart(() => save(value));

$.onReceive((msg, arg) => {
  if (msg === "SetValue") {
    const v = parseFloat(arg);
    if (!isNaN(v)) save(v);
    else $.send("DisplayError", "無効な数値(例: 0.5)");
  } else if (msg === "Reset") {
    save(0.45);
  }
});

Canvas(Text)

T3Input

///<reference types="@clustervr/cluster-script-types"/>
let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("T3holder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(player => {
  if (!holder || !audioLinkHub) fetchHandles();
  player.requestTextInput("threshold3Input", "Threshold3を入力(0.0〜1.0)");
});

$.onTextInput((text, meta) => {
  if (meta !== "threshold3Input") return;
  const v = parseFloat(text);
  if (isNaN(v)) {
    $.send("DisplayError", "無効な数値(例: 0.5)");
    return;
  }

  let attempts = 0;
  while ((!holder || !holder.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (holder && holder.exists()) {
    holder.send("SetValue", v.toString());
  } else {
    $.send("DisplayError", "エラー: Threshold3設定失敗。ワールドを再読み込み");
  }

  attempts = 0;
  while ((!audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }
  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { threshold3: v });
  } else {
    $.send("DisplayError", "エラー: AudioLink設定失敗。ワールドを再読み込み");
  }
});

さらに機能追加

ここまで作ったものでも十分機能はしますが、もうひとつ機能を追加してもいいでしょう。それはResetです!押すと最初の値にする、というものです。
作り方はInputボタンをコピーして、スクリプトを書き換えるだけです。

Gain(規定値1.0)

///<reference types="@clustervr/cluster-script-types"/>
const RESET_VALUE = 1.0;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("Gainholder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(() => {
  let attempts = 0;
  while ((!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }

  if (holder && holder.exists()) {
    holder.send("Reset", null);
  } else {
    $.send("DisplayError", "エラー: リセット失敗。ワールドを再読み込み");
  }

  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { gain: RESET_VALUE });
  } else {
    $.send("DisplayError", "エラー: AudioLinkリセット失敗。ワールドを再読み込み");
  }
});

Bass(規定値1.0)

///<reference types="@clustervr/cluster-script-types"/>
const RESET_VALUE = 1.0;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("Bassholder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(() => {
  let attempts = 0;
  while ((!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }

  if (holder && holder.exists()) {
    holder.send("Reset", null);
  } else {
    $.send("DisplayError", "エラー: リセット失敗。ワールドを再読み込み");
  }

  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { bass: RESET_VALUE });
  } else {
    $.send("DisplayError", "エラー: AudioLinkリセット失敗。ワールドを再読み込み");
  }
});

Treble(規定値1.0)

///<reference types="@clustervr/cluster-script-types"/>
const RESET_VALUE = 1.0;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("Trebholder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(() => {
  let attempts = 0;
  while ((!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }

  if (holder && holder.exists()) {
    holder.send("Reset", null);
  } else {
    $.send("DisplayError", "エラー: リセット失敗。ワールドを再読み込み");
  }

  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { treble: RESET_VALUE });
  } else {
    $.send("DisplayError", "エラー: AudioLinkリセット失敗。ワールドを再読み込み");
  }
});

Low(規定値0.45)

///<reference types="@clustervr/cluster-script-types"/>
const RESET_VALUE = 0.45;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("T0holder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(() => {
  let attempts = 0;
  while ((!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }

  if (holder && holder.exists()) {
    holder.send("Reset", null);
  } else {
    $.send("DisplayError", "エラー: リセット失敗。ワールドを再読み込み");
  }

  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { threshold0: RESET_VALUE });
  } else {
    $.send("DisplayError", "エラー: AudioLinkリセット失敗。ワールドを再読み込み");
  }
});

Low Mid(規定値0.45)

///<reference types="@clustervr/cluster-script-types"/>
const RESET_VALUE = 0.45;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("T1holder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(() => {
  let attempts = 0;
  while ((!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }

  if (holder && holder.exists()) {
    holder.send("Reset", null);
  } else {
    $.send("DisplayError", "エラー: リセット失敗。ワールドを再読み込み");
  }

  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { threshold1: RESET_VALUE });
  } else {
    $.send("DisplayError", "エラー: AudioLinkリセット失敗。ワールドを再読み込み");
  }
});

High Mid(規定値0.45)

///<reference types="@clustervr/cluster-script-types"/>
const RESET_VALUE = 0.45;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("T2holder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(() => {
  let attempts = 0;
  while ((!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }

  if (holder && holder.exists()) {
    holder.send("Reset", null);
  } else {
    $.send("DisplayError", "エラー: リセット失敗。ワールドを再読み込み");
  }

  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { threshold2: RESET_VALUE });
  } else {
    $.send("DisplayError", "エラー: AudioLinkリセット失敗。ワールドを再読み込み");
  }
});

High(規定値0.45)

///<reference types="@clustervr/cluster-script-types"/>
const RESET_VALUE = 0.45;
const FETCH_INTERVAL = 10000;
const MAX_ATTEMPTS = 3;

let holder = null;
let audioLinkHub = null;
let lastFetchAttempt = 0;

function fetchHandles() {
  const now = Date.now();
  if (now - lastFetchAttempt < 1000) return;
  lastFetchAttempt = now;
  if (!holder || !holder.exists()) holder = $.worldItemReference("T3holder");
  if (!audioLinkHub || !audioLinkHub.exists()) audioLinkHub = $.worldItemReference("audioLink");
}

$.onStart(() => {
  fetchHandles();
  $.setInterval(() => {
    if (!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) fetchHandles();
  }, FETCH_INTERVAL);
});

$.onInteract(() => {
  let attempts = 0;
  while ((!holder || !holder.exists() || !audioLinkHub || !audioLinkHub.exists()) && attempts < MAX_ATTEMPTS) {
    fetchHandles();
    attempts++;
  }

  if (holder && holder.exists()) {
    holder.send("Reset", null);
  } else {
    $.send("DisplayError", "エラー: リセット失敗。ワールドを再読み込み");
  }

  if (audioLinkHub && audioLinkHub.exists()) {
    audioLinkHub.send("SetAudioLinkParams", { threshold3: RESET_VALUE });
  } else {
    $.send("DisplayError", "エラー: AudioLinkリセット失敗。ワールドを再読み込み");
  }
});

AudioLinkコントローラーがあるとワールドを更新しなくても、音を出しながらその場で調整できちゃいます!
コントローラーの形や色も好きなように作りましょう!
ばっちりな調整でAudioLinkを楽しみましょう!

ちなみに技術や知識次第ではもっといいコントローラーが作れると思います!作ったら筆者にもこっそり教えてください(笑)

<<前の記事
アバターだけが映るミラー

実験場解説トップページへ


いいなと思ったら応援しよう!