How can I generate an array of floats from an audio file in Swift
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))
iOS float buffer to audio playback
Thanks @jaybers I followed a similar approach for the solution. Apologies for not posting it earlier.
Solution:
1) I built/coded the header for playback (PCM as its easiest) in a BYTE * type array.
2) Appended the header to a NSDATA array.
3) appended the float data to NSDATA array
4) played it using AVAudioPlayer ....
An issue I faced : My data was 16bits while float is 32 bit in IOS so playing it as 16bit PCM / 32 bit PCM was introducing noise - I think because of the extra zeros. So i transferred the float data into short datatype array and appended that to the NSDATA and played it as 16bit-PCM - perfectly played.
Codes:
//mynewdata1 is a short datatype 16bit array
NSData *Wave1= [NSMutableData dataWithData:mynewdata1];
unsigned long totalAudioLen=[Wave1 length];
unsigned long totalDataLen = totalAudioLen + 44;
unsigned long longSampleRate = 4*11025.0;
unsigned int channels = 1;
unsigned long byteRate = (16 * longSampleRate * channels)/8;
Byte *header = (Byte*)malloc(44);
header[0] = 'R'; // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (Byte) (totalDataLen & 0xff);
header[5] = (Byte) ((totalDataLen >> 8) & 0xff);
header[6] = (Byte) ((totalDataLen >> 16) & 0xff);
header[7] = (Byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 1 for pcm and 2 for byte integer
header[21] = 0;
header[22] = (Byte) channels;
header[23] = 0;
header[24] = (Byte) (longSampleRate & 0xff);
header[25] = (Byte) ((longSampleRate >> 8) & 0xff);
header[26] = (Byte) ((longSampleRate >> 16) & 0xff);
header[27] = (Byte) ((longSampleRate >> 24) & 0xff);
header[28] = (Byte) (byteRate & 0xff);
header[29] = (Byte) ((byteRate >> 8) & 0xff);
header[30] = (Byte) ((byteRate >> 16) & 0xff);
header[31] = (Byte) ((byteRate >> 24) & 0xff);
header[32] = (Byte) (16*1)/8; // block align
header[33] = 0;
header[34] = 16; // bits per sample
header[35] = 0;
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (Byte) (totalAudioLen & 0xff);
header[41] = (Byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (Byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (Byte) ((totalAudioLen >> 24) & 0xff);
NSData *headerData = [NSData dataWithBytes:header length:44];
NSMutableData * soundFileData1 = [NSMutableData alloc];
[soundFileData1 appendData:headerData];
[soundFileData1 appendData:Wave1];
self.avap1 = [[AVAudioPlayer alloc] initWithData:soundFileData1 fileTypeHint:@"wav" error:&error1];
[self.avap3 play]; //to play
ExtAudioFile into a float buffer produces zeros
You're setting up your ExtAudioFile
and its client format, but you're not actually reading from it (with ExtAudioFileRead
), so your "output" is actually uninitialised, and in your case, very small.
Writing array of floats to audio file
Scrap the code that I posted in the question. I found the solution here: How to write array of float values to audio file in Core Audio?. Look at the first answer. I went to the given link, downloaded the source code, then added EAFWrite.h and EAFWrite.mm to my project. The class assumes that the audio will be read from multiple buffers (ie: a 2D array), but needed it to work with a 1D array, so I modified the function writeToFloats as follows:
-(OSStatus) writeFloats:(long)numFrames fromArray:(float *)data
{
OSStatus err = noErr;
if (!data) return -1;
if (!numFrames) return -1;
AudioBufferList *abl = AllocateAudioBufferList(mStreamFormat.mChannelsPerFrame, numFrames*sizeof(short));
if (!abl) return -1;
abl->mBuffers[0].mNumberChannels = 1;
abl->mBuffers[0].mDataByteSize = numFrames*sizeof(short);
short *buffer = (short*)abl->mBuffers[0].mData;
for (long v = 0; v < numFrames; v++) {
if (data[v] > 0.999)
data[v] = 0.999;
else if (data[v] < -1)
data[v] = -1;
buffer[v] = (short)(data[v]*32768.f);
}
abl->mBuffers[0].mData = buffer;
err = ExtAudioFileWrite(mOutputAudioFile, numFrames, abl);
DestroyAudioBufferList(abl);
if(err != noErr)
{
char formatID[5];
*(UInt32 *)formatID = CFSwapInt32HostToBig(err);
formatID[4] = '\0';
fprintf(stderr, "ExtAudioFileWrite FAILED! %d '%-4.4s'\n",(int)err, formatID);
return err;
}
return err;
}
And to call this function you need:
NSString *outputPath = @"outputFile.caf"
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager createFileAtPath:outputPath contents:nil attributes:nil];
NSURL* fileURL = [NSURL URLWithString:outputPath];
EAFWrite *writer = [[EAFWrite alloc] init];
[writer openFileForWrite:fileURL sr:44100.0 channels:1 wordLength:32 type:kAudioFileCAFType];
[writer writeFloats:_recordingLength fromArray:_recordingSamples];
Write array of floats to a wav audio file in swift
With a great colleague help we've managed to get it to work. Apparently, AudioPCMBuffer after filling also needs to be notified about it's new size.
Also i was using totally wrong formats.
Here is the code:
let SAMPLE_RATE = Float64(16000.0)
let outputFormatSettings = [
AVFormatIDKey:kAudioFormatLinearPCM,
AVLinearPCMBitDepthKey:32,
AVLinearPCMIsFloatKey: true,
// AVLinearPCMIsBigEndianKey: false,
AVSampleRateKey: SAMPLE_RATE,
AVNumberOfChannelsKey: 1
] as [String : Any]
let audioFile = try? AVAudioFile(forWriting: url, settings: outputFormatSettings, commonFormat: AVAudioCommonFormat.pcmFormatFloat32, interleaved: true)
let bufferFormat = AVAudioFormat(settings: outputFormatSettings)
let outputBuffer = AVAudioPCMBuffer(pcmFormat: bufferFormat, frameCapacity: AVAudioFrameCount(buff.count))
// i had my samples in doubles, so convert then write
for i in 0..<buff.count {
outputBuffer.floatChannelData!.pointee[i] = Float( buff[i] )
}
outputBuffer.frameLength = AVAudioFrameCount( buff.count )
do{
try audioFile?.write(from: outputBuffer)
} catch let error as NSError {
print("error:", error.localizedDescription)
}
Get audio floats from AudioQueueBufferRef output queue callback
Try replacing:
let numSamples = array.count / MemoryLayout<Int16>.size //2
By:
let numSamples = array.count
I believe that your float buffers are half the expected size, otherwise.
Here is a modified, standalone version of a subset of your code, that I used for testing:
import Foundation
import Accelerate
let size = 7
var data : [Int16] = [ 0, 1, 2, 3, 4, -Int16.max, Int16.max ]
let array = Array(UnsafeMutableBufferPointer(start: &data, count: size))
var floatsArr: [Float] = Array(repeating: 0.0, count: array.count)
var scaledFloats: [Float] = Array(repeating: 0.0, count: array.count)
vDSP_vflt16(array, 1, &floatsArr, 1, vDSP_Length(array.count))
var factor = Float(Int16.max)
vDSP_vsdiv(floatsArr, 1, &factor, &scaledFloats, 1, vDSP_Length(array.count))
print( array[0 ... array.count - 1], array.count, "int16")
print( floatsArr[0 ... floatsArr.count - 1], floatsArr.count, "float")
print(scaledFloats[0 ... scaledFloats.count - 1], scaledFloats.count, "scaled")
And its output:
[0, 1, 2, 3, 4, -32767, 32767] 7 int16
[0.0, 1.0, 2.0, 3.0, 4.0, -32767.0, 32767.0] 7 float
[0.0, 3.05185094e-05, 6.10370189e-05, 9.15555283e-05, 0.000122074038, -1.0, 1.0] 7 scaled
Related Topics
Creating a Tutorial for My iOS App with Page Control
Uitableview Scrolls to Top When Reloading Cells with Changing Cell Heights
Core Data Store Included in App Bundle
How to Customize Uirefreshcontrol with Different Image and Position
View Controller Responds to App Delegate Notifications in iOS 12 But Not in iOS 13
Detect Whether a Font Is Bold/Italic on iOS
Difference Between Keychain and Nsuserdefault
Uitextfield Clearbuttonmode Color
Passing Data with Segue Through Navigationcontroller
iPhone - Didselectrowatindexpath: Only Being Called After Long Press on Custom Cell
Could Not Attach to Pid:"####" Unable to Attach
Use Logical Operator as Combine Closure in Reduce
How to Set the Cornerradius of a Uistackview
Header Displaced in Tableview with Uirefreshcontrol