基本波形の生成

オーディオやマルチメディア関連のプログラムを書いていると、手早くノコギリ波や矩形波を生成したいことがある。
そこで、簡単にいろいろな波形を生成する方法をまとめる。


1. ノコギリ波

1サンプルごとに少しずつ値を足していくと、直線的に増加していく波形ができる。
値が 1.0 を超えた瞬間に 0.0 へ戻すことで、ノコギリ状の周期波形が生成できる。
これは、値の小数点以下のみを取り出すことで実装できる。

実装してみる。
以下のコードでは、オーディオ信号として扱いやすいよう出力値の範囲を 0〜1 から -1〜1 へ変換している。
double sawtooth(double 周波数, double サンプルレート)
{
    static double phase = 0.0;   // 初期値
    phase += 周波数 / サンプルレート;
    phase -= floor(phase);       // 整数部分を引き算
    return 2.0 * (phase - 0.5);  // -1.0~1.0 の範囲に広げる
}

「小数点以下を取り出す」を「1.0で割ったときの剰余」として実装する方法もある。
double another_sawtooth(double 周波数, double サンプルレート)
{
    static double phase = 0.0;   // 初期値
    phase += 周波数 / サンプルレート;
    phase = fmod(phase, 1.0);    // 小数部分を取り出す
    return 2.0 * (phase - 0.5);  // -1.0~1.0 の範囲に広げる
}
プログラミング言語によっては(C# など)、浮動小数点数(float や double)に対して剰余演算子 % が使えるので、この実装方法のほうがシンプルに書ける。


2. 矩形波

ノコギリ波が 0.5 を超えたら 1.0、超えなければ -1.0 を出力する。
条件を「0.75 を超えたら 1.0 を出力」に変えれば、デューティ比25%の矩形波になる。
double pulse(double 周波数, double サンプルレート)
{
    static double phase = 0.0;
    phase += 周波数 / サンプルレート;
    phase -= floor(phase);
    if (phase > 0.5) {
        return 1.0;
    } else {
        return -1.0;
    }
}


3. 三角波

ノコギリ波が 0.5 を超えたら、波形を上下反転させて出力する。
double triangle(double 周波数, double サンプルレート)
{
    static double phase = 0.0;
    phase += 周波数 / サンプルレート;
    phase -= floor(phase);

    double tr;
    if (phase > 0.5) {
        tr = 1.0 - phase;    // 0.5 より大きいので反転
    } else {
        tr = phase;          // そうでなければそのまま
    }
    tr *= 2.0;               // 2倍して 0.0~1.0 の範囲に
    return 2.0 * (tr - 0.5); // -1.0~1.0 の範囲に
}

三項演算子でひとまとめにしたバージョン
double triangle(double f, double fs)
{
    static double phase = 0.0;
    phase += f / fs;
    phase -= floor(phase);
    return 4.0 * ((phase > 0.5) ? (1.0 - phase) : phase) - 1.0;
}

絶対値で実装したバージョン
(上のコード例とは位相が反転するため注意)
double another_triangle(double f, double fs)
{
    static double phase = 0.0;
    phase += f / fs;
    phase -= floor(phase);
    return 2.0 * fabs(2.0 * phase - 1.0) - 1.0;
}


4. サイン波

sin 関数に 0 ~ 2π の範囲のノコギリ波を入力する。
double sin(double 周波数, double サンプルレート)
{
    static double phase = 0.0;
    phase += 周波数 / サンプルレート;
    phase -= floor(phase);
    return sin(2.0 * M_PI * phase);
}


5. ノイズ

乱数を -1 ~ 1 の範囲で発生させると手軽にノイズができる。
double noise(void)
{
    // rand関数で生成
    return 2.0 * ((double)rand() / RAND_MAX - 0.5);
}

参考までに、他の擬似乱数生成法である Xorshift で実装した例も挙げておく。
double noise_xorshift(void)
{
    // Xorshift版
    // Wikipediaの実装例を改変
    static unsigned long x = 123456789, y = 362436069,
        z = 521288629, w = 88675123;
    unsigned long t = (x ^ (x << 11));
    x = y; y = z; z = w;
    w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)));
    return 2.0 * ((double)w/32767.0 - 0.5);
}




最後に、ちょっとマニアックな注意書き。
この記事の方法は、シンプルで扱いやすいため軽い用途には向いているものの、実はあまり品質が良くない。

この記事で紹介している生成手法のうち、ノコギリ波矩形波三角波については エイリアシングノイズ(折り返し雑音)と呼ばれる、本来は存在しないはずの周波数成分を生み出してしまう性質がある。

周波数成分の品質にこだわらない用途では、この記事の方法で十分だと思う。
一方、シンセサイザの音源部などとして使う場合は、多少複雑にはなるがエイリアシングノイズの発生しない他の波形の生成方法を使ったほうが音質が良くなる。


スペクトルの比較
シンプルな手法で生成した信号には、本来存在しないはずの周波数成分が生じている


2014/5/4 編集
2016/6/5 編集
2016/7/13 編集
2016/10/30 ソースコードの表示崩れを解消
2017/7/28 編集
2019/1/2 文章をシンプルにしつつサンプルのマイナーチェンジ
2019/3/11 説明を明確化してシンプルに
2019/6/29 エイリアスノイズの話を詳しく
2020/10/9 三角波の亜種追加、エイリアシング画像追加、本文マイナーチェンジ
2021/4/30 本文マイナーチェンジ

コメント

  1. 勉強させてもらってます!
    音声合成もっと記事が増えるの楽しみにしてます(*'ω'*)

    返信削除

コメントを投稿

このブログの人気の投稿

(1)C言語で音声合成もどき ~WAVファイルを生成する~

(2)C言語で音声合成もどき ~母音の生成~