Last active
September 14, 2023 12:13
-
-
Save Yamayamada0924/932689cab9f2c52032016eacf29ab700 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using UnityEngine; | |
namespace InteractiveMusic | |
{ | |
/// <summary> | |
/// # VerticalMixMusic | |
/// WebGLで縦の遷移を行うためのComponent | |
/// | |
/// こちらの方が後発で上位互換です -> https://gist.github.com/Yamayamada0924/d3fe20835d2e6bb696c82007b5e6de26 | |
/// | |
/// ## 準備 | |
/// musicClipsにAudioClipを設定する | |
/// 縦の遷移を行う場合はsizeを2以上にする | |
/// 更にmodesにはこのAudioClipのボリュームの組み合わせを設定する | |
/// volumesの数はmusicClipsの数と同じにする必要がある | |
/// modesの数は1以上であればいくつでも良い | |
/// | |
/// ## 使い方 | |
/// Prepareを呼んだ後にPlayを呼ぶことで再生する | |
/// Prepareには完了まで数フレームかかるのであらかじめ呼んでおくことが望ましい | |
/// prepareOnAwakeをtrueにすることでAwake時にPrepareを呼ぶことができる | |
/// ChangeModeを呼ぶことであらかじめ設定したボリュームの組み合わせに変更できる | |
/// | |
/// ## 縦の遷移を行う場合 | |
/// 同じ長さで楽器パートが違うAudioClipを複数用意して、musicClipsに設定する | |
/// 例えばメロディのみのAudioClipと打楽器のAudioClipを用意する | |
/// modesの1つ目はvolumesの0番目を1.0に、1番目を0.0にする | |
/// modesの2つ目はvolumesの0番目を1.0と1番目を1.0にする | |
/// ChangeMode(0)を呼ぶとメロディのみのAudioClipが再生され、ChangeMode(1)を呼ぶとメロディと打楽器のAudioClipが再生される | |
/// | |
/// ## 注意事項 | |
/// ループポイントを設定したwavファイルを用意してもWebGLではループポイントが無視され、全体がループする | |
/// AudioClip はロードタイプを `Decompress On Load` にして `Preload Audio Data` をオフにする設定を推奨する | |
/// | |
/// ## 仕組み | |
/// 最初に一瞬再生することで読み込みを予め行い、その後に再生することで2つ以上のAudioClipをほぼ同じタイミングで再生する | |
/// 片方のAudioClipを無音で再生しておくことで、そのボリュームの変化で縦の遷移を実現している | |
/// | |
/// </summary> | |
public class VerticalMixMusic : MonoBehaviour | |
{ | |
[SerializeField, Range(0.0f, 1.0f)] private float volume; | |
[SerializeField] private List<AudioClip> musicClips; | |
[SerializeField] private bool loop; | |
[SerializeField] private bool prepareOnAwake; | |
[SerializeField] private bool playOnAwake; | |
[System.SerializableAttribute] | |
private struct ModeParam | |
{ | |
[SerializeField] private string modeName; | |
public string ModeName => modeName; | |
[SerializeField, Range(0.0f, 1.0f)] private List<float> volumes; | |
public IReadOnlyList<float> Volumes => volumes; | |
[SerializeField] private float modeChangeTime; | |
public float ModeChangeTime => this.modeChangeTime; | |
} | |
[SerializeField] private List<ModeParam> modes = new List<ModeParam>(); | |
private readonly List<AudioSource> _audioSources = new List<AudioSource>(); | |
private bool _prepared; | |
private bool _waitPrepare; | |
private bool _reservedPlay; | |
private float _inputVolume; | |
private int _mode; | |
private int _nextMode; | |
private readonly List<float> _modeChangeVolumes = new List<float>(); | |
private float _modeChangeTime; | |
private void Awake() | |
{ | |
_prepared = false; | |
_waitPrepare = false; | |
_reservedPlay = false; | |
_inputVolume = 1.0f; | |
_mode = 0; | |
_nextMode = 0; | |
_modeChangeTime = 0.0f; | |
CreateAudioSource(); | |
if (playOnAwake) | |
{ | |
Play(); | |
} | |
else if (prepareOnAwake) | |
{ | |
Prepare(); | |
} | |
#if DEBUG | |
foreach (var modeParam in modes) | |
{ | |
if(modeParam.Volumes.Count < musicClips.Count) | |
{ | |
Debug.LogWarning( | |
$"{nameof(modes)} {modeParam.ModeName} の volumes が足りていません。足りないボリュームは 0 として扱われます。"); | |
} | |
if(modeParam.Volumes.Count > musicClips.Count) | |
{ | |
Debug.LogWarning( | |
$"{nameof(modes)} {modeParam.ModeName} の volumes が多いです。"); | |
} | |
} | |
#endif | |
} | |
private void CreateAudioSource() | |
{ | |
if (musicClips.Count == 0) | |
{ | |
Debug.LogWarning($"{nameof(musicClips)} が設定されていません。"); | |
return; | |
} | |
#if DEBUG | |
var samples = musicClips[0].samples; | |
#endif | |
foreach (var musicClip in musicClips) | |
{ | |
var audioSource = gameObject.AddComponent<AudioSource>(); | |
audioSource.clip = musicClip; | |
audioSource.loop = loop; | |
audioSource.volume = 0.0f; | |
_audioSources.Add(audioSource); | |
_modeChangeVolumes.Add(0.0f); | |
#if DEBUG | |
if(musicClip.samples != samples) | |
{ | |
Debug.LogWarning($"{nameof(musicClips)} のsample数(音楽の長さ)が一致しません。"); | |
} | |
#endif | |
} | |
} | |
private void Update() | |
{ | |
if (_waitPrepare) | |
{ | |
var isAllPlaying = true; | |
foreach (var audioSource in _audioSources) | |
{ | |
if( !audioSource.isPlaying ) | |
{ | |
isAllPlaying = false; | |
} | |
} | |
if (isAllPlaying) | |
{ | |
foreach (var audioSource in _audioSources) | |
{ | |
audioSource.Stop(); | |
} | |
_waitPrepare = false; | |
_prepared = true; | |
} | |
return; | |
} | |
if(_reservedPlay) | |
{ | |
_reservedPlay = false; | |
for (var index = 0; index < _audioSources.Count; index++) | |
{ | |
var audioSource = _audioSources[index]; | |
audioSource.volume = volume * _inputVolume * GetModeVolume(_mode, index); | |
audioSource.Play(); | |
} | |
return; | |
} | |
if(_mode != _nextMode) | |
{ | |
_modeChangeTime -= Time.deltaTime; | |
if(_modeChangeTime <= 0.0f) | |
{ | |
_mode = _nextMode; | |
_modeChangeTime = 0.0f; | |
for (var index = 0; index < _audioSources.Count; index++) | |
{ | |
var audioSource = _audioSources[index]; | |
audioSource.volume = volume * _inputVolume * GetModeVolume(_mode, index); | |
} | |
} | |
else | |
{ | |
var t = _modeChangeTime / GetModeChangeTime(_mode); | |
for (var index = 0; index < _audioSources.Count; index++) | |
{ | |
var audioSource = _audioSources[index]; | |
audioSource.volume = volume * _inputVolume * Mathf.Lerp(GetModeVolume(_nextMode, index), _modeChangeVolumes[index], t); | |
} | |
} | |
} | |
} | |
public void Play( int mode = 0, float inputVolume = 1.0f) | |
{ | |
_reservedPlay = true; | |
_inputVolume = inputVolume; | |
_mode = _nextMode = mode; | |
if(!_prepared) | |
{ | |
Prepare(); | |
} | |
} | |
public void Stop() | |
{ | |
_reservedPlay = false; | |
foreach (var audioSource in _audioSources) | |
{ | |
audioSource.Stop(); | |
} | |
} | |
public void ChangeMode(string modeName) | |
{ | |
for (int i = 0; i < modes.Count; i++) | |
{ | |
if (modes[i].ModeName == modeName) | |
{ | |
ChangeMode(i); | |
return; | |
} | |
} | |
Debug.LogWarning($"mode {modeName} は見つかりませんでした。"); | |
} | |
public void ChangeMode(int mode) | |
{ | |
_nextMode = mode; | |
_modeChangeTime = GetModeChangeTime(_mode); | |
for (int i = 0; i < _audioSources.Count; i++) | |
{ | |
_modeChangeVolumes[i] = _audioSources[i].volume; | |
} | |
} | |
public void Prepare() | |
{ | |
if(_prepared) | |
{ | |
return; | |
} | |
if(_waitPrepare) | |
{ | |
return; | |
} | |
foreach (var audioSource in _audioSources) | |
{ | |
audioSource.volume = 0.0f; | |
audioSource.Play(); | |
} | |
_waitPrepare = true; | |
} | |
public void SetVolume(float inputVolume) | |
{ | |
_inputVolume = inputVolume; | |
if(_mode != _nextMode) | |
{ | |
// Updateですぐに変わるので何もしない | |
} | |
else | |
{ | |
for (var index = 0; index < _audioSources.Count; index++) | |
{ | |
var audioSource = _audioSources[index]; | |
audioSource.volume = volume * _inputVolume * GetModeVolume(_mode, index); | |
} | |
} | |
} | |
private float GetModeVolume(int mode, int index) | |
{ | |
if (mode < 0 || mode >= modes.Count) | |
{ | |
Debug.LogWarning($"モード {mode} は範囲外です。"); | |
return 0.0f; | |
} | |
var modeParam = modes[mode]; | |
if (index < 0 || index >= modeParam.Volumes.Count) | |
{ | |
return 0.0f; | |
} | |
return modeParam.Volumes[index]; | |
} | |
private float GetModeChangeTime(int mode) | |
{ | |
if (mode < 0 || mode >= modes.Count) | |
{ | |
Debug.LogWarning($"モード {mode} は範囲外です。"); | |
return 0.0f; | |
} | |
var modeParam = modes[mode]; | |
return modeParam.ModeChangeTime; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment