NU:TEKT NTS-1でのノイズの出ないOSCの作りかた


NTS-1にMIDIケーブルがつながるようになったので、先日テスト用に作ったOSCを鳴らしてみると、高音部でエイリアシングが気になります。

DA/AD変換でサンプリング周波数f/2よりも高い周波数f0の信号を変換すると、その信号はf/2より低い周波数{f/2 – (f0 – f/2)}へ折り返されて出現します。これがエイリアシングです。
上記のOSCの実装では、信号の生成を

float sig = (phase - 0.5f <= 0.f) ? 1.f : -1.f;

としていますので、振幅は1.0か-1.0のどちらかでその中間は全くない、という(理想的な)矩形波が生成されます。しかし数学的にはこの信号には、無限に高い周波数の成分が含まれており、DA変換するときにはこれがノイズの元になります。

以下の録音を聴くと、音高が高くなって基本周波数がサンプリング周波数/2に近づくと、高域成分が全部低域へ折り返されてきてノイジーになっていることが分かります。

波形の画像を見ると、エッジの部分にトゲのようなものが見えます。これがノイズ成分です。

エイリアスノイズを取り除くには、信号をDA/ADにかける前にローパスフィルタを通して、高域成分を除去します。その結果、高い音高では矩形波でも鋸波でもサイン波に近づいていきます。
実装方法としては、デジタルシンセサイザーの波形合成では「BLIT(Band Limited Impulse Train)」という方式があります。また、国産のソフトウェアシンセサイザーの草分けであるSynth1は、音高ごとに高域成分を含まないウエーブテーブルを用意しているそうです。

シンセプログラミング

NTS-1では、エイリアスノイズが出ない矩形波を作るためのウエーブテーブルが用意されています。NTS-1に最初から入っている矩形波のOSCでは、高い音を鳴らしてもエイリアスノイズが出ませんが、おそらくこのウェーブテーブルを使っていると思われます。

ウエーブテーブルを使って矩形波を合成するためのAPIは以下です。

osc_bl2_sqrf()
__fast_inline float osc_bl2_sqrf(float 	x,
float 	idx 
)
Band-limited square wave lookup.(interpolated version).
Parameters
  x	Phase in [0, 1.0].
  idx	Fractional wave index in [0,6].
Returns
  Wave sample.

パラメータのidxは、ウエーブテーブルのインデックス番号を指定します。こちらはまだドキュメントが用意されていないようですが、ヘッダファイルを見るとノートナンバーからインデックス番号を求める関数が用意されています。ノートナンバーは整数ではなくfloatで指定するようになっています。

  /**
   * Get band-limited square wave index for note.
   *
   * @param note Fractional note in [0-151] range.
   * @return     Corresponding band-limited wave fractional index in [0-6].
   */
  float _osc_bl_sqr_idx(float note);

  __fast_inline float osc_bl_sqr_idx(float note) {
    return _osc_bl_sqr_idx(note);
  }

このAPIはOSC_CYCLE関数の中で、以下のように使います。

  const float note = (params->pitch >> 8) + (params->pitch & 0xFF)/256.0f;
/* 波形生成ループ開始 */
    float sig = osc_bl2_sqrf(phase, osc_bl_sqr_idx(note));
/* 波形生成ループ終了 */

関数名がosc_bl2_で始まるAPIはウエーブテーブルを補完して使用していますが、補完せずに高速に処理するosc_bl_で始まる関数も用意されています。(補完なしの場合、ウエーブテーブルのインデックス番号はuint8_t型になります。)
鋸波およびパラボリック波についても同様のウエーブテーブルおよび関数群が用意されています。

冒頭のOSCをこのAPIを使って書き直したものの出力音が以下です。

波形からもノイズが消えていることが確認できます。

コメント