全学計算機でC言語のプログラムから音を出す

#はじめに
この記事は筑波大学情報メディア創成学類「プログラミング実習II」履修者向けの記事で、その最終課題であるglutを使ったゲーム開発の際に、ゲームにBGM・効果音を付ける方法を解説したものです。ここでの説明はすべて全学計算機システム上での動作を前提としています。

透過画像を表示する方法の記事も書いています。こちらもよければどうぞ
[【プロ実II】openGLで透過画像(BB)を表示する](https://qiita.com/iciclize/items/a70321e6045eadbce2dc)

この記事ではFreealutならびにopenALというライブラリを使って全学計算機で音を出す方法を説明します。(音を止める方法は説明しません。ドキュメントなど読んでください。筆者も分かり次第追記するかもです。)

同科目履修の方は、音をつけようとするなら必ず動くものを完成させてからにしましょう。この方法がうまくいかないからといって頑張っていると時間を吸われます。

# Freealutを導入したい

2019年2月現在の全学計算機の環境では、Freealutライブラリを生成するためにopenALライブラリが必要です。また、openALライブラリを生成するためにALSAライブラリが必要です。

`ALSA`, `openAL`, `Freealut`の順番でライブラリをコンパイルし、最終的に共有ライブラリ`libalut.so`と`libopenal.so`が得られればプログラムから音が出せるようになります。

ただこれらのライブラリを自力でコンパイルするのは面倒なので、予めコンパイルしておいたライブラリをダウンロードして使えるようにしておきました。[自力でコンパイルする方法](#freealut-openal-alsaライブラリを自力でコンパイルする)も後述しています。

# コンパイル済みライブラリの導入

最終的に、プログラムを開発しているディレクトリを以下のような構成にします。それからMakefileを書き換えることで`Freealut`ならびに`openAL`が導入できます。

```
作業ディレクトリ/
　├ AL/
　│　└ al.h  alc.h  alext.h  alut.h  efx-creative.h  efx-presets.h  efx.h
　├ lib/
　│　└ libalut.so  libalut.so.0  libopenal.so  libopenal.so.1  libopenal.so.1.16.0
　├ Makefile
　├ A.h B.h C.h
　└ A.c B.c C.c
```

## コンパイル済みのライブラリをダウンロード
まずは、[openAL-alut.zip](https://drive.google.com/file/d/1w-vAQ7nHKti6F_5QF86bpcLKG7Gao9cT/view?usp=sharing)をダウンロードして、プログラムを開発しているディレクトリに展開します。するとそのディレクトリに `AL`と`lib` というディレクトリができます。

## Makefileを書き換える

授業のサンプルコードのMakefileを改変したものです。

```make:Makefile
TARGET=main

$(TARGET): main.o A.o B.o C.o
	cc -o $(TARGET) main.o A.o B.o C.o -L. -L./lib -Wl,-rpath ./lib -lglut -lGLU -lGL -lXi -lXrandr -lm -lopenal -lalut

.c.o:
	cc -c $< -I. -Wall

run: $(TARGET)
	./$(TARGET)
clean:
	rm -f *.c~ *.o *.h~ $(TARGET)
```

ポイントは以下の2点です。

- `cc -o ` のオプションに `-Wl,-rpath ./lib` `-L./lib` `-lopenal -lalut`をつけること
- `cc -c` のオプションに `-I.` をつけること


`-Wl,-rpath ./lib` はコンパイルされたプログラムが動的ライブラリの在り処(`./lib`)を知ることができるようにするための引数です。これがないとコンパイルできても実行時にエラーを吐きます。

`-L./lib` はコンパイル(リンク)時、コンパイラ(リンカ)に対しライブラリの在り処(`./lib`)を知らせるための引数です。

`-lopenal -lalut`はライブラリ`libopenal.so`と`libalut.so`とをプログラムに対してリンクするための引数です。ライブラリは`lib***.so`という特別な名前になっていて、`***`の部分を`-l`に続けて指定する必要があります。

# glut / alutを組み合わせてBGMを再生する

大雑把なサンプルコードを示します。(そのままコピーしても動作しません)

```C
#include <GL/glut.h>
#include <AL/alut.h>

int main(int argc, char *argv[])
{
  alutInit(&argc, argv);

  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA);
  glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
  glutCreateWindow("main");
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  init();

  glutMainLoop();
  return 0;
}

void game_start(void) {
  ALuint bgmBuffer, bgmSource;

  bgmBuffer = alutCreateBufferFromFile("BGMファイル.wav"); // 音声ファイルを読み込む. WAV限定.

  alGenSources(1, &bgmSource);
  alSourcei(bgmSource, AL_BUFFER, bgmBuffer);
  alSourcei(bgmSource, AL_LOOPING, AL_TRUE); // BGMがループするようにする

  alSourcePlay(bgmSource); // 音声を再生する. bgmSource番に登録された音を再生するみたいな感じでしょうね(適当)
}

void game_exit(void)
{
  alutExit();
  exit(0);
}
```

glutと同じように `main` 関数内で `alutInit(&argc, argv)` を呼ぶ必要があります。また、プログラムを終了する前に`alutExit()`を呼ぶ必要があります。

`game_start()`関数が呼ばれたと仮定すると、そこで`BGMファイル.wav`の再生が始まります。 `alutCreateBufferFromFile()`関数の引数に渡すファイルは`.wav`ファイルである必要があります。

`alSourcei(bgmSource, AL_LOOPING, AL_TRUE);`によって音楽がループ再生されるようになります。

# 効果音を再生する
上のBGMを再生するサンプルとあまり変わりありません。上のサンプルの`alSourcei(bgmSource, AL_LOOPING, AL_TRUE);`の記述を取り除くと音声の再生が単発になり、効果音として再生できます。

ただし、効果音を鳴らすタイミングで毎回 `alutCreateBufferFromFile()` を呼び出すのは避けるべきです。この関数は実行のたびにファイルを2次記憶から読み出すのでプログラムの実行が一時的に中断されてしまいます。

したがって、上のサンプルの
`ALuint bgmBuffer, bgmSource;`
を大域(グローバル)変数にしておいた上で、プログラムの初期化の際に、先に
`bgmBuffer = alutCreateBufferFromFile("音声ファイル.wav");`
`alGenSources(1, &bgmSource);`
`alSourcei(bgmSource, AL_BUFFER, bgmBuffer);`
を呼び出しておき、効果音を再生するタイミングで 
`alSourcePlay(bgmSource);`
のみが呼ばれるようにしましょう。

# Freealut, openAL, ALSAライブラリを自力でコンパイルする

先に述べたように、2019年2月現在の全学計算機の環境では、Freealutライブラリを生成するためにopenALライブラリが必要です。また、openALライブラリを生成するためにALSAライブラリが必要です。

`ALSA`, `openAL`, `Freealut`の順番でライブラリを自力でインストールしていく方法を説明します。

以下の手順は、筆者が10時間くらいかけてトライアンドエラーをうんざりするほど繰り返した挙げ句にたどり着いた解で、ちょっとでも改変するとビルドできない可能性が高いです。

## インストールディレクトリを作る
`~`(ホームディレクトリ, `/home/sXXXXXXX/`)に`mylibs`というディレクトリを作って、また、その中に`lib`, `include` というディレクトリを作って作業したいと思います。

```bash
~$ mkdir mylibs
~$ cd mylibs/
~/mylibs$ mkdir lib include
```

`~/mylibs`の中に `include`, `lib`ができます。

## ALSAライブラリの生成
alsa-libのソースファイルをダウンロードし、それをビルドして `libasound.so`, `libasound.so.2`, `libasound.so.2.0.0` を生成します。これは次にopenALをビルドするのに必要です。

```bash
~/mylibs$ wget ftp://ftp.alsa-project.org/pub/lib/alsa-lib-1.1.8.tar.bz2
~/mylibs$ tar -xjf alsa-lib-1.1.8.tar.bz2
~/mylibs$ cd alsa-lib-1.1.8/
~/mylibs/alsa-lib-1.1.8$ mkdir build
~/mylibs/alsa-lib-1.1.8$ cd build/
~/mylibs/alsa-lib-1.1.8/build$ ../configure --prefix=`pwd`
~/mylibs/alsa-lib-1.1.8/build$ make install
~/mylibs/alsa-lib-1.1.8/build$ cd ../../
```

## openALライブラリの生成

openal-soft_1.16のソースファイルをダウンロードし、それをビルドして、`libopenal.so`, `libopenal.so.1`, `libopenal.so.1.16.0` を生成します。これは次にFreealutをビルドするのに必要になるほか、音を鳴らすプログラムをコンパイルするときに直接必要になります。

```bash
~/mylibs$ wget http://archive.ubuntu.com/ubuntu/pool/universe/o/openal-soft/openal-soft_1.16.0.orig.tar.bz2
~/mylibs$ tar -xjf openal-soft_1.16.0.orig.tar.bz2 
~/mylibs$ cd openal-soft-1.16.0/
~/mylibs/openal-soft-1.16.0$ cp -r include/AL ../include/
~/mylibs/openal-soft-1.16.0$ cd build
~/mylibs/openal-soft-1.16.0/build$ cmake .. -DALSA_LIBRARY=~/mylibs/lib/libasound.so -DALSA_INCLUDE_DIR=~/mylibs/alsa-lib-1.1.8/include
~/mylibs/openal-soft-1.16.0/build$ make all
~/mylibs/openal-soft-1.16.0/build$ cp -r libopenal.so* ../../lib/
~/mylibs/openal-soft-1.16.0/build$ cd ../../
```

## Freealutライブラリの生成

freealutのリポジトリをGithubからcloneして、それをビルドし、`libalut.so`, `libalut.so.0`を生成します。これは音を鳴らすプログラムをコンパイルするときに直接必要になります。

```bash
~/mylibs$ git clone https://github.com/vancegroup/freealut.git
~/mylibs$ cd freealut/
~/mylibs/freealut$ cp -r include/AL ../include/
~/mylibs/freealut$ mkdir build
~/mylibs/freealut$ cd build/
~/mylibs/freealut/build$ cmake .. -DOPENAL_LIBRARY=~/mylibs/lib/libopenal.so  -DOPENAL_INCLUDE_DIR=~/mylibs/openal-soft-1.16.0/include/
~/mylibs/freealut/build$ make all
~/mylibs/freealut/build$ cp -r src/libalut.so* ../../lib/
~/mylibs/freealut/build$ cd ../../
```

## 生成したライブラリの確認

最終的に `lib/` `include/AL/` が以下のようになればライブラリの用意は完了です。

```bash
~/mylibs$ ls -l lib/
合計 2088
lrwxrwxrwx 1 s8101919 student1919      12  2月  7 02:53 libalut.so -> libalut.so.0
-rwxr-xr-x 1 s8101919 student1919   44432  2月  7 02:53 libalut.so.0
lrwxrwxrwx 1 s8101919 student1919      14  2月  7 02:52 libopenal.so -> libopenal.so.1
lrwxrwxrwx 1 s8101919 student1919      19  2月  7 02:52 libopenal.so.1 -> libopenal.so.1.16.0
-rwxr-xr-x 1 s8101919 student1919 2079504  2月  7 02:52 libopenal.so.1.16.0

~/mylibs$ ls include/
AL
~/mylibs$ ls include/AL
al.h  alc.h  alext.h  alut.h  efx-creative.h  efx-presets.h  efx.h
```

用意したライブラリとインクルードファイル群を以下のようにして、プログラムを開発しているディレクトリにコピーします。

```bash
~/mylibs$ cp -r lib (プログラムを開発しているディレクトリのパス)
~/mylibs$ cp -r include/AL (プログラムを開発しているディレクトリのパス)
```

プログラムを開発しているディレクトリに`AL`というディレクトリと`lib`というディレクトリができます。
こうすることで `#include <AL/alut.h>`を読み込めるようになります。

# おまけ
筆者はゲームを作るにあたって音楽から最初に作りましたが、音の再生の難易度が高すぎて結局音を付けて提出することができませんでした。

作成したゲームのソースコードは以下のリポジトリにアップロードしてあります。(音は付けていません)
[スズメバチに気をつけよう - https://github.com/iciclize/Suzumebachi](https://github.com/iciclize/Suzumebachi)

また、先に作ったBGMはSoundCloudにアップロードしました(蛇足)
[夜の都会を飛ぶ - https://soundcloud.com/icesofticon30th/e1kmhhkhzsdb](https://soundcloud.com/icesofticon30th/e1kmhhkhzsdb)


# 参考文献
[UbuntuでALSAライブラリを使って音のプログラミング - 
工房便り〜TSUBAKI GUITARS & LEATHER](https://tsubakicraft.wordpress.com/2017/07/31/ubuntu%E3%81%A7alsa%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E9%9F%B3%E3%81%AE%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0/)
[OpenAL/alutでサウンドを鳴らしてみる(実践編) - とど。](http://c-crad.wktk.so/td/?p=332)
Stack Overflowなどその他たくさん(絶望)