大したネタでもないですが、この前ふとこれやりたいなと思ってできたのでついでにブログに書いてみます。
DJ配信
みなさんやってますよね。え、やってない?ではいますぐDJ機材買いましょう。
それはそうと、昨今STAY HOMEの影響でDJの人もどうやらクラブでイベント全然できなくなって、仕方ないからDJ配信するか~とTwitchなどで盛り上がってるようです。
僕はただのインドア系のWebアプリケーションエンジニアなので、DJなんてパリピなものよく知らないんですが、家にたまたまDJ機材があったので、配信とかしてみたいな~と思いはじめつつ、OBSというソフトをインストールしたりWebカメラをセットアップしたりしてみてるのですが、DJ中の自分の様子をカメラで写しても盛り上がりにかけることに気づきました。
こんな映像がインターネットに流れてきても絶対にみようという気になれない。かといって、DJはDJしている間手が離せないはずで、DJしながら何かしら映像をリアルタイムで工夫する(VJをする)というのは難しそう。
と考えていった結果、「映像が勝手にいい感じになればよいのでは?」と気づきました。
前提
このへんのものが必要です。
- 音が鳴るものを持っている
- パソコンの音声入力にその音が鳴るものを流すことができる
- パソコンにGoogle Chromeなどのブラウザがインストールされている
- パソコンにOBSというソフトがインストールされている
Web Audio APIを使ってマイクの音に合わせてオーディオスペクトラム的なやつを出す
これ絶対できるはず(大昔にどこかで見た記憶がある)ということでググった結果色々でてきた。
このへんのコード真似すれば完成します。色々試した結果、この3つ目のサイトの「Creating a frequency bar graph」が良さそうということで真似して作りました。
ローカルの適当なところに、htmlファイルとJSファイルとCSSファイル作って、開いたら全画面で波形が出るように作ります。
JSファイルについては、まずCanvas周りを初期化していきます。
const canvas = document.getElementById('wave'); canvas.width = document.body.clientWidth; canvas.height = document.body.clientHeight; const drawContext = canvas.getContext('2d');
オーディオストリーム取得するにはこういうふうにやればいけるそうです。
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(stream => { ... });
この中がむずくて雰囲気で書いてます。AudioContextからAnalyserNodeを作ると、ここから周波数などの値が取れる模様。dataArrayに入ったデータをもとに今回描画していきます。ここのポイントとしてはanalyserNode.fftSize = 128 の部分で、ここを2048みたいな数字にするとより細かいスペクトラムにできるようでした。ぼくは細かいのは微妙に感じたので今回128にしました。
const audioContext = new AudioContext(); const sourceNode = audioContext.createMediaStreamSource(stream); const analyserNode = audioContext.createAnalyser(); analyserNode.fftSize = 128; sourceNode.connect(analyserNode); var bufferLength = analyserNode.frequencyBinCount; var dataArray = new Uint8Array(bufferLength);
次にdraw関数を作ってその中でさっきのdataArrayの値をもとに描画していきます。アニメーションするときはrequestAnimationFrameが肝です。ここらへんも基本さっきのサイトを参考にしているんですが、setBackgroundColorByDataArrayっていうのが僕が新しく作った関数なのと、rgba ではなく hsl を使っているところがポイントです。hsl に i をもとにした値を指定してあげることによって、気軽に虹色っぽい感じにできるので、rgba より便利です。さっきのサイトのコードのままだと背景が単色で寂しいので、この上で、setBackgroundColorByDataArray という関数を作って音楽の感じによって背景色も変わるというのをやってみました。
function draw () { drawVisual = requestAnimationFrame(draw); analyserNode.getByteFrequencyData(dataArray); var barWidth = (canvas.width / bufferLength) * 2.5; var barHeight; var x = 0; setBackgroundColorByDataArray(dataArray); for(var i = 0; i < bufferLength; i++) { barHeight = dataArray[i] * 2; drawContext.fillStyle = "hsl(" + i*3 + ",80%,50%)"; drawContext.fillRect(x,canvas.height-barHeight,barWidth,barHeight); x += barWidth + 1; } }
setBackgroundColorByDataArrayをみてみるとものすごい雑なコードになってますが何がしたかったのかというと、「低音域が多い音がなってるときは背景を赤っぽく、高音域が多い音がなってるときは背景を青っぽく」したかった。
dataArrayの配列の中身をみていくと、最初に低音域の数値が入ってて、後ろにいくほど高音になるのがわかった。ということで、音域ごとの数値をすごいざっくり合計して、それをrgbaのそれぞれの値に適当に調整した値でマッピングしたら、勝手にいい感じになりました。その結果が以下のよくわからないマジックナンバーだらけのコードですね。
function setBackgroundColorByDataArray (dataArray) { var highFrequencies = dataArray[60] +dataArray[61] +dataArray[62] + dataArray[63]; var midFrequencies = dataArray[30] +dataArray[35] +dataArray[40] + dataArray[45]; var lowFrequencies = dataArray[0] + dataArray[5] + dataArray[10] + dataArray[15]; drawContext.fillStyle = 'rgba(' + lowFrequencies / 10 + ', '+ midFrequencies / 9 + ', '+ highFrequencies / 8 + ', 0.2)'; drawContext.fillRect(0, 0, canvas.width, canvas.height); }
結果はこんな感じです。ブラウザで開いたときに最初にマイクの許可が必要ですと聞かれるので許可します。あと、入力として多分デフォルトのマイクとかが最初使われるけど、Google Chromeの設定とかで入力ソース切り替えできるので、僕はDJ機材からの入力を指定してあげました。