月 の 上

VEDA 2.4: GLSLで音楽を演奏できるようになったぞ!!!

こんにちは id:amagitakayosi です。
Atom用GLSL実行環境 VEDA を開発しています。

github.com

昨日リリースしたVEDAの最新版で、GLSLで音楽を演奏できるようになりました!

VEDAでは、この機能を Sound Shader と呼んでいます。
mainSound 関数に時刻から音声を合成する処理を書くことで、GPU上で音声合成できてしまいます!

Sound Shaderの使い方

  • mainSound() 関数を定義
  • alt-enter で実行
  • alt-. で停止

普通のフラグメントシェーダーや頂点シェーダーを ctrl-enter で実行すると、映像と音声を同時にGLSLで生成できます。

Shadertoyとの違い

Sound Shader機能は、ShadertoyのSoundバッファと同様の機能です。
基本的にShadertoyのコードがそのまま動きますが、以下のような違いがあります。

長さを変更できる

Shadertoyでは、生成する音声の長さは180秒固定でした。
VEDAでは、実行したいGLSLファイルの先頭に /*{ soundLength: 10 }*/ 等と指定することで、生成する音声の長さを変更できます。

ループ再生される

Shadertoyでは、180秒を過ぎたら音声が停止してしまいます。
VEDAでは、音声は soundLength の長さでループするようになっています。

ループ再生しつつ徐々に演奏内容を変更できるため、よりライブコーディングに向いた仕様となっています。
音声を停止したい時は alt-. を入力してください。

音声ファイルをロードできる

mp3及びwav形式のファイルをテクスチャとして読み込み、GLSL上で利用できます。

loadSound にテクスチャ名と時刻を渡すと、音声ファイルのその時刻の値が取得できます。 時刻の値を変更することで、再生速度を変更したりもできます。

実装解説

先月のWebGLアドベントカレンダーの記事で、ShadertoyのSound Shaderの実装について解説しました。

blog.gmork.in

VEDAでは、レンダリングのタイミング等は少々異なりますが、基本的にはShadertoyと同じ処理をしています。
大まかな流れは以下の通りです。

  1. 必要なサンプル数を計算する
  2. 音声バッファを作成し、再生を開始する
  3. 必要なサンプルが集まるまで、以下を繰り返す

1. 必要なサンプル数を計算

soundLength から、必要なサンプル数を計算します。
soundLength = 10, sampleRate = 48000 の時、必要なサンプルは 480000 個となります。

2. 音声バッファを作成し、再生

次に、生成したデータを格納するための音声バッファを作成します。

Web Audio APIの AudioBufferSourceNodeでは、Uint8Array で音声を生成できます。
今回は Uint8Array(480000) することになります。

AudioBufferSourceNodeでは、再生開始後にバッファにデータを詰める事もできます。
そのため、レンダリング前に再生を開始することで、ユーザーの操作から再生までのタイムラグを抑えています。

3. レンダリング

続いて、GPUで音声のレンダリングを開始します。
一度のレンダリングでは必要なサンプルが集まらないので、繰り返しレンダリングする必要があります。

VEDAでは、アニメーションのラグを回避するため、一度にレンダリングするサイズを小さくしています。
具体的には 32x64 としているので、音声をすべて生成するには 234 (= 480000 / (32 * 64)) 回のレンダリングが必要です。

レンダリングでは、各ピクセルごとに mainSound(time) を実行し、各時刻に対応する音声サンプルを計算します。
time の値は、ピクセルの位置とレンダリング回数から求めます。 例えば、(x,y) = (10, 20) のピクセルの3回目のレンダリングでは、

10 + (20 * 32) + (32 * 64 * 3) = 6794

より、6794個目のサンプルを計算することになります。

レンダリング結果はRGBAで出力されるので、計4byteとなります。
mainSound の結果はステレオであり、floatの値が2つ生成されますが、画像に出力する際に1byteずつ出力してしまうと精度が悪くなってしまいます。
そのため、次のようにして、各チャンネルの値を2byteに分割して保存するようにします。

vec2 v = mainSound(t);
vec2 h = floor(v/256.0)/255.0;
vec2 l = mod(v,256.0)/255.0;
gl_FragColor = vec4(h.x, l.x, h.y, l.y);

画像を生成したら、これを再度JSで音声に変換してあげます。

outputDataL[i] = (pixels[i * 4 + 0] * 256 + pixels[i * 4 + 1]) / 65535 * 2 - 1;
outputDataR[i] = (pixels[i * 4 + 2] * 256 + pixels[i * 4 + 3]) / 65535 * 2 - 1;

これを繰り返すことで、VEDAで alt-enter を押したら即再生を開始し、アニメーションを続けつつ音声を生成することができるのです。

音声ファイルをロードする実装

音声ファイルは、JS側で画像に変更してテクスチャとしてGPUにロードしています。
この時、画像から音声に変換するのとちょうど逆の処理をしてあげれば良いのです。

// 音声データを格納するUint8Arrayを作成
const array = new Uint8Array(_constants.SAMPLE_WIDTH * _constants.SAMPLE_HEIGHT * 4);

// 音声を画像に変換
for (let i = 0; i < c0.length; i++) {
  const off = i * 4;

  // -1〜1 の値を 0〜65536 に変換
  const l = c0[i] * 32768 + 32768;
  const r = c1[i] * 32768 + 32768;

  // 4bytesに分割
  array[off] = l / 256;
  array[off + 1] = l % 256;
  array[off + 2] = r / 256;
  array[off + 3] = r % 256;
}

// Uint8Arrayからテクスチャを作成
const texture = new THREE.DataTexture(array, _constants.SAMPLE_WIDTH, _constants.SAMPLE_HEIGHT, THREE.RGBAFormat);

簡単ですね!

現在、音声テクスチャのサイズは 1280x720 固定にしています。
その為、ロードできる音声の長さは19.2秒までとなっています (サンプルレートが48000の場合)。

AtomDAWになる日も近い……!!!

ところで

VEDAはもうすぐ100 stars!!
いますぐ fand/veda を Star してください!!!

削ぎ落として余裕を持つ

年始になると、毎回やりたいことリスト作るんだけど、やりたいこと多すぎるんだよな。

できることは少ない。
かといって、何ができて何ができないかは、その時になってみないとわからない。

去年のテーマは「切り替えをがんばる」だった。
いろいろと変化の大きい年だったから、その中できちんと生き抜く、みたいな所を目標にしていた。

今年のテーマは「削ぎ落とす」だ。
やりたいこと、やらなきゃいけないことを見極めて、不要なものは削る。
必要な時に動けるマージンを確保する。

自分に付いた余分な属性を振り落として、本当にできることを形にする。

2017年振り返り

結婚

2016年に交際を始めた相手と結婚した。 昔は自分が結婚するとは思ってなかったけど、なるようになるもんだな。

毎日一緒に料理したり旅行したり、楽しく生活してます。

VJ活動

VJというより、GLSL活動なんだけど。

「フロントエンドのスキルを増やすぞ」という理由でWebGL学習を始めたら、GLSLの世界にどっぷりにハマってしまった。

VJツール VEDA を開発し、DJイベントに出演したり、Node学園祭のパーティを開催したり、GLSLスクールの講師をやったりした。

去年の目標で「趣味開発を増やす」というのがあったんだけど、今度は見事に趣味活動に全振りになったなあ……。

去年の記事のブクマ数ランキング

累計1668ブクマでした。 GLSLの記事ばっかり……

タイトル
1 AtomでVJできるパッケージを作った
2 インターネット溶かすボタンできた
3 expo.ioを使ってリアルタイムにReact Nativeアプリを開発する
4 毎日GLSLでアニメーションを作ってる
5 http://blog.gmork.in/entry/kao=CLIで顔文字検索してクリップボードにコピーする奴作ったヾ(๑╹◡╹)ノ"♡
6 Atom用VJ環境 VEDA 1.0リリース
7 Scala入門としてCLIツールを作り、Scala.js, React, ScalaCSSでサイトを作った
8 スクリーンショット/GIFアニメ作成技術が集まるスレ
9 AtomエディターでVJしました
10 Markdown→はてな記法変換ツール『md2hatena』を作った

generated by 年間ブックマークランキングジェネレーター

2018年は?

今年の具体的な目標は立てません。
やりたいことはたくさんあるけど、本当にやりたいことをやりたい時にやる。

技術的には、UnityとかTouchDesignerとか、パフォーマンス/インスタレーション系の技術を新たに身に着けようと思ってる。
余裕があれば、TidalCycles経由でHaskellを覚えたりするかもしれない……けど、まあ別にできないならできないでいいや。

変に焦って勉強して時間なくなるより、日々に余裕をもって、インプットを増やしたいな。



今週のお題「2018年の抱負」