落書きノート

ふと自分が気になった事を書いてます

C言語でサウンドプログラミング

音は、「音の大きさ」、「音の高さ」、「音色」によって特徴づけることができます。耳で聞くだけでなく、音データの波形を目で見て観察すると、こうした「音の三要素」を詳しく調べることができます。

例えば、ギターは弦を弾いて音を鳴らす楽器ですが、音の大きさは音が鳴り出した直後に最も大きく、次第に小さくなっていくという特徴があります。一方、リコーダーは管に息を吹き込んで音を鳴らす楽器ですが、息を吹き込み続ける限り持続的に音が鳴り続けるという特徴があります。

リコーダーよりもギターの方がとげとげしい波形となります(図が用意できなかったのでなんとなくで…。)。ギターはかたい音色、リコーダーは柔らかい音色に聞こえますが、実は、波形の詳細は音色と密接に関係しており、波形を観察することでこうした楽器の音色を視覚的に把握することができます。

なお、これらはすべて周期的な波形となっています。実は、波形の周期は音の高さを表しており、周期が長いと低い音、周期が短いと高い音になります。

音の高さは、波形の周期の逆数として定義される「基本周波数」によって表すことができます。波形の周期をt0、基本周波数をf0とすると、両者の関係は式(2.1)のように定義できます。

f0_1_t0

一般に、楽器は「12平均律音階」によって調律されます。それぞれの音の高さは、音名を表すアルファベットと音域を表す数字を組み合わせて表記されます。音程が1オクターブ高くなると音の高さは2倍になります。

経験を積むと、音を聞かなくても波形を見ただけでどんな音なのか見当がつくようになっていくことと思います。なお、ここでは実際の楽器を例にとって音データの波形を観察してみましたが、こうした音データの特徴は、人工的に音を作り出す際のポイントにもなっています。

音を作り出す上で基本となるのは、「サイン波」です。基本周波数f0のサイン波は、式(2.2)のように定義出来ます。ここで、Aはサイン波の振幅で、この大小によって音の大きさを調節することができます。

2-2

後述するex2_1.cは、サイン波を生成してWAVEファイルに出力するプログラムです。このプログラムは、fsを8kHz,f0を250Hz,Aを0.25としています。ex2_1.cによって生成されたex2_1.wavを実際に聞いて、サイン波の音色を確認してみましょう。

実は、あらゆる周期的な波形は、周波数が整数倍の関係にあるサイン波を重ね合わせることによって合成出来ることが知られています。この時、最も小さい周波数が基本周波数となり、これによって音の高さが決まります。なお、基本周波数のサイン波を「基本音」、その整数倍の周波数のサイン波を「倍音」と呼びます。すなわち、i倍音の周波数hiは基本周波数f0によって次のように定義できます。

2-3

式(2.4)によって定義された「ノコギリ波」です。倍音の数を増やすと、エッジのはっきりとしたノコギリ状の波形を合成することができます。

2-4

後述するex2_2.cは、15倍音までのノコギリ波を生成してWAVEファイルに出力するプログラムです。このプログラムは、fsを8kHz,f0を250Hz,Aを0.25としています。

ex2_2.wavは、ex2_2.cによって生成されたノコギリ波です。この音データを実際に聞いてみると、基本周波数が同じであることから、ex2_1.wavのサイン波と音の高さが等しくなっていることがお分かりいただけるのではないかと思います。

ただし、音色については違いが見られます。サイン波は柔らかい音色に聞こえますが、ノコギリ波は音色が固くなっています。基本音に倍音を重ねあわせると波形が変化することになりますが、ギターとリコーダーの例と同様、波形が異なると音色も変化することになります。

ノコギリ波と同様、式(2.5)のようにサイン波を重ねあわせると、「矩形波」を作り出すことができます。式の格好は式(2.4)と同じですが、式(2.5)のiは奇数のみとなります。すなわち、矩形波はノコギリ波から偶数次の倍音を取り除いたものになっています。

2-5

ex2_3フォルダのex2_3.cは、15倍音までの矩形波を生成して、WAVEファイルに出力するプログラムです。このプログラムは、fsを8kHz,f0を250Hz,Aを0.25としています。ex2_3.wavは、ex2_3.cによって生成された矩形波です。この音データを実際に聞いてみると、ex2_1.wavのサイン波やex2_2.wavのノコギリ波と音の高さが等しくなっていながらも、音色については違いが見られることがお分かりいただけるのではないかと思います。

もちろん、実際の楽器と比べてノコギリ波や矩形波はきわめて単純な音に過ぎません。しかし、実は、これらはシンセサイザにおける音作りには欠かせない音源となっています。

なお、一昔前の家庭用ゲーム機は、こうした人工的な音をBGMの音源として利用していました。昨今は、実際の楽器の音データをメモリに蓄積することでリアルな音を鳴らすことが出来るようになりましたが、メモリが高価だった時代、コンピュータの音といえば、こうした人工的な音が一般的でした。

ex2_1.c

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "wave.h"

int main(void)
{
  MONO_PCM pcm1;
  int n;
  double A, f0;
  
  pcm1.fs = 8000; /* 標本化周波数 */
  pcm1.bits = 16; /* 量子化精度 */
  pcm1.length = 8000; /* 音データの長さ */
  pcm1.s = calloc(pcm1.length, sizeof(double)); /* メモリの確保 */
  
  A = 0.25; /* 振幅 */
  f0 = 250.0; /* 基本周波数 */
  
  /* サイン波 */
  for (n = 0; n < pcm1.length; n++)
  {
    pcm1.s[n] = A * sin(2.0 * M_PI * f0 * n / pcm1.fs);
  }
  
  mono_wave_write(&pcm1, "ex2_1.wav"); /* WAVEファイルにモノラルの音データを出力する */
  
  free(pcm1.s); /* メモリの解放 */
  
  return 0;
}

ex2_2.c

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "wave.h"

int main(void)
{
  MONO_PCM pcm1;
  int n, i;
  double A, f0;
  
  pcm1.fs = 8000; /* 標本化周波数 */
  pcm1.bits = 16; /* 量子化精度 */
  pcm1.length = 8000; /* 音データの長さ */
  pcm1.s = calloc(pcm1.length, sizeof(double)); /* メモリの確保 */
  
  A = 0.25; /* 振幅 */
  f0 = 250.0; /* 基本周波数 */
  
  /* ノコギリ波 */
  for (n = 0; n < pcm1.length; n++)
  {
    for (i = 1; i <= 15; i++) /* 15倍音までの重ね合わせ */
    {
      pcm1.s[n] += A / i * sin(2.0 * M_PI * f0 * i * n / pcm1.fs);
    }
  }
  
  mono_wave_write(&pcm1, "ex2_2.wav"); /* WAVEファイルにモノラルの音データを出力する */
  
  free(pcm1.s); /* メモリの解放 */
  
  return 0;
}