UnityEditor.AudioUtilとは
エディタ上でAudioClipを扱うための内部APIです。
リファレンスは存在しませんが、その全貌はここで見ることができます。
全貌と言ってもほぼexternなのでメソッド名や型しか分かりませんが。
使うと何ができるの?
AudioSourceを使わずに、Editor上でAudioClipを再生できます。
AudioClipを選択したときにInspector下部にプレビューとしてサウンドプレイヤーが表示されますが、あんな感じのGUIを自前定義のカスタムInspectorやEditorWindow上に構築できるわけです。
私の場合、AudioClip(のラッパークラス)にこんなカスタムPropertyDrawerを定義するのに使いました。
準備
内部APIなので、当然普通に呼ぶことはできません。Reflectionを使って無理やり呼び出します。
メソッド名列挙
ここを見てpublicメソッドの名前を全部列挙します。
列挙型管理なのは、コード補完が効いて扱いやすいため。タイポ怖い。
ついでに使うnamespaceも全て書いておきます。
usingSystem;usingUnityEditor;usingUnityEngine;usingSystem.Reflection;usingSystem.Collections.Concurrent;usingSystem.Linq;usingSystem.Linq.Expressions;publicstaticclassInternalAudioUtil{enumMethod{PlayClip,StopClip,PauseClip,ResumeClip,LoopClip,IsClipPlaying,StopAllClips,GetClipPosition,GetClipSamplePosition,SetClipSamplePosition,GetSampleCount,GetChannelCount,GetBitRate,GetBitsPerSample,GetFrequency,GetSoundSize,GetSoundCompressionFormat,GetTargetPlatformSoundCompressionFormat,GetAmbisonicDecoderPluginNames,HasPreview,GetImporterFromClip,GetMinMaxData,GetDuration,GetFMODMemoryAllocated,GetFMODCPUUsage,IsTrackerFile,GetMusicChannelCount,GetLowpassCurve,GetListenerPos,UpdateAudio,SetListenerTransform,HasAudioCallback,GetCustomFilterChannelCount,GetCustomFilterProcessTime,GetCustomFilterMaxIn,GetCustomFilterMaxOut,}}
以降のコードは全てこのInternalAudioUtil
クラス内に記述します。
メソッド取得・コンパイル・キャッシュ
ReflectionとExpressionをこねくり回します。
全部public staticだし、オーバーロードの曖昧性がないので楽でいいですね。
Expression構築については、neuecc先生の手法を参考にしています。
//AudioUtil型staticreadonlyTypetAudioUtil=typeof(Editor).Assembly.GetType("UnityEditor.AudioUtil");//コンパイル済みメソッドのキャッシュstaticreadonlyConcurrentDictionary<Method,Func<object[],object>>compiled=newConcurrentDictionary<Method,Func<object[],object>>();//キャッシュからメソッドを取得する。コンパイル済みでなければコンパイルしてキャッシュし、それを返す。staticFunc<object[],object>GetOrCompile(Methodmethod){returncompiled.GetOrAdd(method,_m=>{//キャッシュが存在しなければここに来る//MethodInfo取得varm=tAudioUtil.GetMethod(_m.ToString(),BindingFlags.Static|BindingFlags.Public);//voidメソッドのためのreturn先ラベルを定義varvoidTarget=Expression.Label(typeof(object));//引数はobject[]varargs=Expression.Parameter(typeof(object[]),"args");//MethodInfoのパラメータの型に引数をキャストするExpressionの束varparameters=m.GetParameters().Select((x,index)=>Expression.Convert(Expression.ArrayIndex(args,Expression.Constant(index)),x.ParameterType)).ToArray();//式木構築varlambda=Expression.Lambda<Func<object[],object>>(m.ReturnType==typeof(void)//voidメソッドの場合、ブロックにしてreturn default(object)する必要がある?(Expression)Expression.Block(Expression.Call(null,m,parameters),Expression.Return(voidTarget,Expression.Default(typeof(object))),Expression.Label(voidTarget,Expression.Constant(null)))//返り値がある場合はCallして結果をobjectにキャストするだけ:Expression.Convert(Expression.Call(null,m,parameters),typeof(object)),args);//コンパイルしてキャッシュしつつ返すreturnlambda.Compile();});}
呼ぶ
new object[]{...}
やらキャストやらを毎回書くのは嫌なので、適当に中間メソッドを作っておいて、
staticTRetCall<TRet>(Methodmethod)=>(TRet)GetOrCompile(method).Invoke(null);staticTRetCall<T0,TRet>(Methodmethod,T0arg0)=>(TRet)GetOrCompile(method).Invoke(newobject[]{arg0});staticTRetCall<T0,T1,TRet>(Methodmethod,T0arg0,T1arg1)=>(TRet)GetOrCompile(method).Invoke(newobject[]{arg0,arg1});staticTRetCall<T0,T1,T2,TRet>(Methodmethod,T0arg0,T1arg1,T2arg2)=>(TRet)GetOrCompile(method).Invoke(newobject[]{arg0,arg1,arg2});staticTRetCall<T0,T1,T2,T3,TRet>(Methodmethod,T0arg0,T1arg1,T2arg2,T3arg3)=>(TRet)GetOrCompile(method).Invoke(newobject[]{arg0,arg1,arg2,arg3});staticvoidCall(Methodmethod)=>GetOrCompile(method).Invoke(null);staticvoidCall<T0>(Methodmethod,T0arg0)=>GetOrCompile(method).Invoke(newobject[]{arg0});staticvoidCall<T0,T1>(Methodmethod,T0arg0,T1arg1)=>GetOrCompile(method).Invoke(newobject[]{arg0,arg1});staticvoidCall<T0,T1,T2>(Methodmethod,T0arg0,T1arg1,T2arg2)=>GetOrCompile(method).Invoke(newobject[]{arg0,arg1,arg2});staticvoidCall<T0,T1,T2,T3>(Methodmethod,T0arg0,T1arg1,T2arg2,T3arg3)=>GetOrCompile(method).Invoke(newobject[]{arg0,arg1,arg2,arg3});
以下のようにそれぞれのメソッドに対応する公開APIを定義します(これが一番疲れました)。[Obsolete]
を付けているPlayClipについては後述します。
publicstaticvoidPlayClip(AudioClipclip)=>Call(Method.PlayClip,clip,0,false);[Obsolete("The parameters <startSample> and <loop> are not working")]publicstaticvoidPlayClip(AudioClipclip,intstartSample,boolloop)=>Call(Method.PlayClip,clip,startSample,loop);publicstaticvoidStopClip(AudioClipclip)=>Call(Method.StopClip,clip);publicstaticvoidPauseClip(AudioClipclip)=>Call(Method.PauseClip,clip);publicstaticvoidResumeClip(AudioClipclip)=>Call(Method.ResumeClip,clip);publicstaticvoidLoopClip(AudioClipclip)=>Call(Method.LoopClip,clip);publicstaticboolIsClipPlaying(AudioClipclip)=>Call<AudioClip,bool>(Method.IsClipPlaying,clip);publicstaticvoidStopAllClips()=>Call(Method.StopAllClips);publicstaticfloatGetClipPosition(AudioClipclip)=>Call<AudioClip,float>(Method.GetClipPosition,clip);publicstaticintGetClipSamplePosition(AudioClipclip)=>Call<AudioClip,int>(Method.GetClipSamplePosition,clip);publicstaticvoidSetClipSamplePosition(AudioClipclip,intiSamplePosition)=>Call(Method.SetClipSamplePosition,clip,iSamplePosition);publicstaticintGetSampleCount(AudioClipclip)=>Call<AudioClip,int>(Method.GetSampleCount,clip);publicstaticintGetChannelCount(AudioClipclip)=>Call<AudioClip,int>(Method.GetChannelCount,clip);publicstaticintGetBitRate(AudioClipclip)=>Call<AudioClip,int>(Method.GetBitRate,clip);publicstaticintGetBitsPerSample(AudioClipclip)=>Call<AudioClip,int>(Method.GetBitsPerSample,clip);publicstaticintGetFrequency(AudioClipclip)=>Call<AudioClip,int>(Method.GetFrequency,clip);publicstaticintGetSoundSize(AudioClipclip)=>Call<AudioClip,int>(Method.GetSoundSize,clip);publicstaticAudioCompressionFormatGetSoundCompressionFormat(AudioClipclip)=>Call<AudioClip,AudioCompressionFormat>(Method.GetSoundCompressionFormat,clip);publicstaticAudioCompressionFormatGetTargetPlatformSoundCompressionFormat(AudioClipclip)=>Call<AudioClip,AudioCompressionFormat>(Method.GetTargetPlatformSoundCompressionFormat,clip);publicstaticstring[]GetAmbisonicDecoderPluginNames()=>Call<string[]>(Method.GetAmbisonicDecoderPluginNames);publicstaticboolHasPreview(AudioClipclip)=>Call<AudioClip,bool>(Method.HasPreview,clip);publicstaticAudioImporterGetImporterFromClip(AudioClipclip)=>Call<AudioClip,AudioImporter>(Method.GetImporterFromClip,clip);publicstaticfloat[]GetMinMaxData(AudioImporterimporter)=>Call<AudioImporter,float[]>(Method.GetMinMaxData,importer);publicstaticdoubleGetDuration(AudioClipclip)=>Call<AudioClip,double>(Method.GetDuration,clip);publicstaticintGetFMODMemoryAllocated()=>Call<int>(Method.GetFMODMemoryAllocated);publicstaticfloatGetFMODCPUUsage()=>Call<float>(Method.GetFMODCPUUsage);publicstaticboolIsTrackerFile(AudioClipclip)=>Call<AudioClip,bool>(Method.IsTrackerFile,clip);publicstaticintGetMusicChannelCount(AudioClipclip)=>Call<AudioClip,int>(Method.GetMusicChannelCount,clip);publicstaticAnimationCurveGetLowpassCurve(AudioLowPassFilterlowPassFilter)=>Call<AudioLowPassFilter,AnimationCurve>(Method.GetLowpassCurve,lowPassFilter);publicstaticVector3GetListenerPos()=>Call<Vector3>(Method.GetListenerPos);publicstaticvoidUpdateAudio()=>Call(Method.UpdateAudio);publicstaticvoidSetListenerTransform(Transformt)=>Call(Method.SetListenerTransform,t);publicstaticboolHasAudioCallback(MonoBehaviourbehaviour)=>Call<MonoBehaviour,bool>(Method.HasAudioCallback,behaviour);publicstaticintGetCustomFilterChannelCount(MonoBehaviourbehaviour)=>Call<MonoBehaviour,int>(Method.GetCustomFilterChannelCount,behaviour);publicstaticintGetCustomFilterProcessTime(MonoBehaviourbehaviour)=>Call<MonoBehaviour,int>(Method.GetCustomFilterProcessTime,behaviour);publicstaticfloatGetCustomFilterMaxIn(MonoBehaviourbehaviour,intchannel)=>Call<MonoBehaviour,int,float>(Method.GetCustomFilterMaxIn,behaviour,channel);publicstaticfloatGetCustomFilterMaxOut(MonoBehaviourbehaviour,intchannel)=>Call<MonoBehaviour,int,float>(Method.GetCustomFilterMaxOut,behaviour,channel);
これで準備OKです。
使用上の注意
大体メソッド名と引数名から予測できる通りの挙動をしますが、いくつかかなりヤバめの注意点があります。
PlayClip()
の第2引数以降は指定しても何も起きない
PlayClip(AudioClip clip, int startSample, bool loop)
見るからに再生開始位置とループ有無を指定できそうですが、できません。
一応全て引数に取れるメソッドも定義していますが、この理由により[Obsolete]
を付けています。
なお、代わりにSetClipSamplePosition()
やLoopClip()
を同時に使うことで所望の挙動が得られます。
複数のAudioClipを再生すると、最後に再生したもの以外停止不能になる
そんなバカな、StopAllClips()
があるじゃないか。私もそう思いました。
StopAllClips()
は、最後に再生したclipを停止します。
clipを明示的に指定しなくてもいいのでとても便利ですね!!!!!
この状態になると、再生が終了するか、Unityを再起動するまで他のAudioClipは停止できません。
そのため、クロスフェードのプレビューとかは無理です。残念。
全てのメソッドの動作確認はしていない
きっとまだ罠があるので、ぜひ踏み抜いて教えてください。
参考リンク
neuecc先生によるReflectionの高速化手法紹介
http://neue.cc/2014/01/27_446.html
Rtyper氏が作った旧AudioUtilのラッパー(とその問題点(上述したものと同じ))
https://forum.unity.com/threads/reflected-audioutil-class-for-making-audio-based-editor-extensions.308133/