Video要素の音声をWeb Audio APIのAnalyserNodeで解析してWebGLで動画のポストプロセスと連動

26/11/2018

概要

html5のVideo要素をWeb Audio APIとWebGLであそんでみました。
createMediaElementSourceでvideoの音声を取得、
analyserNode経由で解析した音声データをWebGLのシェーダーGLSLと連動させて
videoのテクセルを操作してリアルタイムでグリッチっぽいポストプロセスかけてます。
色々用語あっているのか不明ですが。

とりあえずの結果

色々相談重なって今月後半は忙しくなるぞーって思って片手間にゴニョゴニョやってたら未だ作業が始まらず。
フリーランスあるあるですがせっかくなので記事にしてみます。

素材動画は恥ずかしながら趣味でやってるバンドのリハーサル風景です。

Web Audio APIもスマホだと一回タップとか挟まないと制限があるのでロード用のUI入れてます。



再生位置(変更だけ)

こうゆう系僕の中ではmax/mspとかopenFrameworksとか利用する物という認識だったのですが
(あと使った事ないけどもSuperColliderとか?)

Audio API/WebGL使ってブラウザで結構さくさく動くので驚きです。
GPU処理っていうのが結構大きいのでしょうか?

[ブラウザ対応]

ブラウザ対応とか細かい所はみてないので最新のクロームとかでお願いします。
端末によっては見れないとか処理重いとかあると思います。
誰も興味ないと思いますが見れないよって場合に一応youtubeにもアップしてます。

こうゆうリアルタイム系の動画をデータとして形にするってどういうのが一般的なのだろうか、
サイズ大きいとquicktimeでフレーム落ちるので小さめです。

audio/analyserNode

video要素からcreateMediaElementSourceでaudioのsource取得して

this.video = document.createElement('video');
this.source = this.audioCtx.createMediaElementSource(this.video)
this.analyserNode = this.audioCtx.createAnalyser();
this.analyserNode.fftSize = 1024; //FFTサイズ
this.times = new Uint8Array(this.analyserNode.frequencyBinCount); //波形データ入れる配列Uint8Arrayで用意する
this.freqs = new Uint8Array(this.analyserNode.frequencyBinCount); //波形データ入れる配列Uint8Arrayで用意する

requestAnimationFrameとかでアップデートします

update(){
  window.requestAnimationFrame(this.update.bind(this));
  this.analyserNode.getByteFrequencyData(this.freqs); //周波数領域
  this.analyserNode.getByteTimeDomainData(this.times); //時間領域
}

FFT

Wikipediaみても意味不明です。

フーリエ変換(多分離散フーリエ変換?)に関しては久々見ても窓関数や虚数とかしっかりとは良く分かってないのだが、
昔max(msp)触りながら下記のページとか参考に勉強させてもらって何となく自分の利用したいようには使えてるということで良しとします。

どこかでギター演奏の達人がギター本体やエフェクターとかの工学や細部を理解する必要はかならずしも無い。
とかの文言みかけて妙に納得したので甘んじて受け入れます。

ちなみに誰も興味ないと思いますがPCにlive/max起動して演奏中足でグラニュラーシンセの操作とかしてます。

描画側 WebGL/THREE

描画はWebGLです。THREE使ってます。
かなり抜粋ですがTHREE.VideoTexture使えば割と簡単に動画のテクスチャ利用ができました。

const uniforms = {
  'uTex': {
  type: "t",
  value: new THREE.VideoTexture(window.glitch.audio.video)
};
// ~~~~
const material = new THREE.ShaderMaterial({
  vertexShader : vertex,
  fragmentShader : fragment,
  uniforms : uniforms,
});

あとEffectComposerが分かりやすそうだったので利用しました。
ShaderPassにGLSL渡します。
GLSLもUV座標とかマトリックスとか何となーくの多少の知識でサンプル見ながら何とかとかします。
ポストプロセスなので多分fragmentShaderだけ変えたり加えたりしてればなんとなく変わります。

this.composer = new EffectComposer(this.renderer);
this.composer.addPass(new RenderPass(this.scene, this.camera));
this.effectPass = new ShaderPass("ここにGLSL");
this.composer.addPass(this.effectPass);

あとはまたrequestAnimationFrameとかでaudioの解析データに合わせて
uniformsとかに数値入れれば描画結果が変わります。

update(){
  window.requestAnimationFrame(this.update.bind(this));
  this.renderer.setClearColor(0x000000, 1.0);
  this.renderer.clear();
  // -------------------------
  this.effectPass.uniforms.hoge.value = store.hoge;
  // uniformsとかにaudio解析の結果とかの数値入れてrender
  // -------------------------
  this.composer.render(this.clock.getDelta());
}

描画と三角関数とかノイズ

GLSLに限らずですが描画に関しては色々な使い方あるとは思うのですが、
グリッジとかこうゆうアブストラクトな表現?って言い方であってるのか分からないけども
利用する場合とかもsin/cosの三角関数とかパーリンノイズとかの疑似乱数の使い方が重要になるのかなと個人的に思います。
例によって完全に理解はできないけども利用だけできれば個人的にはありだと思っています。

以上です。