前回の続きです。
Unity Web Player | livecoding-audio
波形を即時編集する
前回下調べした通り、JavaScriptのevalを用いて実装していきます。
先日の動画と似たようなインターフェースを目指します
function Calc( t : float, w : float, s : float, f : float ) : float { var y : float = 0; y = Mathf.sin( w * t ); return y; }
この関数の内部を編集していくイメージです。
一度関数が確定した後、同じ関数を何度も呼ぶことになるので
無名関数を作成し、それを変数に代入して保持しておくことにします。
public var innerCalc = function(t:float,w:float,s:float,f:float):float{return 0;}; public var LastError : String = null; public var IsValid : boolean = false; public function Check( code : String ) : boolean { var y : float = 0; var t : float = 0; var w : float = 0; var s : float = 48000; var f : float = 440; try { eval( code ); eval( "innerCalc = function(t:float,w:float,s:float,f:float):float{\n" + code + ";\nreturn y;};" ); IsValid = true; return true; } catch( e : System.Exception ) { IsValid = false; if( e as UnityScript.Scripting.CompilationErrorsException ) { LastError = ( e as UnityScript.Scripting.CompilationErrorsException ).Errors.ToString(); return false; } LastError = e.GetType().ToString() + " : " + e.Message; } return false; } public function Calc( t : float, w : float, s : float, f : float ) : float { return innerCalc( t, w, s, f ); }
Check関数でコードの実行チェックと無名関数の生成を行っています
evalを二回呼んでますが、一回目はコードチェック用、二回目は無名関数生成用です。
無名関数生成と同時にチェックも行うことはできますが、
ここではユーザーが入力したコードに手を入れているので
エラー表記がユーザー入力時点のコードと一致しなくなってしまいます。
そのため、チェック用と無名関数生成用の二つにわけて実行しています。
実行に成功したら innerCalc に代入しておきます。
失敗した場合はエラー内容を LastError に突っ込んでおきます。
文法エラーは UnityScript.Scripting.CompilationErrorsException が返ってくるので、
そのときは読みやすいようにいい感じにしときます。
他のエラーは手抜きです。
Check関数に成功した場合はコード実行ができる状態なので、
Calc関数を呼んでユーザーが編集したコードを実行します。
上記のコードを UserCode.js としてAssets/Plugins においておきます。
呼び出し側のC#のコードは下記のような感じになりました。
public float mTime = 0.0f; public float mFrequency = 261.62f; public float mSampling = 48000.0f; public UserCode mUserCode = null; public string mCode; void Start() { mUserCode = this.GetComponent<usercode>(); mCode = "y = Mathf.sin( w * t ) * Mathf.exp( -2 * t );"; bool result = mUserCode.Check( mCode ); } void OnAudioFilterRead( float[] data, int channels ) { for( int i = 0; i < data.Length; i += channels ) { float t = (mTime+i) / mSampling; float w = Mathf.PI * 2.0f * mFrequency; float s = mSampling; float f = mFrequency; data[i] = mUserCode.Calc( t, w, s, f ); for( int k = 1; k < channels; k++ ) { data[i+k] = data[i]; } } mTime += data.Length; }
まず、Start の中で先ほどのJavaScriptのコンポーネントを取得して保持しておきます。
ついでに今回はここでコード生成、チェックも行っちゃいます。
あとは data[i] に突っ込む計算の部分を mUserCode.Calc( t, w, s, f ) に置き換えてあります。
これでランタイム上で任意のコードから波形生成できる仕組みができました。
あとはGUIでコード編集、任意のタイミングでコードチェックを行えば
リアルタイム変数ができますが、今回GUIについては割愛します。
音に同期してオブジェクトを動かす
さて、これでリアルタイム波形編集はできるようになったのですが、
ただ音が鳴らせるだけってのもツマランので、
音に同期させてオブジェクトを動かしてみようと思います。
音に同期させて動かすために、現在鳴っている音の周波数成分を取得します。
それぞれの周波数の成分を取得するのにはフーリエ変換を用いるらしい。
ですが、すでに周波数スペクトルを取得するライブラリを
作ってくださった方がいますので、そちらを利用させていただくことにします。
ビジュアライザ向けスペクトル分析 (Unity, C#) –Keijiro Takahashi
こちらのAudioSpectrum.csをAssets/以下の任意のディレクトリに、
AudioSpectrumInspector.csをAssets/Editor/以下に置きます。
あとはAudioSpectrumを任意のGameObjectに追加すればOK
あとは下記のような感じで、各周波数成分を取得し、同期させることができます
public float Scale; public float Torque; private AudioSpectrum mSpectrum; private int mIndex; private Vector3 mBaseScale; private Vector3 mTorqueDir; void Start() { GameObject camera = GameObject.Find( "Main Camera" ); mSpectrum = camera.GetComponent<audiospectrum>(); mBaseScale = transform.localScale; mTorqueDir = Random.onUnitSphere; mIndex = Random.Range( 0, mSpectrum.Levels.Length ); } void Update { transform.localScale = mBaseScale * ( 1.0f * mSpectrum.levels[mIndex] * Scale ); rigidbody.AddTorque( mSpectrum.Levels[mIndex] * Torque * mTorqueDir ); }
Start で Main Camera に追加した AudioSpectrum を取得し、保持しておきます。
あとは基準となるオブジェクトの大きさを保持し、トルクをかける方向を決めておきます。
AudioSpectrum.Levels が現在の周波数成分をあらわしているので、
どの周波数成分に同期するかをランダムに決めておきます。
あとは Update で周波数のレベルを取得し
オブジェクトの大きさを変えたり、トルクをかけたりしています。
おわりに
細かい調整は入っていますが、大体上記の要素で先のアプリはできています。
コード編集をユーザーが行えるとなると、Unityでゲームツクールをツクールこともできそうですね。
あとはユーザー編集Modや、Ruby Warriorみたいなこともできそうです。