canvas
の描画を動画にして保存したり、 動画に音を付けたりしました。
応用すると音と画像から新しい動画を作れるようになる。
まずは、音を元に canvas
を書き換えてみます。
ファイル / データ読み込み
前回の onDrop
を使ってローカルファイルを読み込みます。
input
の file
が取得できればやり方はなんでもいいです。
欲しいのは読み込みのための画像URL
と AudioBuffer
です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
const [img, setImg] = React.useState<any>(); const [ audio, setAudio] = React.useState<any>(); //画像ファイルを blob url にする const onDropImg = useCallback((acceptedFiles) => { if (acceptedFiles.length == 0) return; const createObjectURL = (window.URL || window.webkitURL).createObjectURL; setImg({ name: acceptedFiles[0].name, url: createObjectURL(acceptedFiles[0]), }); }, []); //音ファイルを AudioBuffer にする const onDropAudio = useCallback((acceptedFiles) => { if (acceptedFiles.length == 0) return; const fr = new FileReader(); fr.onload = () => { const arrayBuffer = fr.result; if (!arrayBuffer || typeof arrayBuffer == "string") return; const audioCtx = new AudioContext(); audioCtx.decodeAudioData(arrayBuffer, (audioBuffer) => { setAudio({ name: acceptedFiles[0].name, buffer: audioBuffer, }); }); }; fr.readAsArrayBuffer(acceptedFiles[0]); }, []); |
GLSL
の準備
gl-react
を使って GLSL
を書ける canvas
を用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import { Surface } from "gl-react-dom"; import { Node, GLSL } from "gl-react"; ... const glsl = require("./index.frag").default; ... <Surface width={300} height={300}> <Node shader={{ frag: GLSL`${glsl}` }} uniforms={{ t: img?.url }} ></Node> </Surface> |
1 2 3 4 5 6 |
precision highp float; varying vec2 uv; uniform sampler2D t; void main(){ gl_FragColor=texture2D(t,uv); } |
音の解析と描画のアップデート
AudioContext.createAnalyser()
で AnalyserNode
を作成。
フーリエ変換(FFT
)をして波形を GLSL
に渡します。
Analyer
サンプルでは fftSize
は 2048
でしたが、GLSL
に渡そうとしたところ ERROR: too many uniforms
エラーで怒られたので 1024
に減らしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
// 音の解析を設定したうえで再生する const [analyser, setAnalyser] = React.useState<AnalyserNode>(); const play = () => { const ctx = new AudioContext(); const src = ctx.createBufferSource(); src.buffer = audio.buffer; src.connect(ctx.destination); const analyser = ctx.createAnalyser(); analyser.fftSize = 1024; src.connect(analyser); setAnalyser(analyser); src.start(0); }; // analyser が更新されたら描画開始 const [data, setData] = React.useState<Uint8Array>(); let id = 0; const update = () => { if (!analyser) return; const data = new Uint8Array(analyser.fftSize); //音声波形データ analyser.getByteTimeDomainData(data); //周波数データ //analyser.getByteFrequencyData(data); setData(data); id = requestAnimationFrame(update); return () => cancelAnimationFrame(id); }; React.useEffect(update, [analyser]); ... <Surface width={300} height={300}> <Node shader={{ frag: GLSL`${glsl}` }} uniforms={{ t: img?.url, data: data }} ></Node> </Surface> |
1 2 3 4 5 6 7 8 9 10 11 12 |
precision highp float; varying vec2 uv; uniform sampler2D t; uniform float data[1024]; void main(){ vec4 c=texture2D(t,uv); for(int i=0;i<1024;i++){ float l=length(uv-vec2(float(i)/1024.,data[i]/256.)); if(l<.01)c=vec4(1,0,0,1); } gl_FragColor=c; } |
大分ごちゃついてきた。
ここから stop, pause, resume
あたりのイベント管理を追加する必要がありそう。
実際に動かしてみるとこのような感じになります。
もうちょっと見せ方を考えて GLSL
を書いた方がいいですね。
あとは getFloatFrequencyData()
などに変えて精度を上げてみたり。
とりえあず音のデータを GLSL
に渡せるようになったので、また色々試します。