言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

SDL でピコピコサウンドを作ろう!

今更ですが、C 言語って良いですね。バイト列を直接触って絵を描いたり音を出したりすると、まさにタンジブル(笑)な感じがします。今日はファミコンサウンドにチャレンジです。サウンドというと、ややこしい OS とのやり取りが大変ですが、SDL を使うとその辺楽勝です。

やりたい事: 矩形波の音を鳴らす。とりあえずプーっとだけ鳴らします。

最初に矩形波とは何ぞや?という事から考えます。矩形波とは、ON と OFF が交互の並んでいるだけの単純な波形です。例えば、波長が 1 で、プラスマイナス 1 の矩形波を C で書くとこんな感じでしょうか。

double wave(double t)
{
  return t - abs(t) < 0.5 ? 1 : -1;
}

こういう風に波長と振幅を 1 にしておくと後で波形を変えたいときに楽かもという配慮です。あとはこれをサウンドドライバに送るだけの話。泥臭いグローバル変数とメインルーチンはこんな感じ。コマンドラインから一つ引数を受け取ってその周波数の音を鳴らそうとしています。

double Frequency;
SDL_AudioSpec Desired;
SDL_AudioSpec Obtained;

int main(int argc, char *argv[])
{
  if (argc < 2) {
    fprintf(stderr, "usage: %s frequency\n", argv[0]);
    return 0;
  } else {
    Frequency = atof(argv[1]);
  }

  Desired.freq= 22050; /* Sampling rate: 22050Hz */
  Desired.format= AUDIO_S16LSB; /* 16-bit signed audio */
  Desired.channels= 0; /* Mono */
  Desired.samples= 8192; /* Buffer size: 8K = 0.37 sec. */
  Desired.callback= callback;
  Desired.userdata= NULL;

  SDL_OpenAudio(&Desired, &Obtained);
  SDL_PauseAudio(0);
  SDL_Delay(200);
  SDL_Quit();
  return 0;
}

実際に音を鳴らすのは、Desired.callback で指定したコールバック関数です。ここで、フレームレート(Obtained.freq)と周波数(Frequency)、現在時刻(step)を元に、波長が1になるように時刻を正規化して wave を呼びます。

void callback(void *unused, Uint8 *stream, int len)
{
  int i;
  static unsigned int step= 0;
  Uint16 *frames = (Uint16 *) stream;
  int framesize = len / 2;
  for (i = 0; i < framesize; i++, step++) {
    frames[i]= wave(step * Frequency / Obtained.freq) * 3000;
  }
}

コンパイルして音を鳴らしてみましょう!

$ ./square.exe 400

プー。
うむ。良い音色である。
音楽っぽく。

$ echo 300 400 450 600 450 400 300 | xargs -n1 ./square.exe

波形をやさしく変えてみる。

double wave(double t)
{
  return cos(t * 2 * 3.14);
}

全体のソース。コンパイルの仕方: gcc -o square square.c $(sdl-config --cflags --libs)

#include <SDL.h>
double Frequency;
SDL_AudioSpec Desired;
SDL_AudioSpec Obtained;

double wave(double t)
{
  return t - abs(t) < 0.5 ? 1 : -1;
}

void callback(void *unused, Uint8 *stream, int len)
{
  int i;
  static unsigned int step= 0;
  Uint16 *frames = (Uint16 *) stream;
  int framesize = len / 2;
  for (i = 0; i < framesize; i++, step++) {
    frames[i]= wave(step * Frequency / Obtained.freq) * 3000;
  }
}

int main(int argc, char *argv[])
{
  if (argc < 2) {
    fprintf(stderr, "usage: %s frequency\n", argv[0]);
    return 0;
  } else {
    Frequency = atof(argv[1]);
  }

  Desired.freq= 22050; /* Sampling rate: 22050Hz */
  Desired.format= AUDIO_S16LSB; /* 16-bit signed audio */
  Desired.channels= 0; /* Mono */
  Desired.samples= 8192; /* Buffer size: 8K = 0.37 sec. */
  Desired.callback= callback;
  Desired.userdata= NULL;

  SDL_OpenAudio(&Desired, &Obtained);
  SDL_PauseAudio(0);
  SDL_Delay(200);
  SDL_Quit();
  return 0;
}