Perl Hackers Hub

第15回Perl meets beats―鳴らして学ぶシンセサイザー入門(2)

音の作り方[入門編]

(1)で紹介した関数を使って、任意の周波数を持った波形データを生成する方法を説明します。これによってビープ音を鳴らすことができます。

波形の生成

任意の周波数を持った波形データを生成するには、サンプリング周波数について知る必要があります。図3のように、もともと滑らかな波形を一定間隔で記録することをサンプリングと言います。サンプリング周波数とは1秒間に行うサンプリングの回数を意味していて、1秒間をこの回数で割った間隔①でサンプリングして波形データを生成します。任意の周波数を持った波形データを生成するには、1秒間をその任意の周波数で分割した②の範囲で繰り返す波形をサンプリングします。

図3 波形データの例
図3 波形データの例

リスト3は、任意の周波数を持った波形データを生成するコードです。(1)の関数は、引数で与えられた条件で波形データを生成する関数を返します。(2)で1周期あたりのデータ数を算出して、(3)で波形のサンプリング位置を0.0以上1.0未満に正規化した値を引数で渡しています。1秒間の波形データを生成する場合は、(4)で取得した関数をサンプリング周波数と同じ数だけ呼び出します。(5)の引数については次の周波数変調で説明します。

リスト3 波形の生成
sub create_modulator { # (1)
    my $samples_per_sec = shift;
    my $arg_ref = shift;

    my $freq = $arg_ref->{freq};
    my $osc_func = create_mod_func( $arg_ref->{waveform} );
    my $t = 0.0;
    my $samples_per_cycle = $samples_per_sec / $freq; # (2)
    return sub {
        my $mod = shift;
        my $ret = $osc_func->(
            $t / $samples_per_cycle # (3)
        );

        my $dt = 1.0 + $mod;
        if ( 0.0 < $dt ) {
            $t += $dt;
            while ( $samples_per_cycle <= $t ) {
                $t -= $samples_per_cycle;
            }
        }

        return $ret;
    };
}

# 1秒間分の440Hzのサイン波を生成する
my $osc = create_modulator(  # (4)
    44100,                   # サンプリング周波数
    {
        freq => 440,         # 周波数
        waveform => 'sin'    # 波形の種類
    }
);
my @sin_samples = map {
    $osc->( 0 );             # (5)
} 1..44100;

周波数変調

ここでは、時間の経過と共に音の周波数を変化させるモジュレーション(周波数変調)について説明します図4⁠。

図4 周波数変調の例
図4 周波数変調の例

図4は、①の矩形波によって、②の周波数が一定なサイン波を周波数変調しています。周波数変調を行うと③の波形のように、①の出力がマイナスの区間aは周波数が低くなり、①の出力がプラスの区間bは周波数が高くなります。

Perlによる実装

リスト4は周波数変調を実装したコードです。(1)の関数を使って周波数変調用の関数を取得し、(2)でその出力である図4①のような波形を引数に入力しています。リスト3(5)では引数に0を与えているため図4②のように周波数が一定の波形が生成され、リスト4では図4③のように入力された値で周波数が変調された波形が生成されます。create_envelope()で生成される波形については、次の減衰音で説明します。

リスト4 変調用の波形の生成
sub create_pitch_modulator { # (1)
    my $samples_per_sec = shift;
    my $arg_ref = shift;

    my $waveform = $arg_ref->{waveform};
    my $depth = $arg_ref->{depth};
    if ( $waveform eq 'env' ) {
        my $curve = 1.0;
        if ( exists $arg_ref->{curve} ) {
            $curve = $arg_ref->{curve};
        }
        my $env = create_envelope(
            $samples_per_sec,
            { sec => $arg_ref->{speed}, curve => $curve } );
        return sub { $env->() * $depth };
    }
    else {
        my $osc = create_modulator(
            $samples_per_sec,
            {
                freq => (1.0 / $arg_ref->{speed}),
                waveform => $waveform
            }
        );
        return sub { $osc->(0) * $depth };
    }
}

# 周波数変調用の関数の取得
my $mod = create_pitch_modulator(
    44100,                  # サンプリング周波数
    {
        speed => 0.5,       # 変調する速度
        depth => 0.5,       # 変調の度合い
        waveform => 'pulse' # 矩形波
    }
);

# リスト3で定義した波形を周波数変調する
my @sin_with_mod_samples = map {
    $osc->( $mod->() ); # (2)
} 1..44100;

減衰音

太鼓や鐘の音のように、時間の経過と共に音量が小さくなる音を減衰音と言います図5⁠。図5①のように音量(振幅)が一定の波形に対して、②のように+1.0から0.0に変化する波形を掛け合わせると、③のように音量が変化する波形データを生成できます。これらはシンセサイザーでは「エンベロープ」という名称で搭載されていますが、その簡易版をPerlで実装します。

図5 減衰音の例
図5 減衰音の例

Perlによる実装

リスト5は、図5②のような波形を生成する関数を返します。+1.0から0.0に変化させる際に、一定の割合で変化させるか、図5のように最初は急に、後半は緩やかに変化させるかで音のニュアンスが変わってきます。このニュアンスを出す処理はリスト5(1)で行っています。

リスト5 減衰音の例
sub create_envelope {
    my $samples_per_sec = shift;
    my $arg_ref = shift;

    my $curve = 1.0;
    if ( exists $arg_ref->{curve} ) {
        $curve = $arg_ref->{curve};
    }
    my $mod_func = sub { return ( 1.0 - $_[0] ); };
    my $t = 0.0;
    my $interval = $samples_per_sec * $arg_ref->{sec};
    return sub {
        if ( $t < $interval ) {
            my $ret = $mod_func->( $t / $interval );
            $t += 1.0;
            return $ret ** $curve; # (1)
        }
        else {
            return 0.0;
        }
    };
}

音の作り方[実践編]

次はドラムマシンのように、もう少し複雑な音(⁠⁠ドンッ!」とか「タンッ!」とか)を作ります。現在出回っているドラムマシンは、あらかじめ録音したドラムの音を再生することで音を出力していますが、今ほどサンプリング技術が一般的でなかったころは、前節で説明した処理を電子回路で実現していました。つまり、生のドラムに近い音はできませんが、古いリズムマシンの音なら作ることができます(現行機種であるKORG monotribeの存在を考えると、別に古くないですね⁠⁠。

キック

バスドラムのような低い音程で「ドンッ!」と鳴る音を作ります。低い周波数のサイン波を周波数変調して、高い音から低い音へ急激に変化させます。さらに減衰時のニュアンス(音量の変化のさせ方)を調節して、それっぽく聴こえるように工夫しています。

Perlによる実装

リスト6はキックのパラメータです。まずはoscで定義しているパラメータですが、(1)は周波数、(2)は波形です。これだけだと低いサイン波が鳴っているだけなので、短い時間で高い音から元の音程まで変化させます。その変化のさせ方を(3)で定義しています。次にampですが、(4)は音の鳴っている時間、(5)は掛け合わせる波形、(6)は減衰時のニュアンスを定義しています。

リスト6 キックの音色
my $kick = {
    osc => {
        freq => 25,            # (1)
        waveform => 'sin',     # (2)
        mod => {
            speed => 0.25,    
            depth => 3.5,     
            waveform => 'env',┃(3)
            curve => 1.8      
        }
    },
    amp => {
        sec => 0.25,           # (4)
        waveform => 'env',     # (5)
        curve => 1.4           # (6)
    }
};

スネア

スネアドラムは太鼓の一種ですが、実際に鳴る音は金属的な響きを持っています。この金属的な響きはノイズ成分をコントロールして表現します。ノイズ自体はコントロールできないので、図6のような「サンプリグ&ホールド」を使います。

図6 サンプリング&ホールド
図6 サンプリング&ホールド

サンプリング&ホールドとは、図6①のようなノイズ波形からデータを取り出して、②のように取り出したデータを一定期間出力する処理です。取り出す処理を「サンプリング⁠⁠、一定期間出力する処理を「ホールド」と呼びます。本来は、①のように先頭から1つずつデータを取り出すのではなく、ホールド開始時に乱数を発生させるのが正しいのですが、今回は図6のように先頭からデータを取得してホールドを行う実装にしました。このホールド時間は、ノイズによるモジュレーション速度(speed)で設定でき、この値を調整してノイズ成分をコントロールします。

Perlによる実装

リスト7はスネアのパラメータです。バスドラムのように低い音ではないので(1)の周波数を設定しています。さらにサンプリング&ホールド(ノイズ波形)で周波数変調して金属的な響きを加えています。

リスト7 スネアの音色
my $snare = {
    osc => {
        freq => 400,           # (1)
        waveform => 'tri',
        mod => {
            speed => 0.08, depth => 9.0, waveform => 'noise'
        }
    },
    amp => { sec => 0.16, waveform => 'env', curve => 2.2 }
};

ハイハット

ハイハットはシンバルを2つ組み合わせた楽器で、ペダルを使ってその隙間を調節できます。今回は、隙間がある状態で鳴らした音を「オープンハイハット⁠⁠、閉じた状態で鳴らした音を「クローズハイハット」と呼びます。スネアと同様に、金属的な響きを表現するためにノイズ成分をコントロールして音を作ります。

Perlによる実装

リスト8はオープンハイハットとクローズハイハットの2つのパラメータです。両者の違いはampに定義している音の鳴っている時間だけです。オープンハイハットは時間を長く、クローズハイハットは時間を短く設定してあります。

リスト8 ハイハットの音色
# オープンハイハット
my $o_hat = {
    osc => {
        freq => 16000,
        waveform => 'tri',
        mod => {
            speed => 0.06, depth => 6.0, waveform => 'noise'
        }
    },
    amp => { sec => 0.15, waveform => 'env', curve => 2.7 }
};

# クローズハイハット
my $c_hat = {
    osc => {
        freq => 16000,
        waveform => 'tri',
        mod => {
            speed => 0.06, depth => 6.0, waveform => 'noise'
        }
    },
    amp => { sec => 0.08, waveform => 'env', curve => 2.7 }
};

波形データの生成

次に、これらのパラメータを使って波形データを生成します。

リスト9は、リスト6~8で定義したパラメータから波形データを生成するコードです。(1)で周波数変調に使う関数を生成し、(2)で音量を変化させるための関数を生成しています。(3)が周波数変調と音量の変化を実現するコードです。波形データの長さの分だけ(3)を実行して、その結果を波形データとして確保しています。クロージャを使えば(3)のように簡潔に記述できるので、このような処理には適しています。

リスト9 単音の波形データの生成
sub create_oneshot {
    my $samples_per_sec = 44100;
    my $arg_ref = shift;

    my $osc = create_modulator(
        $samples_per_sec, $arg_ref->{osc} ); # (1)
    my $env = create_envelope(
        $samples_per_sec, $arg_ref->{amp} ); # (2)

    my $mod = sub { return 0.0; }; # pitch modulator
    if ( exists $arg_ref->{osc}->{mod} ) {
        $mod = create_pitch_modulator(
            $samples_per_sec, $arg_ref->{osc}->{mod} );
    }

    my $amp = $arg_ref->{amp};
    my $gate_time = int( $samples_per_sec * $amp->{sec} );

    my @samples = map {
        $osc->( $mod->() ) * $env->(); # (3)
    } 0..($gate_time - 1);

    return \@samples;
}

my $samples_kick_ref = create_oneshot( $kick );
my $samples_snare_ref = create_oneshot( $snare );
my $samples_o_hat_ref = create_oneshot( $o_hat );
my $samples_h_hat_ref = create_oneshot( $c_hat );

おすすめ記事

記事・ニュース一覧