SwiftとSpriteKitで、スペクトログラムを描写するプログラムを書いています。
手順としては
①オーディオファイル読み込み ここ
②FFTでスペクトラル分析 非常に古いですがここ
③周波数領域データを[UInt8]データに格納
④[UInt8]をCGImageに変換 ←この記事
⑤CGImageをtextureとしてShaderに送る
⑥Shaderでスペクトログラム描写
完成イメージ
(Gif埋め込みが上手く行かなかったためYoutubeです。)
動画のように、軽い動作でスペクトログラムを拡大縮小・スクロールできるようにしつつ、せっかくだからAppleのライブラリを使いたいというのが趣旨です。
var buffer:[UInt8]! // spectrogram data
var texture:CGImage!
texture = createTextureFromArray(src: &buffer,
w: bufferWidth,
h: bufferHeight,
toW: textureWidth,
toH: textureH)
buffer はFFTでスペクトラル分析した周波数領域データになります。
サイズはデフォルトでは([FFTフレーム数]x [FFTSIZE / 2])になりますが、スペクトラムとして表示する周波数幅を小さくする際は([FFTフレーム数]x [FFTSIZE / 4])など適宜調整します。
bufferサイズはスペクトラル分析データに依存しますが、それをそのままShaderメモリ領域に送ると都合が悪いので、適宜サイズ調整しつつ、それに合わせてスケールアップ・ダウン処理を行います。
texture size はSpriteKitのSceneサイズと同じにすることで、スペクトラルを効率よく画面に描写することが可能になります。
func createTextureFromArray(src:inout [UInt8], w:Int, h:Int, toW:Int, toH:Int)->CGImage?{
let src = array2vImageBuffer(src: &src, w: w, h: h)
var dst = try! vImage_Buffer(size: CGSize(width: CGFloat(toW), height: CGFloat(toH)), bitsPerPixel: 8)
scaleBuffer(source: src, destination: &dst)
guard let format = vImage_CGImageFormat(bitsPerComponent: 8,
bitsPerPixel: 8,
colorSpace: CGColorSpaceCreateDeviceGray(),
bitmapInfo: CGBitmapInfo(rawValue: 0)) else{
return nil
}
let img:CGImage? = try? dst.createCGImage(format: format)
dst.free()
return img
}
[UInt8]型のArrayをAccerelateライブラリを使用してvImage_Buffer型に変換し、スケール処理を実行、その後でCGImageに変換しています。
vImage_Bufferはプログラマが明示的にメモリ解放する必要があるため、dst.free()を読んでいます。
この処理よって、スペクトラルデータが、bufferサイズからテクスチャーサイズに変換されたCGImage型として保存されました。
最後にUniformを介してShaderへTextureデータを送ります。
private var uniformBuffer:SKUniform!
self.uniformBuffer = SKUniform(name: "buffer",
texture:SKTexture(cgImage: texture))
self.shaderSource.addUniform(self.uniformBuffer)
自分向けメモレベルで書いてしまいました。
もう少し時間ができましたら、丁寧にまとめたいと思います。