信号データの生成
前回のサンプルライブラリを修正しました.
(終了処理用の関数
pcmFin() 周辺にバグが潜んでいました.)
今回の実習を始める前に,最新バージョンへ更新してください.
pcm.c をDLし,
make コマンドを実行.
サンプルプログラムと基本的な波形
まず,新たなサンプルアプリ wave.c
で基本的な波形のWAVファイルを生成し,
audacity や paplay で波形と音色を確認・比較しよう.
作業用ディレクトリ:前回と同じ ~/sp/pcm/ 等としておきましょう.
ソースファイル:wave.c
関数ポインタと云ふ技を使いましたよ.
コンパイル:
$ cc wave.c -std=c99 -L. -lpcm -lm -o wave
作業:信号の生成,波形と音色の確認・比較
$ ./wave sin C > sin-C.wav; audacity sin-C.wav & # 前回の sin.c と同じ,無表情な音色
$ ./wave tri C > tri-C.wav; ... # sin とほぼ同じ or 木管楽器っぽい音色?
$ ./wave sqr C > sqr-C.wav; ... # 木管楽器っぽい音色?
$ ./wave saw C > saw-C.wav; ... # 金管楽器 or 弦楽器っぽい音色?
基本周波数 f が等しくても,波形によって音色が変化しますね.
波形表示と音声再生の両方に使えるけど,重いのが audacity .
一方,音声再生だけだが,
ファイル保存不要でパイプライン処理可能なので,お手軽なのが paplay .
用途に応じて,使い分けよう.
参考資料:(Wikipedia)
任意波形の加算合成
次に,教科書の第3章などを参考にして,
基本的波形を複数の正弦波によって加算合成 "additive synthesis"
するプログラム synth.c を実装しよう.
理論:
基本周波数 "fundamental frequency" \( f_0 \) の
整数倍の高調波周波数 "harmonic frequency" \( h_k = k \ f_0 \ , \ k = 2, 3, 4, \cdots \) の正弦波を重ね合わせて,
つまり,複数の正弦波の瞬時値を加算して,新しい波形を生成.
ただし,単純な合計ではなく,
振幅スペクトルの分布(各周波数成分の振幅,配合比率)を乗じて加算,
つまり加重平均となりますよ.
基本周波数の正弦波の音:基音 "fundamental tone" or 純音 "pure tone"
整数倍周波数の正弦波の音:高調波 "harmonics" or 倍音 "overtone"
スペクトル展開:基音から\( N \)倍音までの加重平均.
\[ x(t) = \sum_{k = 1}^{N} a_k \ \sin 2 \pi k f_0 t \]
実際には,加算結果の値域を \( [-1.0, +1.0] \) に収めないと,
出力時に,リミッタが作動し,波形が乱れてしまいますよ.
いわゆる「音割れ」が発生.
値域オーバする場合,適切な定数値を乗じて,配合比率を改めればよい.
振幅スペクトルの正規化:お手軽な音割れ対策.
\[ \F{a_k}{\sum_k a_k} \rightarrow a_k \]
適切な係数がわからん場合,合計が1となるように,合計値で割ればよい.
ただし,安全マージンが大きい分,合成信号の音量は小さめになってしまいますよ.
音量小さすぎ問題を避けるには,
振幅スペクトル(電圧)ではなく,
パワースペクトル(電力)の正規化と云う意味で,
各振幅の自乗の合計の平方根で割ってもよいでしょう.
瞬間的な音割れの可能性はありますが.
プログラミング:
$ cp sin.c synth.c # 前回の sin.c を元に改造
$ vim synth.c
...
int main(...)
{
...
// スペクトル分布
#define N 50 // 例:50倍音まで
double a[N+1 ] = {}; // 0 〜 N 倍音の配合比率(振幅スペクトル)の配列,初期値0.0
for (int k = 1 ; k <= N ; k += 2 ) a[k] = 4.0/(M_PI*k); // 例:矩形波(奇数次高調波のみ から成る)
// 配列の要素番号は 0 〜 N.しかし,要素ゼロは不使用.
// スペクトルの正規化
/*
double a0 = 0.0; // 合計
for (int k = 1; k <= N; k++) a0 += a[k];
for (int k = 1; k <= N; k++) a[k] /= a0;
*/
// 音割れへの手抜きな対策.振幅の合計を 1.0 としている.
// 本来なら瞬時値の値域を±1.0 とすべき
// 実数による標本値の設定
double *x0 = p->val[0];
for (int i = 0; i < len; i++) {
...
double v = 0.0;
for (int k = 1; k <= N; k++) v += a[k]*sin(k*wt);
x0[i] = v;
}
...
}
とりあえず,矩形波のコード例を挙げました.
鋸歯状波や三角波も作成してみよう.
コンパイル・実行:
$ cc ...
$ ./synth C | paplay
高調波の配合比率によって,波形・音色を変更できますね.
はい,アナログシンセサイザみたいなものを作ったということです.
自分好みの音色になるような配合比率を見つけ出しましょう.
スペクトル分布の計算式を変更したり,
定数値を指定しても良いでしょう.
実際のアナログシンセでは加算合成ではなく,
減算合成 "subtractive synthesis" が主流のようですが,
波形合成としての基本原理は,どちらでも同じです.
合成波形の振幅変調
さらに,教科書の第5章や第9章などを参考にして,
合成波形の包絡線 "envelope"(振幅の時間的変化)の形状を
振幅変調 "amplitude modulation"(AM)によって整えよう.
現実の楽器の響きのような,
音の強弱の微妙な変化を表現したいですね.
ADSR:一般的なシンセサイザ製品で利用される
包絡線形状モデリング用のパラメータ.
\( A \):アタックタイム "attack time",
開始(無音)から最大音量までの起動時間
\( D \):ディケイタイム "decay time",
最大音量から持続音量までの減衰時間
\( S \):サステインレベル "sustain level",
持続音量
\( R \):リリースタイム "release time",
終了(消音)までの減衰時間(残響 or 余韻の時間)
ADR は時間ですが,S だけは音量ですよ.
包絡線形状の定義式の例:
\[ e(t) = \left\{\begin{array}{ll}
1 - \exp\P({-5t/A}) & ,\quad t = [0, A) \\
S + (1 - S) \ \exp\P\{{-5(t-A)/D}\} & ,\quad t = [A, G) \\
S \ \exp\P\{{-5(t-G)/R}\} & ,\quad t = [G, L)
\end{array}\right. \]
なぜ,指数関数とか面倒なことを?
実際のアナログシンセでは電気回路の過渡現象を利用していたので...
この例では,それをシミュレートしているだけですよ.
もちろん,自然界の振動の減衰が一般に指数関数的だったりもします.
\( G = L - R \):ゲートタイム "gate time",開始から持続終了までの時間
鍵盤入力のオンからオフまでの時間ですよ.
\( L \):デュレーション "duration" or "length of time",開始から終了(消音)までの時間
音声出力の開始から終了までの時間ですよ.
シンセサイザ用語的には,ゲートタイムもデュレーションも同じく音長とのことですが,
本科目では両者を区別しますよ.
区別しないと WAV ファイル化の際に困るので...
WAV ファイルの収録時間全体(無音区間あり)をデュレーションとします.
振幅変調の適用:
\[ e(t) \ x(t) \rightarrow x(t) \]
プログラミング〜実行:synth.c を改造し,
何らかの包絡線形状を適用し,
自分好みの楽器音を生み出そう.
$ vim synth.c
$ cc ...
...
上記の定義例に拘る必要はありません.
現実の楽器は様々です.
とにかく,前半に強く,後半に弱まるように,
強弱を付けるだけで充分でしょう.
例えば,お手軽にピアノっぽくするには,
\( D \) だけ考慮(\( A = S = R = 0 \) と大胆に近似)して,
\( e(t) = \{ \cos (\pi t / L) + 1 \}/2 \) とか
\( e(t) = 1 - t/L \) とかがオススメです.
課題への準備:
自分好みの音色の synth.c が完成したら,
単音の WAV ファイルを用意しておこう.
$ for $note in C D E F G A B C5
> do
> ./synth $note > $note.wav
> paplay $note.wav
> done
# ドレミファソラシド♫
$ ls *.wav
A.wav B.wav C.wav ...
# 単音のラ,シ,ド,...のWAVファイル
for の後の「$」は余計でした.削除・訂正(2024.04.25)
本日の課題
複数の WAVファイルを入力し,
音を連結(つなぎ合わせ)出力するプログラム seq.c を作成せよ.
要するに,シーケンサー(自動演奏器)です.
複数の WAVファイルを入力し,
音を混合(重ね合わせ)出力するプログラム mix.c を作成せよ.
要するに,ミキサー(混合器)"mixer" ですね.
複数音で和音とか,複数楽器で合奏.
レポート提出
提出方法:
電子メール
宛先:yanagawa@kushiro-ct.ac.jp
件名:sp-1208
提出期限:12月15日(金)17:00
提出内容:(本文 )
学年・分野,出席番号,氏名
seq.c および mix.c のソースコード
(メロディの作成手順)
(工夫など)
メロディ作成手順の例:(お辞儀の曲として知られるカデンツの演奏)
$ ./mix C5.wav E.wav G.wav > CEG.wav # ドミソ
$ ./mix B.wav F.wav G.wav > BFG.wav # シファソ
$ ./seq CEG.wav BFG.wav CEG.wav | paplay # ジャーン⤴ジャーン⤵ジャーン⤴
次回予告
(c) 2023, yanagawa@kushiro-ct.ac.jp