Files
EZAudio/Classes/Utility Components/EZAudioUtilities.m

667 lines
24 KiB
Objective-C

//
// EZAudioUtilities.m
// EZAudio
//
// Created by Syed Haris Ali on 6/23/15.
// Copyright (c) 2015 Syed Haris Ali. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "EZAudioUtilities.h"
BOOL __shouldExitOnCheckResultFail = YES;
@implementation EZAudioUtilities
//------------------------------------------------------------------------------
#pragma mark - Debugging
//------------------------------------------------------------------------------
+ (void)setShouldExitOnCheckResultFail:(BOOL)shouldExitOnCheckResultFail
{
__shouldExitOnCheckResultFail = shouldExitOnCheckResultFail;
}
//------------------------------------------------------------------------------
+ (BOOL)shouldExitOnCheckResultFail
{
return __shouldExitOnCheckResultFail;
}
//------------------------------------------------------------------------------
#pragma mark - AudioBufferList Utility
//------------------------------------------------------------------------------
+ (AudioBufferList *)audioBufferListWithNumberOfFrames:(UInt32)frames
numberOfChannels:(UInt32)channels
interleaved:(BOOL)interleaved
{
AudioBufferList *audioBufferList = (AudioBufferList*)malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer) * (channels-1));
UInt32 outputBufferSize = 32 * frames; // 32 KB
audioBufferList->mNumberBuffers = interleaved ? 1 : channels;
for(int i = 0; i < audioBufferList->mNumberBuffers; i++)
{
audioBufferList->mBuffers[i].mNumberChannels = channels;
audioBufferList->mBuffers[i].mDataByteSize = channels * outputBufferSize;
audioBufferList->mBuffers[i].mData = (float *)malloc(channels * sizeof(float) *outputBufferSize);
memset(audioBufferList->mBuffers[i].mData, 0, frames);
}
return audioBufferList;
}
//------------------------------------------------------------------------------
+ (float **)floatBuffersWithNumberOfFrames:(UInt32)frames
numberOfChannels:(UInt32)channels
{
size_t size = sizeof(float *) * channels;
float **buffers = (float **)malloc(size);
for (int i = 0; i < channels; i++)
{
size = sizeof(float) * frames;
buffers[i] = (float *)malloc(size);
}
return buffers;
}
//------------------------------------------------------------------------------
+ (void)freeBufferList:(AudioBufferList *)bufferList
{
if (bufferList)
{
if (bufferList->mNumberBuffers)
{
for( int i = 0; i < bufferList->mNumberBuffers; i++)
{
if (bufferList->mBuffers[i].mData)
{
free(bufferList->mBuffers[i].mData);
}
}
}
free(bufferList);
}
bufferList = NULL;
}
//------------------------------------------------------------------------------
+ (void)freeFloatBuffers:(float **)buffers numberOfChannels:(UInt32)channels
{
for (int i = 0; i < channels; i++)
{
free(buffers[i]);
}
free(buffers);
}
//------------------------------------------------------------------------------
#pragma mark - AudioStreamBasicDescription Utility
//------------------------------------------------------------------------------
+ (AudioStreamBasicDescription)AIFFFormatWithNumberOfChannels:(UInt32)channels
sampleRate:(float)sampleRate
{
AudioStreamBasicDescription asbd;
memset(&asbd, 0, sizeof(asbd));
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFormatFlags = kAudioFormatFlagIsBigEndian|kAudioFormatFlagIsPacked|kAudioFormatFlagIsSignedInteger;
asbd.mSampleRate = sampleRate;
asbd.mChannelsPerFrame = channels;
asbd.mBitsPerChannel = 32;
asbd.mBytesPerPacket = (asbd.mBitsPerChannel / 8) * asbd.mChannelsPerFrame;
asbd.mFramesPerPacket = 1;
asbd.mBytesPerFrame = (asbd.mBitsPerChannel / 8) * asbd.mChannelsPerFrame;
return asbd;
}
//------------------------------------------------------------------------------
+ (AudioStreamBasicDescription)iLBCFormatWithSampleRate:(float)sampleRate
{
AudioStreamBasicDescription asbd;
memset(&asbd, 0, sizeof(asbd));
asbd.mFormatID = kAudioFormatiLBC;
asbd.mChannelsPerFrame = 1;
asbd.mSampleRate = sampleRate;
// Fill in the rest of the descriptions using the Audio Format API
UInt32 propSize = sizeof(asbd);
[EZAudioUtilities checkResult:AudioFormatGetProperty(kAudioFormatProperty_FormatInfo,
0,
NULL,
&propSize,
&asbd)
operation:"Failed to fill out the rest of the m4a AudioStreamBasicDescription"];
return asbd;
}
//------------------------------------------------------------------------------
+ (AudioStreamBasicDescription)floatFormatWithNumberOfChannels:(UInt32)channels
sampleRate:(float)sampleRate
{
AudioStreamBasicDescription asbd;
UInt32 floatByteSize = sizeof(float);
asbd.mBitsPerChannel = 8 * floatByteSize;
asbd.mBytesPerFrame = floatByteSize;
asbd.mBytesPerPacket = floatByteSize;
asbd.mChannelsPerFrame = channels;
asbd.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsNonInterleaved;
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFramesPerPacket = 1;
asbd.mSampleRate = sampleRate;
return asbd;
}
//------------------------------------------------------------------------------
+ (AudioStreamBasicDescription)M4AFormatWithNumberOfChannels:(UInt32)channels
sampleRate:(float)sampleRate
{
AudioStreamBasicDescription asbd;
memset(&asbd, 0, sizeof(asbd));
asbd.mFormatID = kAudioFormatMPEG4AAC;
asbd.mChannelsPerFrame = channels;
asbd.mSampleRate = sampleRate;
// Fill in the rest of the descriptions using the Audio Format API
UInt32 propSize = sizeof(asbd);
[EZAudioUtilities checkResult:AudioFormatGetProperty(kAudioFormatProperty_FormatInfo,
0,
NULL,
&propSize,
&asbd)
operation:"Failed to fill out the rest of the m4a AudioStreamBasicDescription"];
return asbd;
}
//------------------------------------------------------------------------------
+ (AudioStreamBasicDescription)monoFloatFormatWithSampleRate:(float)sampleRate
{
AudioStreamBasicDescription asbd;
UInt32 byteSize = sizeof(float);
asbd.mBitsPerChannel = 8 * byteSize;
asbd.mBytesPerFrame = byteSize;
asbd.mBytesPerPacket = byteSize;
asbd.mChannelsPerFrame = 1;
asbd.mFormatFlags = kAudioFormatFlagIsPacked|kAudioFormatFlagIsFloat;
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFramesPerPacket = 1;
asbd.mSampleRate = sampleRate;
return asbd;
}
//------------------------------------------------------------------------------
+ (AudioStreamBasicDescription)monoCanonicalFormatWithSampleRate:(float)sampleRate
{
AudioStreamBasicDescription asbd;
UInt32 byteSize = sizeof(float);
asbd.mBitsPerChannel = 8 * byteSize;
asbd.mBytesPerFrame = byteSize;
asbd.mBytesPerPacket = byteSize;
asbd.mChannelsPerFrame = 1;
asbd.mFormatFlags = kAudioFormatFlagsNativeFloatPacked|kAudioFormatFlagIsNonInterleaved;
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFramesPerPacket = 1;
asbd.mSampleRate = sampleRate;
return asbd;
}
//------------------------------------------------------------------------------
+ (AudioStreamBasicDescription)stereoCanonicalNonInterleavedFormatWithSampleRate:(float)sampleRate
{
AudioStreamBasicDescription asbd;
UInt32 byteSize = sizeof(float);
asbd.mBitsPerChannel = 8 * byteSize;
asbd.mBytesPerFrame = byteSize;
asbd.mBytesPerPacket = byteSize;
asbd.mChannelsPerFrame = 2;
asbd.mFormatFlags = kAudioFormatFlagsNativeFloatPacked|kAudioFormatFlagIsNonInterleaved;
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFramesPerPacket = 1;
asbd.mSampleRate = sampleRate;
return asbd;
}
//------------------------------------------------------------------------------
+ (AudioStreamBasicDescription)stereoFloatInterleavedFormatWithSampleRate:(float)sampleRate
{
AudioStreamBasicDescription asbd;
UInt32 floatByteSize = sizeof(float);
asbd.mChannelsPerFrame = 2;
asbd.mBitsPerChannel = 8 * floatByteSize;
asbd.mBytesPerFrame = asbd.mChannelsPerFrame * floatByteSize;
asbd.mFramesPerPacket = 1;
asbd.mBytesPerPacket = asbd.mFramesPerPacket * asbd.mBytesPerFrame;
asbd.mFormatFlags = kAudioFormatFlagIsFloat;
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mSampleRate = sampleRate;
asbd.mReserved = 0;
return asbd;
}
//------------------------------------------------------------------------------
+ (AudioStreamBasicDescription)stereoFloatNonInterleavedFormatWithSampleRate:(float)sampleRate
{
AudioStreamBasicDescription asbd;
UInt32 floatByteSize = sizeof(float);
asbd.mBitsPerChannel = 8 * floatByteSize;
asbd.mBytesPerFrame = floatByteSize;
asbd.mChannelsPerFrame = 2;
asbd.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsNonInterleaved;
asbd.mFormatID = kAudioFormatLinearPCM;
asbd.mFramesPerPacket = 1;
asbd.mBytesPerPacket = asbd.mFramesPerPacket * asbd.mBytesPerFrame;
asbd.mSampleRate = sampleRate;
return asbd;
}
//------------------------------------------------------------------------------
+ (BOOL)isFloatFormat:(AudioStreamBasicDescription)asbd
{
return asbd.mFormatFlags & kAudioFormatFlagIsFloat;
}
//------------------------------------------------------------------------------
+ (BOOL)isInterleaved:(AudioStreamBasicDescription)asbd
{
return !(asbd.mFormatFlags & kAudioFormatFlagIsNonInterleaved);
}
//------------------------------------------------------------------------------
+ (BOOL)isLinearPCM:(AudioStreamBasicDescription)asbd
{
return asbd.mFormatID == kAudioFormatLinearPCM;
}
//------------------------------------------------------------------------------
+ (void)printASBD:(AudioStreamBasicDescription)asbd
{
char formatIDString[5];
UInt32 formatID = CFSwapInt32HostToBig(asbd.mFormatID);
bcopy (&formatID, formatIDString, 4);
formatIDString[4] = '\0';
NSLog (@" Sample Rate: %10.0f", asbd.mSampleRate);
NSLog (@" Format ID: %10s", formatIDString);
NSLog (@" Format Flags: %10X", (unsigned int)asbd.mFormatFlags);
NSLog (@" Bytes per Packet: %10d", (unsigned int)asbd.mBytesPerPacket);
NSLog (@" Frames per Packet: %10d", (unsigned int)asbd.mFramesPerPacket);
NSLog (@" Bytes per Frame: %10d", (unsigned int)asbd.mBytesPerFrame);
NSLog (@" Channels per Frame: %10d", (unsigned int)asbd.mChannelsPerFrame);
NSLog (@" Bits per Channel: %10d", (unsigned int)asbd.mBitsPerChannel);
}
//------------------------------------------------------------------------------
+ (NSString *)displayTimeStringFromSeconds:(NSTimeInterval)seconds
{
int totalSeconds = (int)ceil(seconds);
int secondsComponent = totalSeconds % 60;
int minutesComponent = (totalSeconds / 60) % 60;
return [NSString stringWithFormat:@"%02d:%02d", minutesComponent, secondsComponent];
}
//------------------------------------------------------------------------------
+ (NSString *)stringForAudioStreamBasicDescription:(AudioStreamBasicDescription)asbd
{
char formatIDString[5];
UInt32 formatID = CFSwapInt32HostToBig(asbd.mFormatID);
bcopy (&formatID, formatIDString, 4);
formatIDString[4] = '\0';
return [NSString stringWithFormat:
@"\nSample Rate: %10.0f,\n"
@"Format ID: %10s,\n"
@"Format Flags: %10X,\n"
@"Bytes per Packet: %10d,\n"
@"Frames per Packet: %10d,\n"
@"Bytes per Frame: %10d,\n"
@"Channels per Frame: %10d,\n"
@"Bits per Channel: %10d,\n"
@"IsInterleaved: %i,\n"
@"IsFloat: %i,",
asbd.mSampleRate,
formatIDString,
(unsigned int)asbd.mFormatFlags,
(unsigned int)asbd.mBytesPerPacket,
(unsigned int)asbd.mFramesPerPacket,
(unsigned int)asbd.mBytesPerFrame,
(unsigned int)asbd.mChannelsPerFrame,
(unsigned int)asbd.mBitsPerChannel,
[self isInterleaved:asbd],
[self isFloatFormat:asbd]];
}
//------------------------------------------------------------------------------
+ (void)setCanonicalAudioStreamBasicDescription:(AudioStreamBasicDescription*)asbd
numberOfChannels:(UInt32)nChannels
interleaved:(BOOL)interleaved
{
asbd->mFormatID = kAudioFormatLinearPCM;
#if TARGET_OS_IPHONE
int sampleSize = sizeof(float);
asbd->mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
#elif TARGET_OS_MAC
int sampleSize = sizeof(Float32);
asbd->mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
#endif
asbd->mBitsPerChannel = 8 * sampleSize;
asbd->mChannelsPerFrame = nChannels;
asbd->mFramesPerPacket = 1;
if (interleaved)
asbd->mBytesPerPacket = asbd->mBytesPerFrame = nChannels * sampleSize;
else {
asbd->mBytesPerPacket = asbd->mBytesPerFrame = sampleSize;
asbd->mFormatFlags |= kAudioFormatFlagIsNonInterleaved;
}
}
//------------------------------------------------------------------------------
#pragma mark - Math Utilities
//------------------------------------------------------------------------------
+ (void)appendBufferAndShift:(float*)buffer
withBufferSize:(int)bufferLength
toScrollHistory:(float*)scrollHistory
withScrollHistorySize:(int)scrollHistoryLength
{
int shiftLength = scrollHistoryLength - bufferLength;
size_t floatByteSize = sizeof(float);
size_t shiftByteSize = shiftLength * floatByteSize;
size_t bufferByteSize = bufferLength * floatByteSize;
memmove(&scrollHistory[0],
&scrollHistory[bufferLength],
shiftByteSize);
memmove(&scrollHistory[shiftLength],
&buffer[0],
bufferByteSize);
}
//------------------------------------------------------------------------------
+ (void) appendValue:(float)value
toScrollHistory:(float*)scrollHistory
withScrollHistorySize:(int)scrollHistoryLength
{
float val[1]; val[0] = value;
[self appendBufferAndShift:val
withBufferSize:1
toScrollHistory:scrollHistory
withScrollHistorySize:scrollHistoryLength];
}
//------------------------------------------------------------------------------
+(float)MAP:(float)value
leftMin:(float)leftMin
leftMax:(float)leftMax
rightMin:(float)rightMin
rightMax:(float)rightMax
{
float leftSpan = leftMax - leftMin;
float rightSpan = rightMax - rightMin;
float valueScaled = ( value - leftMin) / leftSpan;
return rightMin + (valueScaled * rightSpan);
}
//------------------------------------------------------------------------------
+(float)RMS:(float *)buffer
length:(int)bufferSize
{
float sum = 0.0;
for(int i = 0; i < bufferSize; i++)
sum += buffer[i] * buffer[i];
return sqrtf( sum / bufferSize);
}
//------------------------------------------------------------------------------
+(float)SGN:(float)value
{
return value < 0 ? -1.0f : ( value > 0 ? 1.0f : 0.0f);
}
//------------------------------------------------------------------------------
#pragma mark - OSStatus Utility
//------------------------------------------------------------------------------
+ (void)checkResult:(OSStatus)result operation:(const char *)operation
{
if (result == noErr) return;
char errorString[20];
// see if it appears to be a 4-char-code
*(UInt32 *)(errorString + 1) = CFSwapInt32HostToBig(result);
if (isprint(errorString[1]) && isprint(errorString[2]) && isprint(errorString[3]) && isprint(errorString[4]))
{
errorString[0] = errorString[5] = '\'';
errorString[6] = '\0';
} else
// no, format it as an integer
sprintf(errorString, "%d", (int)result);
fprintf(stderr, "Error: %s (%s)\n", operation, errorString);
if (__shouldExitOnCheckResultFail)
{
exit(-1);
}
}
//------------------------------------------------------------------------------
+ (NSString *)stringFromUInt32Code:(UInt32)code
{
char errorString[20];
// see if it appears to be a 4-char-code
*(UInt32 *)(errorString + 1) = CFSwapInt32HostToBig(code);
if (isprint(errorString[1]) &&
isprint(errorString[2]) &&
isprint(errorString[3]) &&
isprint(errorString[4]))
{
errorString[0] = errorString[5] = '\'';
errorString[6] = '\0';
}
return [NSString stringWithUTF8String:errorString];
}
//------------------------------------------------------------------------------
#pragma mark - Plot Utility
//------------------------------------------------------------------------------
+ (void)updateScrollHistory:(float **)scrollHistory
withLength:(int)scrollHistoryLength
atIndex:(int *)index
withBuffer:(float *)buffer
withBufferSize:(int)bufferSize
isResolutionChanging:(BOOL *)isChanging
{
//
size_t floatByteSize = sizeof(float);
if(*scrollHistory == NULL)
{
// Create the history buffer
*scrollHistory = (float *)calloc(8192, floatByteSize);
}
//
if(!*isChanging)
{
float rms = [EZAudioUtilities RMS:buffer length:bufferSize];
if(*index < scrollHistoryLength)
{
float *hist = *scrollHistory;
hist[*index] = rms;
(*index)++;
}
else
{
[EZAudioUtilities appendValue:rms
toScrollHistory:*scrollHistory
withScrollHistorySize:scrollHistoryLength];
}
}
}
//------------------------------------------------------------------------------
#pragma mark - Color Utility
//------------------------------------------------------------------------------
///-----------------------------------------------------------
/// @name Color Utility
///-----------------------------------------------------------
+ (void)getColorComponentsFromCGColor:(CGColorRef)color
red:(CGFloat *)red
green:(CGFloat *)green
blue:(CGFloat *)blue
alpha:(CGFloat *)alpha
{
size_t componentCount = CGColorGetNumberOfComponents(color);
if (componentCount == 4)
{
const CGFloat *components = CGColorGetComponents(color);
*red = components[0];
*green = components[1];
*blue = components[2];
*alpha = components[3];
}
}
//------------------------------------------------------------------------------
#pragma mark - TPCircularBuffer Utility
//------------------------------------------------------------------------------
+ (void)appendDataToCircularBuffer:(TPCircularBuffer *)circularBuffer
fromAudioBufferList:(AudioBufferList *)audioBufferList
{
TPCircularBufferProduceBytes(circularBuffer,
audioBufferList->mBuffers[0].mData,
audioBufferList->mBuffers[0].mDataByteSize);
}
//------------------------------------------------------------------------------
+ (void)circularBuffer:(TPCircularBuffer *)circularBuffer withSize:(int)size
{
TPCircularBufferInit(circularBuffer, size);
}
//------------------------------------------------------------------------------
+ (void)freeCircularBuffer:(TPCircularBuffer *)circularBuffer
{
TPCircularBufferClear(circularBuffer);
TPCircularBufferCleanup(circularBuffer);
}
//------------------------------------------------------------------------------
#pragma mark - EZPlotHistoryInfo Utility
//------------------------------------------------------------------------------
+ (void)appendBuffer:(float *)buffer
withBufferSize:(UInt32)bufferSize
toHistoryInfo:(EZPlotHistoryInfo *)historyInfo
{
//
// Do nothing if there is no buffer
//
if (bufferSize == 0)
{
return;
}
//
// Update the scroll history datasource
//
float rms = [EZAudioUtilities RMS:buffer length:bufferSize];
float src[1];
src[0] = isnan(rms) ? 0.0 : rms;
TPCircularBufferProduceBytes(&historyInfo->circularBuffer, src, sizeof(src));
int32_t targetBytes = historyInfo->bufferSize * sizeof(float);
int32_t availableBytes = 0;
float *historyBuffer = TPCircularBufferTail(&historyInfo->circularBuffer, &availableBytes);
int32_t bytes = MIN(targetBytes, availableBytes);
memmove(historyInfo->buffer, historyBuffer, bytes);
if (targetBytes <= availableBytes)
{
TPCircularBufferConsume(&historyInfo->circularBuffer, availableBytes - targetBytes);
}
}
//------------------------------------------------------------------------------
+ (void)clearHistoryInfo:(EZPlotHistoryInfo *)historyInfo
{
memset(historyInfo->buffer, 0, historyInfo->bufferSize * sizeof(float));
TPCircularBufferClear(&historyInfo->circularBuffer);
}
//------------------------------------------------------------------------------
+ (void)freeHistoryInfo:(EZPlotHistoryInfo *)historyInfo
{
free(historyInfo->buffer);
free(historyInfo);
TPCircularBufferCleanup(&historyInfo->circularBuffer);
}
//------------------------------------------------------------------------------
+ (EZPlotHistoryInfo *)historyInfoWithDefaultLength:(int)defaultLength
maximumLength:(int)maximumLength
{
//
// Setup buffers
//
EZPlotHistoryInfo *historyInfo = (EZPlotHistoryInfo *)malloc(sizeof(EZPlotHistoryInfo));
historyInfo->bufferSize = defaultLength;
historyInfo->buffer = calloc(maximumLength, sizeof(float));
TPCircularBufferInit(&historyInfo->circularBuffer, maximumLength);
//
// Zero out circular buffer
//
float emptyBuffer[maximumLength];
memset(emptyBuffer, 0, sizeof(emptyBuffer));
TPCircularBufferProduceBytes(&historyInfo->circularBuffer,
emptyBuffer,
(int32_t)sizeof(emptyBuffer));
return historyInfo;
}
//------------------------------------------------------------------------------
@end