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変換にかける前に高域成分を除去します。その結果、高い音高では矩形波でも鋸波でもサイン波に近づいていきます。
実装方法としては、デジタルシンセサイザーの波形合成では「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を使って書き直したものの出力音が以下です。

なお、矩形波のウエーブテーブルはデューティ比50%のものしかありません。
PWMができるパルス波を実現するには、矩形波ではなく鋸波のウエーブテーブルを使います。

下図のように、二つの同じ周波数の鋸波を逆向きにして足し合わせると、両者の減少と増加が打ち消しあって矩形波を作ることができます。
このとき、2つの鋸波の位相をずらすことにより、デューティ比が制御できます。

鋸波のウエーブテーブルはlogue SDKに含まれていますので、これを使うとPWMつきのパルス波を作ることができます。鋸波のウエーブテーブルの値域は-1.0 .. 1.0ですから、生成されるパルス波の値域は、デューティ比50%のときは-1.0 .. 1.0ですが、デューティ比が0%や100%に近いとき(二つの鋸波の頂点が近接しているとき)は下図のように-2.0 .. 0や0 .. 2.0になっていきます。そのため、(1.0 - 2*デューティ比)を加算して補正する必要があります。

コメント