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)
自分向けメモレベルで書いてしまいました。
もう少し時間ができましたら、丁寧にまとめたいと思います。