UE4 サウンドデータ再生とAudioComponentについて

# 概要
UnrealEngine のサウンドデータ再生とオーディオコンポーネントについてのメモ書きです。

## 修正履歴
|日付|内容|
|:---------|:-------|
|2019/09/18| SpawnSound2Dについて追記|
|2019/10/02| PlaySoundAtLocationについて修正|
|2019/10/21| wave以外のサウンドデータについて追記|
|2020/01/15| サウンドデータのリファレンスが残る場合の対処方法を追記 |

## 環境
Windows10
Visual Studio 2017
UnrealEngine 4.22

## 参考
以下を参考にさせて頂きました、ありがとうございます。

[公式 - Audioコンポーネント](https://docs.unrealengine.com/ja/Engine/Components/Audio/index.html)
[公式 - UAudioComponent](https://docs.unrealengine.com/en-US/API/Runtime/Engine/Components/UAudioComponent/index.html)
[UE4 Saitama 初心者向けハンズオン #7『サウンド機能』](https://www.slideshare.net/YuukiOgino/ue4-saitama-7)
[UE4 Audio Listener周りの備忘録](http://mozpaca.hatenablog.com/entry/20180225/1519521954)
[UE4のリスナーのOverrideの実験](https://www.plus-signal.com/single-post/2017/04/13/UE4%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%8A%E3%83%BC%E3%81%AEOverride%E3%81%AE%E5%AE%9F%E9%A8%93)


# サウンドデータアセット
再生する対象は主に以下2種です。

## サウンドウェーブ
サウンドファイルをコンバートしたアセット。サンプリングレートは44.1kHz以下が必要です。
ループ設定やストリーミング設定は[詳細]から行います。

![SoundWaveDetail.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/236be4e6-c2a3-3927-a9b1-32e52884ec98.jpeg)

waveファイル自体にループポイント設定をしても反映はされないようです。
24bitや32bitのハイレゾ音源も不可だと思われます。


### wave以外のサウンドデータについて
4.22から[AIFF], [FLAC], [Ogg Vorbis]も対応フォーマットとして追加されているようです。
- Aiff 2ch 16bit 8kHz
- Flac 1ch 16bit 8kHz 
の2種を試してみましたが、特に問題なくUEエディタでサウンドウェーブアセットにコンバート＆エディタにて再生できました。


## サウンドキュー
サウンドデータに対し、合成やエフェクトやプロパティの変更をかけたものです。

![SoundCue.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/65aa2a7e-f70b-a9bb-837a-8247ff360ae0.jpeg)


# サウンドデータ再生
サウンドの再生メソッドのまとめです。

## PlaySound2D
2Dサウンドの再生、3D座標の影響はありません。

![PlaySound2D.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/cce225e8-9000-5521-86b3-f3ea65ac4c5e.png)


C++でサウンドウェーブを再生するテストコードです。
サウンドキューの場合も基底の USoundBaseクラスで受けているので同じコードになります。

```cpp:ATestActor.h
UPROPERTY()
USoundBase* Sound_Obj;
```

```cpp:ATestActor.cpp
// コンストラクタ
// se_ok というサウンドウェーブオブジェクトを探す
static ConstructorHelpers::FObjectFinder< USoundBase > find_sound(TEXT("SoundWave'/Game/Sound/se_ok.se_ok'"));
	if (find_sound.Succeeded()) {
		Sound_Obj = find_sound.Object;
	}

...

// 再生(7番目の引き数でオーナーアクターを自分に)
UGameplayStatics::PlaySound2D(GetWorld(), Sound_Obj, 1.0f, 1.0f, 0.0f, nullptr, this);
```



## SpawnSound2D / CreateSound2D
2Dサウンドの再生、返り値にUAudioComponentを返します。
Createのほうは自動で再生しないため、AudioComponentでPlayする必要があります。

![SpwanSound2D.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/07c12617-3cff-a458-03fa-fdd7f4887dc6.png)


```cpp:.cpp
// 再生（8番目の引き数がtrueで自動削除）
UGameplayStatics::SpawnSound2D(GetWorld(), SoundObject, 1.0f, 1.0f, 0.0f, nullptr, false, false);
```

自動削除したオーディオコンポーネントに対しアクセスを行うとクラッシュするので注意が必要です。


## PlaySoundAtLocation / SpawnSoundAtLocation
3Dサウンドの再生、音源の座標を指定する必要があります。音源の移動はしません。

![SpawnSoundatLocation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/4d2df115-65e2-aae3-7c3d-ec80d114a502.png)


C++でサウンドウェーブを再生するテストコードです。
サウンドウェーブの場合も基底の USoundBaseクラスで受けているので同じコードになります。

```cpp:ATestActor.h
UPROPERTY()
USoundBase* Sound_Obj;
```

```cpp:ATestActor.cpp
// コンストラクタ
// SoundCue_Test というサウンドキューオブジェクトを探す
static ConstructorHelpers::FObjectFinder< USoundBase > find_sound(TEXT("SoundCue'/Game/Sound/SoundCue_Test.SoundCue_Test'"));
	if (find_sound.Succeeded()) {
		Sound_Obj = find_sound.Object;
	}

...

// 再生
UGameplayStatics::PlaySoundAtLocation(GetWorld(), Sound_Obj, GetActorLocation());

// オーディオコンポーネントが必要な場合はこちら
// UAudioComponent* _Audio = UGameplayStatics::SpawnSoundAtLocation(GetWorld(), Sound_Obj, GetActorLocation());
```

## SpawnSoundAttached
3Dサウンドの再生、アタッチするコンポーネントを指定する必要があります。
アタッチ後は音源位置がコンポーネント位置に更新されるようです。

![SpawnSoundAttached.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/1ae0d4d7-153d-ac07-c52f-ef363a5f3abe.png)


```cpp:GameplayStatics.h
static class UAudioComponent* SpawnSoundAttached(
	USoundBase* Sound,
	USceneComponent* AttachToComponent,
	FName AttachPointName,
	FVector Location,
	EAttachLocation::Type LocationType =EAttachLocation::KeepRelativeOffset,
	bool bStopWhenAttachedToDestroyed = false,
	float VolumeMultiplier = 1.f,
	float PitchMultiplier = 1.f,
	float StartTime = 0.f,
	USoundAttenuation* AttenuationSettings = nullptr,
	USoundConcurrency* ConcurrencySettings = nullptr,
	bool bAutoDestroy = true);
```



# オーディオコンポーネント
サウンドデータを１つ再生管理できます。
途中で別のサウンドに差し替え（SetSound）もできますが、その場合前のサウンドは停止します。

## コンポーネント設定詳細
![Detail.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/b2cf2e15-761e-e799-ddf8-bbceaf699cdd.png)

詳細にデフォルトサウンドアセットを設定できますが、スポーン時に再生されてしまいます。それを回避するには[Auto Activate]を false にして、任意のタイミングでオーディオコンポーネントをアクティベートすればできます。


## メソッド
オーディオコンポーネントの代表的なメソッドです。C++でのコードもアセットの指定以外ほとんどそのままです。

### Play
サウンドを再生します。引き数でディレイ時間を指定できます。
![Play.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/7608fa05-c901-21eb-97b2-404864efa5b4.png)

```cpp:.cpp
// サウンド再生
AudioComponent->Play();
```

### Stop
サウンドを停止します。
![Stop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/cf4aae3b-4cfc-ad9b-e80a-b48f3e160d9a.png)

```cpp:.cpp
// サウンド停止
AudioComponent->Stop();
```

### IsPlaying
サウンドが再生中か調べます。
![IsPlaying.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/c1213988-a618-6de8-3685-69ef82b770ea.png)

```cpp:.cpp
if(AudioComponent->IsPlaying()){
	// 再生中
}
```

### SetSound
オーディオコンポーネントのサウンドアセットを差し替えます。

![SetSound.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/a0fa119b-56aa-93b0-0e04-e246850d10c1.png)

```cpp:.h
UPROPERTY()
USoundBase* SoundObject;
```

```cpp:.cpp
// コンストラクタ
static ConstructorHelpers::FObjectFinder< USoundBase > find_sound(TEXT("SoundWave'/Game/Sound/test_se.test_se'"));
if (find_sound.Succeeded()) {
	SoundObject = find_sound.Object;
}

...

// サウンドアセット差し替え
AudioComponent->SetSound(SoundObject);
```

### FadeIn / FadeOut
フェードの設定です。Play()/Stop()代わりに使用することができます。

![FadeInOut.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/0e422893-670a-3bf3-2233-3a586d5e5ce2.png)


### SetVolumeMultiplier
ボリュームの変更です。現在のボリューム値への掛け算で変更します。

![SetVolumeMaltiplier.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/5af0c16b-8ed2-9a38-79c8-c625eb534ee5.png)


### AdjustVolume
ボリュームの変更です。引き数で変化する時間を設定できます。

![AdjustVolume.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/6bb5d38a-a238-f78e-43d8-c7a7e8ea4a47.png)

```cpp:.cpp
// 1.0秒かけてボリューム値0へ変更
AudioComponent->AdjustVolume(1.0f, 0.0f);
```

### SetPitchMultiplier
ピッチの変更です、現在のピッチへの乗算値で変更します。

![SetPitchMultiplier.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/d433747f-8f60-39b9-ff5e-1ba3fe5dbabc.png)

```cpp:.cpp
// ピッチの変更
AudioComponent->SetPitchMultiplier(0.5f);
```


## イベント
### OnAudioFinished
オーディオ再生を最後まで再生したか、停止した時に呼ばれるイベント

![OnAudioFinished.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/93e88533-fe80-9a6e-444a-7c8c4dfd520b.png)

C++ではNative用のデリゲートが用意されています。
以下サンプルコード。

```cpp:.h
UFUNCTION()
		void TestFinished(UAudioComponent* _AudioComponent){ UE_LOG(LogTemp, Log, TEXT("TestFinished!")); }
```

```cpp:.cpp
// 取得したオーディオコンポーネントに対しイベントを登録
AudioComponent->OnAudioFinishedNative.AddUFunction(this, "TestFinished");
```
再生終了時に[TestFinished!]がアウトプットログに出力されます。


### OnAudioPlaybackPercent
サウンドが再生されている間ずっと呼ばれるイベント
サウンドの再生時間を取得できます。

![OnAudioPlaybackPercent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/dc4634af-e709-7a03-faa6-714c80c6d38c.png)

こちらもC++ではNative用のデリゲートが用意されています。
以下サンプルコード。

```cpp:.h
UFUNCTION()
		void TestPercent(UAudioComponent* _AudioComponent, const class USoundWave* _SoundWave, const float _Per){
		UE_LOG(LogTemp, Log, TEXT("%f"), _Per);
	}
```

```cpp:.cpp
// 取得したオーディオコンポーネントに対しイベントを登録
AudioComponent->OnAudioPlaybackPercentNative.AddUFunction(this, "TestPercent");
```

これで再生中にアウトプットログに再生時間が出力されます。


### OnAudioSingleEnvelopeValue
サウンドのエンベロープ値（ボリューム値）を取得するイベント
サウンドキューの[Enveloper]適用時？

![OnAudioSingleEnvelopeValue.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/2aeb4353-ecee-d0f8-0252-f17c9ed671d5.png)


### OnAudioMultiEnvelopeValue
サウンドのエンベロープ値（ボリューム値）を取得するイベント
サウンドキューの[Enveloper]適用時？

![OnAudioMultiEnvelopeValue.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/8d3948a9-e836-a2ff-9097-6e4a9d62b7b0.png)


### PhysicsVolumeChanged

PhysicsVolumeが変更されたときに呼ばれるイベント
変更後のボリューム値が取得できます。

![PhysicsVolumeChanged.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/3944eb36-9839-00be-d79a-5422b93869d0.png)


### OnComponentActivated
AudioComponentがアクティブになった時に呼ばれるイベント

![OnComponentActivated.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/f12c3aee-36b3-166b-027e-59e02fb53a15.png)


### OnComponentDeactivated
AudioComponentが非アクティブになった時に呼ばれるイベント

![OnComponentDeactivated.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/03664923-d42d-3156-15ff-e29ac1054da8.png)


## C++でのアクターへのオーディオコンポーネントの追加
以下の様に追加できます。
細かい設定（サウンドクラスやサウンド減衰アセット）の指定がアセットファイルなため面倒です。

```cpp:.h

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Test")
	class UAudioComponent* AudioComponent;
```

```cpp:.cpp
// コンストラクタ
AudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("AudioComponent0"));
if (AudioComponent) {
	AudioComponent->SetupAttachment(RootComponent);
	AudioComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
	AudioComponent->SetSound(NewSound);
}
```


# アニメ―ションシーケンスでのサウンド再生について

## AnimNotifySound
アニメ―ションシーケンスに対し、直接サウンドを再生する通知を追加できます。
[通知を追加] -> [Play Sound]
![AddAnimNotifySound.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/5bef76e2-882f-ddcb-6287-d7d61dc70d45.jpeg)

このようにアニメ―ショントラックに通知が追加されています。
![AnimNotifySound.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/8eec319e-a9a4-d1f2-b098-bb9528d7965c.png)
サウンド再生のタイミングはアニメ―ションに紐づくものも多いのでこれで十分な場合も多いと思います。
また、アニメ―ションモンタージュにも通知の追加が可能です。

## State版のAnimNotifySoundを作る場合の注意点

AnimNotifyStateのSound版はver.4.22時点は存在しませんが、UAnimNotifyState_TimedParticleEffectクラスを参考に作成することは可能だと思われます。
ただ、AnimNotifyクラスやAnimNotifyStateクラスはインスタンス毎に作成されるわけではなく、１つのオブジェクトを使いまわしするため、メンバ変数にそのまま情報を持つと別の通知で参照を行ったり書き換えたりするとバグります。
別途メンバに持たせる情報を管理するプールを用意するなどしないとなりませんので手間です。


# 使用しなくなったサウンドデータのリファレンスが残る場合の対処方法
サウンドデータの右クリックでの[RefarenceViewer]を見ると、既に使用しなくなったサウンドデータへの参照が残る場合があります。その場合の対処方法として、

1. 対象のBPのサウンド再生ノードを削除しコンパイル（保存はしない）
1. アンドゥをして削除したサウンドノードを戻して再度コンパイルして保存

を行うと参照が再度修正され、未使用のサウンドデータの参照が消えます。


# まとめ
ループサウンドはアクターがAudioComponentを持って再生,停止を管理し、短いワンショットサウンドはSpawnSound等で再生し、長いワンショットサウンドはケースにより管理するかどうかを決める必要があると思います。

再生サウンド全体を一括管理するようなものはないんですかね。。:thinking: