Max 5 API Reference
ここでは、ほとんどのシグナルオブジェクトで利用されている、追加機能を実装するテクニックのいくつかを紹介します。
フィルタやランプジェネレータのようなユニットジェネレータを実装するためには、オブジェクトのパフォームルーチンの呼び出しと次の呼び出しの間で内部状 態を保存しておく必要があります。次の例は、非常にシンプルなローパスフィルタの例です(単に連続したサンプル値の平均値を求めるものです)。ここでは、 ベクタの最後のサンプルの値を保存し次のベクタの最初のサンプル値との平均を取っています。まず最初に、オブジェクトの構造体の中に値を保持しておく フィールドを追加します。
typedef struct _myfilter { t_pxobject f_obj; t_float f_sample; } t_myfilter;
そして、dsp メソッド(これは入力を1つ、出力を1つ持っています)の中で、DSP チェインの引数の1つとして、オブジェクトへのポインタを渡します。dsp メソッドは、オーディオ処理を開始する際のノイズを避けるために、内部状態の値の初期化も行ないます。
void myfilter_dsp(t_myfilter *x, t_signal **sp, short *count) { dsp_add(myfilter_perform, 4, x, sp[0]->s_vec, sp[1]->s_vec, sp[0]->s_n); x->f_sample = 0; }
これはパフォームルーチンです。処理のループに入る前に内部状態を取得し、ループ終了後に最後の値を保存します。
t_int *myfilter_perform(t_int *w) { t_myfilter *x = (t_myfilter *)w[1]; t_float *in = (t_float *)w[2]; t_float *out = (t_float *)w[3]; int n = (int)w[4]; t_float samp = x->f_sample; // 内部状態からの呼び出し t_float val; while (n--) { val = *in++; *out++ = (val + samp) * 0.5; samp = val; } x->f_sample = samp; // 内部状態への保存 return w + 5; }
thispatcher オブジェクトへの enable メッセージと同じように、MSP の mute~ オブジェクトはサブパッチの動作を停止させるために使うことができます。あなたが開発するオブジェクトのパフォームルーチンが、少しでも演算による負荷が 大きいと考えられる場合、この停止処理が行なわれているかどうかをチェックしなければなりません。これを行なうためには、dsp_add() の呼び出しを行なう際に、DSP チェインへの引数の1つとしてオブジェクトへのポインタを渡す必要があります。次のコードは、すでに例として示したフィルタオブジェクトのパフォームルー チンを修正し、停止処理が行なわれているかどうかをチェックできるようにしたものです。
t_int *myfilter_perform(t_int *w) { t_myfilter *x = (t_myfilter *)w[1]; t_float *in = (t_float *)w[2]; t_float *out = (t_float *)w[3]; int n = (int)w[4]; t_float samp = x->f_sample; // 内部状態の読み込み t_float val; if (x->f_obj.z_disabled) // オブジェクトが停止されているかどうかのチェック return w + 5; while (n--) { val = *in++; *out++ = (val + samp) * 0.5; samp = val; } x->f_sample = samp; // 内部状態の保存 return w + 5; }
The third argument to the dsp method is an array of numbers that enumerate the number of objects connected to each of your objects inputs and outputs. More advanced dsp methods can use this information for optimization purposes. For example, if you find that your object has no inputs or outputs, you could avoid calling dsp_add() altogether. The MSP signal operator objects (such as +~ and *~) to implement a basic polymorphism: they look at the connections count to determine whether the perform routine should use scalar or signal inputs. For example, if the right input has no connected signals, the user can add a scalar value sent to the right inlet.
To implement this behavior, you have a few different options. The first option is to write two different perform methods, one which handles the two-signal case, and one which handles the scalar case. The dsp method looks at the count array and passes a different function to dsp_add(). The example below assumes that the second element in the signal and count arrays (sp[1]) is the right input:
if (count[1]) // シグナルが接続されている場合 dsp_add(mydspobject_twosigperform, 5, x, sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[0]->s_n); else dsp_add(mydspobject_scalarperform, 4, x, sp[0]->s_vec, sp[2]->s_vec, sp[0]->s_n);
2番目の選択肢は、特定のシグナルを表す count 配列の値をパフォームメソッドに渡し、それによってシグナル値を使うか、オブジェクト内に格納されているスカラ値を使うかを決定できるようにするというも のです。この場合、たいていのオブジェクトは、スカラ値の代わりにシグナルからのサンプル値のうちの1つだけを使用します。 ベクタサイズが単体のサンプルとあまり変わらないくらい小さい場合、最初のサンプル値(すなわちインデックスが 0である値)を使用する方法は、どのようなベクタサイズでもうまく動作させるためのテクニックとして用いられます。次の例は、2つの入力と1つの出力を持 つオブジェクトで、このテクニックを用いている例です。右の入力シグナルを表す接続のカウント(count[1])がDSPチェインへの第2の引数として 渡され、右のの入力シグナルベクタは、接続されているかどうかにかかわらず、引数として渡されています。
dsp_add(mydspobject_perform, 6, x, count[1], sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[0]->s_n);
上のような方法で渡された接続のカウント情報(count)を使用するパフォームルーチンは次のようになります。
t_int mydspobject_perform(t_int *w) { t_mydspobject *x = (t_mydspobject *)w[1]; int connected = (int)w[2]; t_float *in = (t_float *)w[3]; t_float *in2 = (t_float *)w[4]; t_float *out = (t_float *)w[5]; int n = (int)w[6]; double in2value; //シグナルが接続されているかどうかによってスカラ値を取得するか、シグナルを使用するかを判断します。 in2value = connected? *in2 : x->m_scalarvalue; // do calculation here return w + 7; }