インベーダーゲームを作ってみる(6)サウンド

インベーダーゲームの解説、最後はサウンドです。

インベーダーの効果音は7種類しかありません。

①インベーダーの行進音
②自機弾の発射音
③インベーダー命中音
④UFO出現音
⑤UFO命中音
⑥自機被弾音
⑦1500点での自機+1の音

オリジナルのインベーダーゲームは、これらの音を全て専用の回路で生成しています。
以下の記事にそれぞれの回路図が出ています。

Space Invaders sound emulation

これによると、①⑦は有名なタイマーICのNE556、④はTIのサウンドチップSN76477、②③⑤⑥はオペアンプLM3900を使っているそうです。また、②⑥はノイズ音が混じりますが、これはCMOS ICの発振回路とシフトレジスタで生成しているそうです。
説明には4066とありますが、シフトレジスタなので4006の間違いでしょう。こちらのマニュアルのP8のパーツリストにも4006とあります。)

俗にゲームの音を「ピコピコ音」と言いますが、インベーダーゲームのサウンドは後にピコピコ音の代名詞となるファミコンなどよりも、アナログ回路で生成した電子音の比率が高かったようです。
以下のビデオはおそらく実機だと思いますが、今聴いても味わいがありますね。(3:00~)

#180 Bally Midway SPACE INVADERS Arcade Video Game–with blacklight reflection TNT Amusements

まずはオリジナルの効果音を分析してみましょう。

最初は行進音です。

上のビデオの音声トラックを取り出し、Audacityで増幅・ノイズ除去したものを聴いてみます。
前半はそのまま、後半は2オクターブ上にトランスポーズしています。

音階は、「ラ・ソ・ファ・ミ」あるいは「ミ・レ・ド・シ」というように聞こえなくもありません。
いずれにせよ短調ですね。

インベーダーが減るに連れてテンポが上がりますが、音長(ゲートタイム)自体は変化せず、発音の間隔だけが狭まっていきます。
演奏速度としては、最後は最初の約10倍になるように設定しました。

次に、インベーダーに命中したときの「プチュン」という音です。
これも上のビデオから取り出したものを聴いてみます。
最初は通常再生、2番目は1/2倍速のスロー再生です。

波形を図にすると、下のようになっています。

iv10.png

全体で0.3秒ほどの音ですが、前半0.1秒くらいが低い音がさらに低くなる「プン」という音、その後は高い音からだんだん下がっていく「チューーーン」という音になっています。
また、末尾のところで同じ「チューーン」という音が、残響のようにもう一度小さく入っています。
なので、続けて鳴らすと「プチュンチュン」という具合に聴こえます。

このほか、UFO飛来音は4オクターブくらいの音域をサイン波でピッチを上げ下げするとそれらしくなります。
命中したときの音も、音程が低くなるだけで基本的には同様です。
なお、自機弾の発射音や自機爆発音は、ノイズ成分が必要なのでArduboyでは作るのは難しいです。

プログラム内での音声処理は、以下の通りです。
まず、各効果音の周波数の時間変化を配列に入れます。
データの末尾は「-1」とします。

int16_t snd1[5] = {   // laser
NOTE_B6, NOTE_C7, 0, NOTE_B6, -1
};

 

これとは別に、構造体で音の特性(ゲートタイム、再生速度、リピートするか等)と状態(再生中か等)を保持します。

enum SoundState {
SoundReady,
SoundPlaying,
SoundDone
};

struct sound_fx_t {
int16_t *data;    // array of frequencies, use -1 as end marker
uint8_t gate_time;  // note gate time in msec
uint8_t clk;    // ticks per note
boolean loop;   // true for loop play
uint8_t idx;
uint8_t clk_cnt;
enum SoundState status;
};

サウンドの状態は「Ready」→「Playing」→「Done」と遷移します。
それぞれ、再生前、再生中、再生完了を表します。

Playing状態では、再生速度(clk)ごとに配列のデータを1つずつ再生します。
clkが大きくなれば、演奏速度はゆっくりになります。

サウンド関連の関数は以下の6つを用意しています。

void sound_play(struct sound_fx_t *s)
loop()から各サウンドにつき1回ずつ呼び出すことでサウンドの状態を更新します。
音声チャンネルは1つだけですので、全ての効果音の処理をした結果として、基本的には最後に再生された音だけが聴こえます。
逆に言えば、サウンドの優先順位はsound_playを呼び出す順番で制御できます。後に呼び出されたものが優先です。

void sound_start(struct sound_fx_t *s)
サウンドの再生を開始します。(再生前→再生中へ遷移)

void sound_stop(struct sound_fx_t *s)
サウンドの再生を強制的に完了させます。(再生中→再生完了へ遷移)

void sound_restart(struct sound_fx_t *s)
再生が完了または中止されたサウンドを再び再生可能にします。(再生完了→再生前へ遷移)

boolean sound_ready(struct sound_fx_t *s)
サウンドが再生前ならtrue、そうでなければfalseを返します。

void sound_set_tempo(struct sound_fx_t *s, uint8_t t)
サウンドの再生テンポを指定します。再生はt+1 ticks(1/60秒)ごとに1データずつ進行します。0が最速です。

コメント