Espectrograma de AVAudioPCMBuffer usando Accelerate framework en Swift
Estoy tratando de generar un espectrograma a partir de unAVAudioPCMBuffer
en Swift Instalo un grifo en unAVAudioMixerNode
y recibir una devolución de llamada con el búfer de audio. Me gustaría convertir la señal en el búfer a[Float:Float]
diccionario donde la clave representa la frecuencia y el valor representa la magnitud del audio en la frecuencia correspondiente.
Intenté usar el marco Accelerate de Apple, pero los resultados que obtengo parecen dudosos. Estoy seguro de que es solo por la forma en que estoy convirtiendo la señal.
Miréesta publicación de blog entre otras cosas como referencia.
Esto es lo que tengo:
self.audioEngine.mainMixerNode.installTapOnBus(0, bufferSize: 1024, format: nil, block: { buffer, when in
let bufferSize: Int = Int(buffer.frameLength)
// Set up the transform
let log2n = UInt(round(log2(Double(bufferSize))))
let fftSetup = vDSP_create_fftsetup(log2n, Int32(kFFTRadix2))
// Create the complex split value to hold the output of the transform
var realp = [Float](count: bufferSize/2, repeatedValue: 0)
var imagp = [Float](count: bufferSize/2, repeatedValue: 0)
var output = DSPSplitComplex(realp: &realp, imagp: &imagp)
// Now I need to convert the signal from the buffer to complex value, this is what I'm struggling to grasp.
// The complexValue should be UnsafePointer<DSPComplex>. How do I generate it from the buffer's floatChannelData?
vDSP_ctoz(complexValue, 2, &output, 1, UInt(bufferSize / 2))
// Do the fast Fournier forward transform
vDSP_fft_zrip(fftSetup, &output, 1, log2n, Int32(FFT_FORWARD))
// Convert the complex output to magnitude
var fft = [Float](count:Int(bufferSize / 2), repeatedValue:0.0)
vDSP_zvmags(&output, 1, &fft, 1, vDSP_length(bufferSize / 2))
// Release the setup
vDSP_destroy_fftsetup(fftsetup)
// TODO: Convert fft to [Float:Float] dictionary of frequency vs magnitude. How?
})
Mis preguntas son¿Cómo convierto elbuffer.floatChannelData
aUnsafePointer<DSPComplex>
pasar a lavDSP_ctoz
¿función? ¿Hay una forma diferente / mejor de hacerlo, incluso evitandovDSP_ctoz
?¿Es esto diferente si el búfer contiene audio de múltiples canales? ¿Cómo es diferente cuando los datos del canal de audio del búfer están o no intercalados?¿Cómo convierto los índices en elfft
matriz a frecuencias en Hz?¿Algo más que pueda estar haciendo mal?ActualizarGracias a todos por las sugerencias. Terminé llenando la matriz compleja como se sugiere en la respuesta aceptada. Cuando trazo los valores y toco un tono de 440 Hz en un diapasón, registra exactamente dónde debería.
Aquí está el código para llenar la matriz:
var channelSamples: [[DSPComplex]] = []
for var i=0; i<channelCount; ++i {
channelSamples.append([])
let firstSample = buffer.format.interleaved ? i : i*bufferSize
for var j=firstSample; j<bufferSize; j+=buffer.stride*2 {
channelSamples[i].append(DSPComplex(real: buffer.floatChannelData.memory[j], imag: buffer.floatChannelData.memory[j+buffer.stride]))
}
}
loschannelSamples
Entonces, la matriz contiene una matriz de muestras separada para cada canal.
Para calcular la magnitud usé esto:
var spectrum = [Float]()
for var i=0; i<bufferSize/2; ++i {
let imag = out.imagp[i]
let real = out.realp[i]
let magnitude = sqrt(pow(real,2)+pow(imag,2))
spectrum.append(magnitude)
}