13

I would like to load mp3 and wav audio files as arrays of floats or doubles, similar to the io.wavfile.read function in scipy. I can do this with microphone data or playing audio by writing the audio stream to a buffer. However, I'm not sure how to load all of an audio file's data at once.

-- Update

For anyone working with audio signal data in the future, here's a function that does the trick. It's based on Rhythmic Fistman's answer.

    func loadAudioSignal(audioURL: NSURL) -> (signal: [Float], rate: Double, frameCount: Int) {
        let file = try! AVAudioFile(forReading: audioURL)
        let format = AVAudioFormat(commonFormat: .PCMFormatFloat32, sampleRate: file.fileFormat.sampleRate, channels: file.fileFormat.channelCount, interleaved: false)
        let buf = AVAudioPCMBuffer(PCMFormat: format, frameCapacity: UInt32(file.length))
        try! file.readIntoBuffer(buf) // You probably want better error handling
        let floatArray = Array(UnsafeBufferPointer(start: buf.floatChannelData[0], count:Int(buf.frameLength)))
        return (signal: floatArray, rate: file.fileFormat.sampleRate, frameCount: Int(file.length))
    }
Alizaybak
  • 327
  • 3
  • 8

4 Answers4

20

AVAudioFile built-in to iOS (and OS X), is very convenient and will also do format conversions for you:

import AVFoundation
// ...

let url = NSBundle.mainBundle().URLForResource("your audio file", withExtension: "wav")
let file = try! AVAudioFile(forReading: url!)
let format = AVAudioFormat(commonFormat: .PCMFormatFloat32, sampleRate: file.fileFormat.sampleRate, channels: 1, interleaved: false)

let buf = AVAudioPCMBuffer(PCMFormat: format, frameCapacity: 1024)
try! file.readIntoBuffer(buf)

// this makes a copy, you might not want that
let floatArray = Array(UnsafeBufferPointer(start: buf.floatChannelData[0], count:Int(buf.frameLength)))

print("floatArray \(floatArray)\n")

Sadly, for doubles it doesn't seem to be enough to substitute .PCMFormatFloat32 with .PCMFormatFloat64 because AVAudioPCMBuffer doesn't have a float64ChannelData method.

update because I don't know swift well

You can avoid copying the array by working with the UnsafeBufferPointer, which is a perfectly good collection type:

let floatArray = UnsafeBufferPointer(start: buf.floatChannelData[0], count:Int(buf.frameLength))
Rhythmic Fistman
  • 34,352
  • 5
  • 87
  • 159
  • and can we convert array to buffer again? because in the tap I want to append arrays eachother and when they are at certain length(5 seconds) I want to save them to disk. Or should I directly append buffers? but I couldnt find a way to do that. I can create a question if you like. – Spring Feb 07 '19 at 22:01
  • Please do, although you may have already figured it out. I only just saw this. I should update this answer for whatever version of swift we're up to. Is it 4? – Rhythmic Fistman Mar 05 '19 at 09:19
4

It's really tricky to find everything about UnsafeBufferPointer

Here I am posting updated code for Swift 5.0

if let url = Bundle.main.url(forResource: "silence", withExtension: "mp3") {
    let file = try! AVAudioFile(forReading: url)
    if let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: file.fileFormat.sampleRate, channels: 1, interleaved: false) {
        if let buf = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: 1024) {
            try! file.read(into: buf)

            // this makes a copy, you might not want that
            let floatArray = UnsafeBufferPointer(start: buf.floatChannelData![0], count:Int(buf.frameLength))
            // convert to data
            var data = Data()
            for buf in floatArray {
                data.append(withUnsafeBytes(of: buf) { Data($0) })
            }
            // use the data if required.
        }
    }
}

Hope it will help you :)

Ashish Kakkad
  • 23,586
  • 12
  • 103
  • 136
2

Above answers didn't work for me, I'm using Swift5, found this extensions that worked for me here: https://gist.github.com/jtodaone/f2fa59c19794811dbe989dff65a772bc

Also here is how i use the code on Playground

import UIKit
import AVFoundation

let filePath: String = Bundle.main.path(forResource: "nameOfFile", ofType: "wav")!
print("\(filePath)")
let fileURL: NSURL = NSURL(fileURLWithPath: filePath)
let audioFile = try AVAudioFile(forReading: fileURL as URL)
let audioFormat = audioFile.processingFormat
let audioFrameCount = UInt32(audioFile.length)
let audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity: audioFrameCount)

try audioFile.read(into: audioFileBuffer!)

extension AudioBuffer {
    func array() -> [Float] {
        return Array(UnsafeBufferPointer(self))
    }
}

extension AVAudioPCMBuffer {
    func array() -> [Float] {
        return self.audioBufferList.pointee.mBuffers.array()
    }
}

extension Array where Element: FloatingPoint {
    mutating func buffer() -> AudioBuffer {
        return AudioBuffer(mNumberChannels: 1, mDataByteSize: UInt32(self.count * MemoryLayout<Element>.size), mData: &self)
    }
}

let array = audioFileBuffer?.array()
print(array?.count) //Optional(2705408)
1

I have updated the code from @rhythmicfistman to Swift5. There were about a dozen changes to make: apparently things changed dramatically in swift world.

func readWavIntoFloats(fname: String, ext: String) -> [Float] {

    let url = Bundle.main.url(forResource: fname, withExtension: ext)
    let file = try! AVAudioFile(forReading: url!)
    let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: file.fileFormat.sampleRate, channels: 1, interleaved: false) ?? <#default value#>

    let buf = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: 1024)!
    try! file.read(into: buf)

    // this makes a copy, you might not want that
    let floatArray = Array(UnsafeBufferPointer(start: buf.floatChannelData?[0], count:Int(buf.frameLength)))

    return floatArray

}
WestCoastProjects
  • 58,982
  • 91
  • 316
  • 560