前回の続きです。
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みたいなこともできそうです。
