GPUSound入門(2) 音を鳴らすGLSLコード入門

## シリーズ

GPUSound入門
https://qiita.com/MachiaWorx/items/68f8691d0866e0e0ff68

またもや走り書きになります。

## お試しGPUSound

さて、まずはなんか書いてみましょうか。
今の所一番手軽なのが、Shadertoyというサイトで音を鳴らすことです。

実際のところ(準備が必要ですが)これをコピペして自前の環境で鳴らそうとすると鳴ってしまいますので、Shadertoyのところから書いてみることにします。

Shadertoyを開いて、New押下
ソースコードが書いてあるタブで「+」を押して「Sound」を追加すると下記のようなコードが記述されます。

“` GLSL:SampleCode
vec2 mainSound( float time )
{
// A 440 Hz wave that attenuates quickly overt time
return vec2( sin(6.2831*440.0*time)*exp(-3.0*time) );
}
“`
一度データを絞って説明していこうかと思います。
(exp関数については別の機会に)

### 何を記述してるか説明

Sin関数は問題ないかと思います。
周期を発生させることで一定の音が生成されます。

問題はどんな周期でどんな音になっているかという部分かと。

記述は以下の通り。

return sin( 6.2831 * 440.0 * time ) * exp(-3.0*time) );

これだと分かりづらいので、まずsin部分だけにしてしまいましょう。
以下のコードをコピペの上実行してみてください。

“` GLSL:SampleCode2
vec2 mainSound( float time )
{
// 改造
return vec2( sin(6.2831*440.0*time) );
}
“`

そうすると減衰が消えてます。
つまり、まず基本となる(音が発生する)コードがあって、それにプラスして音量を変更するコードが存在する形になります。

### サイン波が鳴る仕組み

次にSampleCode2について説明してきます。

sin( 6.2831 * 440.0 * time )

この部分は記述をまとめてあるだけで、実際のところは下記のようになります。

sing( 3.1415 * 2 * 440.0 * time )

順に説明します。

| 数値 |意味| 別名 |
|:—————–|:——————|:——————|
| 3.1415 |円周率 | PI |
| 2 |周期計算のための数値 | – |
| 440.0 | 音の高さ |音階、周波数|
| time |経過時間 | – |

ここでは、経過時間あたりどんな周期になっているかを全部計算して、
それに対応したサイン波を出力しています。

そもそも音っていうのが「物体の振動によって発生する」ものであって、
そのときの特定の周波数を音階として定義してる形になります。

あと、サイン波が「周期的な振動を行う」という計算なので、
音量を変えることなくずっと音が出力されることになるんですね。

で、もっかいSampleCode2を表示しときます。

“` GLSL:SampleCode2
vec2 mainSound( float time )
{
// 改造(ちょい記述変更しました)
return vec2( sin( 2 * 3.1415*440.0*time ) );
}
“`

数値の意味は下記の通り。(前掲)

| 数値 |意味| 別名 |
|:—————–|:——————|:——————|
| 3.1415 |円周率 | PI |
| 2 |周期計算のための数値 | – |
| 440.0 |音の高さ |音階、周波数|
| time |経過時間 | – |

で、必要な式は以下の通り。
これは単振動の式になります(t=時間ね)

f(t) = sin(ωt)
(ω:2πf、f:周波数)

つまり、SampleCode2では、「特定周期の音が減衰せずにずっと鳴ってる」という式になります。
また、今回のケースではtの値によってWAVを1サンプルずつプロットすると考えるため、tをいじることは殆どないです。

ここまで来ると初めて見るコードもそこまで怖いものじゃないかと思います。

### 音の高さをいじってみる

単振動なので、2*Pi部分は変更しないです。
ここで音階つけるのに必要なのは音の高さ、すなわち周波数部分になります。

試しに「440.0」って数値を880.0に変更してみてください。音が高くなるはずです。

あとはタイミングを計算して、特定の時間で指定した周波数が鳴る、という形で指定してしまえばメロディが作れます。
ということでちょいタイミングを調整したものを作ってみました。
(ブツ切れ感ありますが我慢してください)

“` GLSL:SampleCode3
vec2 mainSound( float time )
{
//BPM120の四分音符ごとにカウントを行う
float count = mod(floor(time/240.*120.),4.);

//上記カウントごとに指定する周波数を変更する
float inc = count * 1.05;

// サイン波を再生する(周波数は時間ごとに変化する)
return vec2( sin(2*3.1415*440.0*inc*time) );
}
“`

BPM計算は、以下の様にして行います。
1分あたりの四分音符の数=BPM
1秒あたりの四分音符の数はBPM/60
で、ここでは基準を60BPMにして、その何倍の長さか、という形で計算してます。
60BPMの場合、ちょうど長さが1分なので、比率計算しやすいんですね。

ということで上のcountの式では、四部音符の長さを計算して、
そのカウントが何拍目に該当するかだけを計算してるんですね。
(modにしてるのは拍数だけ取りたいから)

で、その拍数をデータソースとして周波数を変更しているわけです。(incの定義部分)

あとは既存のソースと同じで、最後に周波数を代入して出力してます。

ということで周期的に四分音符の長さでメロディを鳴らすことができました。
あとはこれをいっぱい並べていくことで曲になります。

## おわりに

ちょい駆け足気味で書いてみましたがいかがでしょうか。
以前は結構書いて放置したりして書く気がなくなったケースが多かったので、
記憶が残っているうちに書いてみました。

これでメロディは作れるようになるかと思いますので、
あとは音色・ドラム・和音・フレーズ制御を書いて終わりにしようかと。