GPUSound入門

ご無沙汰しております、Machiaです。

1年半以上シンセサイザー入門書いてませんが、その間何してたかというと仕事が忙しくなったりその状態で趣味のゲーム開発したり作曲したりTDF合わせでついうっかり4kb introを作り始めたりしてそんな余裕ねえじゃねえかアハハという状況です。

今回はGPU Soundについて記載残しときます。
自分はGLSLで書いてるんですが、別にGLSLに限ったことでもないのでこの名前にしとこうかと。

# GPUSoundとは

GPUSoundとは、とりあえず「GPUで音関連の処理を計算すること」にしときます。
一般用語でもないし、こういうのってGPGPUで括られると思うし。
あとShadertoyの記述に倣ったところがあります。

自分のヤツで恐縮ですが、先日開催されたTokyoDemoFestで提出した4k introをShadertoyに移植したページについてリンク張っておきます。

a Stranger – imported
https://www.shadertoy.com/view/XtVfWy

鳴らしているメソッドは少し違ったりしますが、ソースコード書いて曲が思いっきり鳴ってるかと思います。

## 使用する言語等

(2018/12/10 5:09追記)

使用する言語・ライブラリ等は以下の通りです。

* OpenGL/GLSL
* Win32API(他にもオーディオ再生用のAPIがあれば移植は難しくないはず)
* Shadertoy(WebAudioAPI?)

CUDAとかOpenCL等GPGPU用のライブラリは利用してません。
これは、自分が並列計算のプログラムほぼ書いたことないのと、単純に4kb introに詰め込みたくて利用するライブラリを最小限にしたから、です。

あと上記の副次的効果として、コンパイルの時間をそれほど待つことなく計算をゴリゴリ書いて進められる、計算が間違ってたら即座に直す力技OKな環境になっております。

## 使用する技術の特徴

GPUの特徴として、以下が挙げられます。

・計算が並列で実行される
・CPU-GPU間の協調は取れないケースがある(ここらへん別のライブラリ使うと大丈夫になってくるかもしれない)
・リアルタイム用途だと計算結果を保証しない

3番目は、GLSLで負荷の高い描画をしていると実感できる部分ですが、データが不完全になることでティアリングや描画が間に合わないケースがあります。

(勿論現在の描画はバッファリング形式なので描画が完成するまで待つ手もありますが、そうするとなかなか絵がでてこないことに・・・)

今回のシンセは、単純に「曲のレンダリングを行うのにGPUを利用する」だけなので、構成は結構シンプルで、計算順序をそれほど考えなくていいです。
(とは言っても理解に時間を要した・・・結局3日くらいかかった)

問題なければプロジェクトファイルも一式置く考えです。
せっかくなので、GPUで曲書く人増えろ!と。

## 他で使われてる事例

Shadertoy

デモシーナーとかすごい人の総本山感あるところ。
うちの環境だとシェーダ負荷がすごすぎてブラウザが落ちることもあるので閲覧注意。
記述はここで書ける文法を参考にしてますが、一部オリジナルです。
(オリジナルでも最終的に辻褄が合えばOKの考え)

Fragment

どうも加算合成の他色々できるシンセで、サーバクライアント型で制御してるみたい。

## GPUでシンセ作る処理の手順

以下の手順を実施。

1. OpenGL上でデータ格納用バッファを確保
2. シェーダ命令を1回だけ投げる
3. TransformFeedbackでデータをVBOに書き込む
4. 書き込んだデータをWAV再生用APIで読み込む

### 手順

#### 1. OpenGL上でWAVデータ格納用バッファを確保

これはOpenGL上でVBOを確保します。
つまりVBO=WAVデータにするわけです。

VBOとは、頂点バッファオブジェクトの事です。
本来「どの点を結んで図形をつくる」というのはCPU側で行うわけですが、
CPU→GPUの転送コストを考えると、大量の点があったときに処理落ちが起こります。
そういうのを回避するために、「頂点をGPU側で持っておけばいいんじゃね?」という考えからかVBOやFBO(フレームバッファオブジェクト)等、GPU側でリソースを持つ機能が実装されています。
CPU-GPU間の転送コストによるオーバーヘッドを防ぐ、という使い方で利用されるみたいです。
ただ、その分CPUでの制御ができない形になるので、コントロールをどう行うかがキモかと。
自分はあまり詳しくないのでここらへん識者に伺いたいところですが。

#### 2. シェーダ命令を1回だけ実行する

これは、描画に反映するためではないため、一度だけ実行する形にします。
あくまでデータ書き込みのためのシェーダ、という位置づけですね。

主にこの部分でソースコードの解釈が行われます。

注意なのが、データ側は「BPM等の速さ情報を持っていない」事です。
すなわち、データ書き込みのときに、このくらいのBPMだからこの音、というのをシェーダ側で指定する形になります。

計算式は、1秒あたりのデータが44100byteとして、「小節数+データの割合 = gl_VertexID / 44100.0」時点でのデータを書き込むことになります。

例:gl_VertexID = 22050の場合、22050/44100=0.5
つまり0.5秒ということになる。
ここ時点で120BPMではどのくらいの拍子か、どんなティックで分けて再生する/しない等を書き込んでいく形になる。

この部分はGLSLの書き方に関連する部分で、内容が膨れそうなので別記事にする予定。

#### 3. TransformFeedbackでデータをVBOに書き込む

2.で実行したシェーダ内では、Out変数としてTransformFeedbackで指定した変数(構造体)を定義できるので、ここにデータを書き込むことになります。

#### 4. 書き込んだデータをWAV再生用APIで読み込む

3.で出力されるデータは簡単に言ってしまうとWAV形式なので、メモリからWAVを読み込む形で再生することが可能。

## ざっくりとした説明おわり

ざっくりとこんな感じになります。
正直OpenGLの知識が必要になるので、拡張機能の取扱と相まってとっつきづらい分野ではありますが、一度書いてしまうと後は使い回すことが容易な上に、拡張機能はライブラリ全部読み込む形式ではないため、必要なものだけつまみ食いする形で実装可能です。

余裕があれば続きかこうかと思います。
(これから冬コミの準備・・・orz)

## 参考資料

GLSL作曲入門
https://qiita.com/notargs/items/be2fa153e62e3554a773

GLSL作曲講座
https://qiita.com/notargs/items/2ffe487cc6cb3844fc21

GLSLで音楽(はじめに)
https://qiita.com/gaziya5/items/e5d058c20bc75c72b5a2

その他Shadertoyの音楽投稿とか。