Spectrogram de AVAudioPCMBuffer utilizando Accelerate framework en Swift

Estoy tratando de generar un espectrogtwig de AVAudioPCMBuffer en Swift. AVAudioMixerNode un toque en un AVAudioMixerNode y recibo una callback con el búfer de audio. Me gustaría convertir la señal en el búfer en un dictionary [Float:Float] donde la key representa la frecuencia y el valor representa la magnitud del audio en la frecuencia correspondiente.

Traté de usar el framework Accelerate de Apple pero los resultados que obtengo parecen dudosos. Estoy seguro de que está en la forma en que estoy convirtiendo la señal.

Miré esta publicación de blog, entre otras cosas, como reference.

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

  1. ¿Cómo convierto el buffer.floatChannelData a UnsafePointer<DSPComplex> para pasar a la function vDSP_ctoz ? ¿Hay una forma diferente / mejor de hacerlo, incluso puede vDSP_ctoz ?
  2. ¿Esto es diferente si el buffer contiene audio de múltiples canales? ¿Cómo es diferente cuando los datos del canal de audio del búfer están intercalados o no?
  3. ¿Cómo convierto los índices en la matriz fft a frecuencias en Hz?
  4. ¿Algo más que pueda estar haciendo mal?

Actualizar

Gracias a todos por sugerencias. Terminé llenando la matriz compleja como se sugiere en la respuesta aceptada. Cuando grabo los valores y reproduzco un tono de 440 Hz en un diapasón, se registra exactamente donde 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])) } } 

La matriz channelSamples luego contiene una matriz separada de muestras 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) } 

  1. Hacky way: puedes simplemente lanzar una serie flotante. Donde los valores reales y imaginarios van uno tras otro.
  2. Depende de si el audio está intercalado o no. Si está intercalado (la mayoría de los casos), los canales izquierdo y derecho están en la matriz con STRIDE 2
  3. La frecuencia más baja en su caso es la frecuencia de un período de 1024 muestras. En el caso de 44100kHz es ~ 23ms, la frecuencia más baja del espectro será 1 / (1024/44100) (~ 43Hz). La siguiente frecuencia será dos veces mayor que esto (~ 86Hz) y así sucesivamente.

4: ha instalado un controller de callback en un bus de audio. Es probable que esto se ejecute con prioridad de subprocesss en time real y con frecuencia. No debe hacer nada que tenga potencial de locking (es probable que resulte en inversión de prioridad y audio glitchy):

  1. Asignar memory ( realp , imagp[Float](.....) es una abreviatura de Array[float] – y probablemente se asignó en el montón. 'Pnetworkingeterminar estos

  2. Llame a operaciones largas, como vDSP_create_fftsetup() , que también asigna memory y la inicializa. Nuevamente, puedes asignar esta una vez fuera de tu function.