CRIを使ったピッチ変更実装のハマりポイント

[グレンジ Advent Calendar 2018](https://qiita.com/advent-calendar/2018/grenge) 19日目の記事を担当しました。s_ebataと申します。
グレンジでUnityのクライアントエンジニアをやっております。

# 概要

[CRI ADX2](https://game.criware.jp/products/adx2-smartphone/)を導入したプロジェクトで「速度に合わせてピッチを変更したい」という要件を実装した時にハマった問題を中心に、どういう解決方法に至ったかを書いていきます。

## 定義の統一
ピッチとツールという言葉が大量に出てきますので、最初に定義を統一させてください。

用語|内容
---|---
Unityのピッチ| **UnityのAudio SourceのPitch** を指します
CRIのピッチ| **CriAtomSourceのPitch** を指します
ツール| **CRI Atom Craft** を指します

# CRIのピッチとUnityのピッチは単位が違う

まず最初にハマった（というか困惑した）仕様の違いです。
以下、CRIテクニカルサポート問い合わせに対する返信を一部引用したものです。

> CriAtomSource と UnityのAudioSource で、指定するピッチの値の単位が異なります。
> CriAtomSource ではピッチを **cent単位** で指定します。
>  **100centが半音** なので、 **1200centで1オクターブ** 上がります。
> UnityのAudioSourceにおけるピッチは **再生速度** を示します。
>  **2.0** と入れれば **1オクターブ** 上がります。

## Unityのピッチは再生速度？

どうやら本当にそういう実装のようです。
[【Unity】AudioSource の Pitch にマイナス値を設定すると逆再生できる](http://nn-hokuson.hatenablog.com/entry/2016/09/05/212448)
> AudioSource の Pitch に **マイナス値** を設定すると **逆再生** できます

[テンポとピッチの関係](http://nn-hokuson.hatenablog.com/entry/2016/09/05/212448)
> テンポが **2倍** になると、ピッチは **１オクターブ** あがります。

## UnityのピッチからCRIのピッチへ変換

以下計算式を使うことで変換が可能です。
変数ratioはUnityのピッチです

```csharp
float criPitch = 1200f * Mathf.Log(ratio) / Mathf.Log(2.0f);
```

## CRIが表現できるピッチの有効範囲

CRIが表現できるピッチの有効範囲はUnityのピッチでいうと **0.125〜8.0** です。
ただし、Audio Sourceに設定できるピッチ上限は3.0なので、 **3.0以上は無効** になると思われます。

# ここからが本題

ここまでが基本知識です。
ここからは上記を踏まえた上で **ハマった問題** とその **解決方法** になります。

## 広い範囲のピッチ変動はスクリプトだけで完結できない
いざやってみたものの、ハマった問題がこれです。
CRIのPitchプロパティはスクリプトから設定できる値の範囲が **-1200〜1200** となっています。
これはUnityのピッチで言う **0.5〜2.0** の範囲です。（-1オクターブ〜+1オクターブ）
つまりUnityのピッチで言う **0.25** や **3.0** を鳴らしたい場合は、Pitchプロパティの設定だけだと **不十分** という事です。
自分が対応したピッチ実装はUnityのピッチで言う **0.125〜1.0** の変動を鳴らす必要がありました。
以下、解決方法です。

## 広い範囲のピッチ変動を扱いたい場合

表現したい範囲にあわせてツール側でパラメータを設定する必要があります。
その上でスクリプト側も同じく追加で設定する項目があります。
設定項目については後述します。

Unityのピッチ（再生速度）|表現したいCRIのピッチ（Cent）|必要な設定項目
:---:|:---:|:---:
0.5〜2.0|-1200〜1200|①CRIのピッチ
0.25〜4.0|-2400〜2400|②ツールのAISAC
0.125〜8.0|-3600〜3600|③ピッチ+AISAC

ひとつずつ見ていきます。

### ①CRIのピッチ
最も簡単なケース。スクリプトのみで完結しますが、表現できる範囲はUnityのピッチで言う **0.5〜2.0** です。

```csharp:CriPitchTest.cs
private CriAtomSource _source;

// 中略

private void SetPitch(float value)
{
  _source.Pitch = value;
}
```

### ②ツールのAISAC

ツールのAISACという機能を使い、それに対応したスクリプトを記述することで表現する範囲を広げられます。
表現できる範囲はUnityのピッチで言う **0.25〜4.0** です。

#### AISAC（アイザック）とは？

[AISAC](http://blog.cri-mw.co.jp/adx2portal/glossary/#aisac)
> Advanced Interactive Sound and Active Controllerの略
AISACコントロール値に対応するボリュームやピッチ、フィルタ等のカーブを作成して **音色を変化** させたりする機能。
例えば、あるキャラクターとの距離をコントロール値とし、その距離に応じたキャラクタのセリフ音量のカーブを描いておくことで、距離に応じた音量をデザインすることができます。
他のミドルウェアではRTPC(Real Time Parameter Control)とも呼ばれる。

自分はピッチ以外で使ったことはないのですが、音に変化をつけるための機能程度に認識しています。
公式の動画が一番わかりやすいです。
0:15〜0:30まで観ればだいたいの機能が把握できるかと思います。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">４．状況に応じて変化する音の作成２（例：リッチなヘリコプター音） <a href="https://t.co/WqjJweQMbm">https://t.co/WqjJweQMbm</a> <a href="https://twitter.com/YouTube?ref_src=twsrc%5Etfw">@YouTube</a>さんから</p>&mdash; beejoe (@beejoe718) <a href="https://twitter.com/beejoe718/status/1074837208698253312?ref_src=twsrc%5Etfw">2018年12月18日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

AISACの説明はここまでにして、実際にCRI Atom CraftのAISACを設定します。


#### ⅰ. AISACを追加

新規オブジェクト→AISACの追加を選択します。
![2018y12m17d_105421144.jpg](https://qiita-image-store.s3.amazonaws.com/0/218179/d59daa69-31e6-2ab2-4097-8387d4f59a83.jpeg)

AISACグラフタイプをピッチに設定して追加ボタンをクリックします。
![2018y12m17d_105524623.jpg](https://qiita-image-store.s3.amazonaws.com/0/218179/e58b77cb-20fb-6d00-1eea-1c7b2f259b0c.jpeg)

#### ⅱ. コントロールのグラフを描く

**0.0** がピッチ **-2400** に、 **1.0** がピッチ **2400** になるようにグラフを調整します。（右肩上がりの直線になります）
右パネルからも数値入力できます。
![2018y12m17d_143300047.jpg](https://qiita-image-store.s3.amazonaws.com/0/218179/7e322c5e-b28f-f11d-6f6f-4542e251a0da.jpeg)

これでツール側の設定は終わりです。（書き出し手順は割愛させていただきます）

#### ⅲ. スクリプト側でAISACを動的に変更

SetAisacControlという関数を使います。
[公式のドキュメント](https://game.criware.jp/manual/unity_plugin/jpn/contents/classCriAtomSource.html#acd45ffdcbfde395a9279808d37c8b610)によると、第一引数にcontrolNameとして文字列を渡すことも可能なようです。（Any設定なら"Any"を渡せば動くはず）
今回は第一引数にcontrolIdを渡すコードを記載します。

```csharp:CriPitchAisacTest.cs
private CriAtomSource _source;

// 中略

// controlIdはツールから書き出した際に吐き出されるcsファイルに定義された列挙体から指定可能
// Any設定なら0でもいけるはず
private void SetPitch(uint controlId, float value)
{
  _source.SetAisacControl(controlId, value);
}
```

### ③ピッチ+AISAC

①と②の合わせ技です。
自分のケースはこちらに当てはまります。
表現できる範囲はUnityのピッチで言う **0.125〜8.0** です。

## まとめ

必要としている機能の下調べをしないと痛い目を見るという典型でした。
CRIはテクニカルサポートに問い合わせれば丁寧な回答を頂けるので助かりました。

なお、CRI ADX2を導入する場合はグレンジ Advent Calendar 4日目の「[UnityでCRIを使う覚悟はいいか？オレはできてる](https://qiita.com/shirasaya0201/items/53baa38c83eebaa2cfbf)」が最速で導入できるヒントになるかと思いますので、あわせてよろしくおねがいします。