Repository: zhxnlai/ZLHistogramAudioPlot Branch: master Commit: 72abec771aae Files: 49 Total size: 348.7 KB Directory structure: gitextract_miqjd55q/ ├── .gitignore ├── EZAudioRecordExample/ │ ├── EZAudioRecordExample/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj/ │ │ │ ├── Main_iPad.storyboard │ │ │ └── Main_iPhone.storyboard │ │ ├── EZAudio/ │ │ │ ├── AEFloatConverter.h │ │ │ ├── AEFloatConverter.m │ │ │ ├── EZAudio.h │ │ │ ├── EZAudio.m │ │ │ ├── EZAudioFile.h │ │ │ ├── EZAudioFile.m │ │ │ ├── EZAudioPlayer.h │ │ │ ├── EZAudioPlayer.m │ │ │ ├── EZAudioPlot.h │ │ │ ├── EZAudioPlot.m │ │ │ ├── EZAudioPlotGL.h │ │ │ ├── EZAudioPlotGL.m │ │ │ ├── EZAudioPlotGLKViewController.h │ │ │ ├── EZAudioPlotGLKViewController.m │ │ │ ├── EZMicrophone.h │ │ │ ├── EZMicrophone.m │ │ │ ├── EZOutput.h │ │ │ ├── EZOutput.m │ │ │ ├── EZPlot.h │ │ │ ├── EZPlot.m │ │ │ ├── EZRecorder.h │ │ │ ├── EZRecorder.m │ │ │ ├── TPCircularBuffer.c │ │ │ ├── TPCircularBuffer.h │ │ │ └── VERSION/ │ │ │ ├── CHANGELOG │ │ │ └── VERSION │ │ ├── EZAudioRecordExample-Info.plist │ │ ├── EZAudioRecordExample-Prefix.pch │ │ ├── Images.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ ├── RecordViewController.h │ │ ├── RecordViewController.m │ │ ├── en.lproj/ │ │ │ └── InfoPlist.strings │ │ └── main.m │ ├── EZAudioRecordExample.xcodeproj/ │ │ ├── project.pbxproj │ │ └── project.xcworkspace/ │ │ └── contents.xcworkspacedata │ └── EZAudioRecordExampleTests/ │ ├── EZAudioRecordExampleTests-Info.plist │ ├── EZAudioRecordExampleTests.m │ └── en.lproj/ │ └── InfoPlist.strings ├── LICENSE ├── README.md ├── ZLHistogramAudioPlot/ │ ├── ZLHistogramAudioPlot.h │ └── ZLHistogramAudioPlot.m └── ZLHistogramAudioPlot.podspec ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode # build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.hmap *.ipa *.xcuserstate # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control # # Pods/ ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/AppDelegate.h ================================================ // // AppDelegate.h // EZAudioRecordExample // // Created by Syed Haris Ali on 12/15/13. // Copyright (c) 2013 Syed Haris Ali. All rights reserved. // #import @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/AppDelegate.m ================================================ // // AppDelegate.m // EZAudioRecordExample // // Created by Syed Haris Ali on 12/15/13. // Copyright (c) 2013 Syed Haris Ali. All rights reserved. // #import "AppDelegate.h" #import @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. // Remember to configure your audio session AVAudioSession *audioSession = [AVAudioSession sharedInstance]; NSError *err = NULL; [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err]; // [audioSession setMode:AVAudioSessionModeMeasurement error:&err]; if (err) { NSLog(@"There was an error creating the audio session"); } [audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:NULL]; if (err) { NSLog(@"There was an error sending the audio to the speakers"); } return YES; } - (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. // This can occur for certain types of temporary interruptions (such as an // incoming phone call or SMS message) or when the user quits the // application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down // OpenGL ES frame rates. Games should use this method to pause the game. } - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate // timers, and store enough application state information to restore your // application to its current state in case it is terminated later. // If your application supports background execution, this method is called // instead of applicationWillTerminate: when the user quits. } - (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the inactive // state; here you can undo many of the changes made on entering the // background. } - (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the // application was inactive. If the application was previously in the // background, optionally refresh the user interface. } - (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if // appropriate. See also applicationDidEnterBackground:. } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/Base.lproj/Main_iPad.storyboard ================================================ ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/Base.lproj/Main_iPhone.storyboard ================================================ ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/AEFloatConverter.h ================================================ // // AEFloatConverter.h // The Amazing Audio Engine // // Created by Michael Tyson on 25/10/2012. // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. // #ifdef __cplusplus extern "C" { #endif #import #import /*! * Universal converter to float format * * Use this class to easily convert arbitrary audio formats to floating point * for use with utilities like the Accelerate framework. */ @interface AEFloatConverter : NSObject /*! * Initialize * * @param sourceFormat The audio format to use */ - (id)initWithSourceFormat:(AudioStreamBasicDescription)sourceFormat; /*! * Convert audio to floating-point * * This C function, safe to use in a Core Audio realtime thread context, will take * an audio buffer list of audio in the format you provided at initialisation, and * convert it into a noninterleaved float array. * * @param converter Pointer to the converter object. * @param sourceBuffer An audio buffer list containing the source audio. * @param targetBuffers An array of floating-point arrays to store the converted float audio into. * Note that you must provide the correct number of arrays, to match the number of channels. * @param frames The number of frames to convert. * @return YES on success; NO on failure */ BOOL AEFloatConverterToFloat(AEFloatConverter* converter, AudioBufferList *sourceBuffer, float * const * targetBuffers, UInt32 frames); /*! * Convert audio to floating-point, in a buffer list * * This C function, safe to use in a Core Audio realtime thread context, will take * an audio buffer list of audio in the format you provided at initialisation, and * convert it into a noninterleaved float format. * * @param converter Pointer to the converter object. * @param sourceBuffer An audio buffer list containing the source audio. * @param targetBuffer An audio buffer list to store the converted floating-point audio. * @param frames The number of frames to convert. * @return YES on success; NO on failure */ BOOL AEFloatConverterToFloatBufferList(AEFloatConverter* converter, AudioBufferList *sourceBuffer, AudioBufferList *targetBuffer, UInt32 frames); /*! * Convert audio from floating-point * * This C function, safe to use in a Core Audio realtime thread context, will take * an audio buffer list of audio in the format you provided at initialisation, and * convert it into a float array. * * @param converter Pointer to the converter object. * @param sourceBuffers An array of floating-point arrays containing the floating-point audio to convert. * Note that you must provide the correct number of arrays, to match the number of channels. * @param targetBuffer An audio buffer list to store the converted audio into. * @param frames The number of frames to convert. * @return YES on success; NO on failure */ BOOL AEFloatConverterFromFloat(AEFloatConverter* converter, float * const * sourceBuffers, AudioBufferList *targetBuffer, UInt32 frames); /*! * Convert audio from floating-point, in a buffer list * * This C function, safe to use in a Core Audio realtime thread context, will take * an audio buffer list of audio in the format you provided at initialisation, and * convert it into a float array. * * @param converter Pointer to the converter object. * @param sourceBuffer An audio buffer list containing the source audio. * @param targetBuffer An audio buffer list to store the converted audio into. * @param frames The number of frames to convert. * @return YES on success; NO on failure */ BOOL AEFloatConverterFromFloatBufferList(AEFloatConverter* converter, AudioBufferList *sourceBuffer, AudioBufferList *targetBuffer, UInt32 frames); /*! * The AudioStreamBasicDescription representing the converted floating-point format */ @property (nonatomic, readonly) AudioStreamBasicDescription floatingPointAudioDescription; /*! * The source audio format set at initialization */ @property (nonatomic, readonly) AudioStreamBasicDescription sourceFormat; @end #ifdef __cplusplus } #endif ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/AEFloatConverter.m ================================================ // // AEFloatConverter.m // The Amazing Audio Engine // // Created by Michael Tyson on 25/10/2012. // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. // #import "AEFloatConverter.h" #define checkResult(result,operation) (_checkResult((result),(operation),strrchr(__FILE__, '/')+1,__LINE__)) static inline BOOL _checkResult(OSStatus result, const char *operation, const char* file, int line) { if ( result != noErr ) { NSLog(@"%s:%d: %s result %d %08X %4.4s", file, line, operation, (int)result, (int)result, (char*)&result); return NO; } return YES; } #define kNoMoreDataErr -2222 struct complexInputDataProc_t { AudioBufferList *sourceBuffer; }; @interface AEFloatConverter () { AudioStreamBasicDescription _sourceAudioDescription; AudioStreamBasicDescription _floatAudioDescription; AudioConverterRef _toFloatConverter; AudioConverterRef _fromFloatConverter; AudioBufferList *_scratchFloatBufferList; } static OSStatus complexInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData); @end @implementation AEFloatConverter @synthesize sourceFormat = _sourceAudioDescription; -(id)initWithSourceFormat:(AudioStreamBasicDescription)sourceFormat { if ( !(self = [super init]) ) return nil; _floatAudioDescription.mFormatID = kAudioFormatLinearPCM; _floatAudioDescription.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved; _floatAudioDescription.mChannelsPerFrame = sourceFormat.mChannelsPerFrame; _floatAudioDescription.mBytesPerPacket = sizeof(float); _floatAudioDescription.mFramesPerPacket = 1; _floatAudioDescription.mBytesPerFrame = sizeof(float); _floatAudioDescription.mBitsPerChannel = 8 * sizeof(float); _floatAudioDescription.mSampleRate = sourceFormat.mSampleRate; _sourceAudioDescription = sourceFormat; if ( memcmp(&sourceFormat, &_floatAudioDescription, sizeof(AudioStreamBasicDescription)) != 0 ) { checkResult(AudioConverterNew(&sourceFormat, &_floatAudioDescription, &_toFloatConverter), "AudioConverterNew"); checkResult(AudioConverterNew(&_floatAudioDescription, &sourceFormat, &_fromFloatConverter), "AudioConverterNew"); _scratchFloatBufferList = (AudioBufferList*)malloc(sizeof(AudioBufferList) + (_floatAudioDescription.mChannelsPerFrame-1)*sizeof(AudioBuffer)); _scratchFloatBufferList->mNumberBuffers = _floatAudioDescription.mChannelsPerFrame; for ( int i=0; i<_scratchFloatBufferList->mNumberBuffers; i++ ) { _scratchFloatBufferList->mBuffers[i].mNumberChannels = 1; } } return self; } -(void)dealloc { if ( _toFloatConverter ) AudioConverterDispose(_toFloatConverter); if ( _fromFloatConverter ) AudioConverterDispose(_fromFloatConverter); if ( _scratchFloatBufferList ) free(_scratchFloatBufferList); // [super dealloc]; } BOOL AEFloatConverterToFloat(AEFloatConverter* THIS, AudioBufferList *sourceBuffer, float * const * targetBuffers, UInt32 frames) { if ( frames == 0 ) return YES; if ( THIS->_toFloatConverter ) { UInt32 priorDataByteSize = sourceBuffer->mBuffers[0].mDataByteSize; for ( int i=0; imNumberBuffers; i++ ) { sourceBuffer->mBuffers[i].mDataByteSize = frames * THIS->_sourceAudioDescription.mBytesPerFrame; } for ( int i=0; i_scratchFloatBufferList->mNumberBuffers; i++ ) { THIS->_scratchFloatBufferList->mBuffers[i].mData = targetBuffers[i]; THIS->_scratchFloatBufferList->mBuffers[i].mDataByteSize = frames * sizeof(float); } OSStatus result = AudioConverterFillComplexBuffer(THIS->_toFloatConverter, complexInputDataProc, &(struct complexInputDataProc_t) { .sourceBuffer = sourceBuffer }, &frames, THIS->_scratchFloatBufferList, NULL); for ( int i=0; imNumberBuffers; i++ ) { sourceBuffer->mBuffers[i].mDataByteSize = priorDataByteSize; } if ( !checkResult(result, "AudioConverterConvertComplexBuffer") ) { return NO; } } else { for ( int i=0; imNumberBuffers; i++ ) { memcpy(targetBuffers[i], sourceBuffer->mBuffers[i].mData, frames * sizeof(float)); } } return YES; } BOOL AEFloatConverterToFloatBufferList(AEFloatConverter* converter, AudioBufferList *sourceBuffer, AudioBufferList *targetBuffer, UInt32 frames) { assert(targetBuffer->mNumberBuffers == converter->_floatAudioDescription.mChannelsPerFrame); float *targetBuffers[targetBuffer->mNumberBuffers]; for ( int i=0; imNumberBuffers; i++ ) { targetBuffers[i] = (float*)targetBuffer->mBuffers[i].mData; } return AEFloatConverterToFloat(converter, sourceBuffer, targetBuffers, frames); } BOOL AEFloatConverterFromFloat(AEFloatConverter* THIS, float * const * sourceBuffers, AudioBufferList *targetBuffer, UInt32 frames) { if ( frames == 0 ) return YES; if ( THIS->_fromFloatConverter ) { for ( int i=0; i_scratchFloatBufferList->mNumberBuffers; i++ ) { THIS->_scratchFloatBufferList->mBuffers[i].mData = sourceBuffers[i]; THIS->_scratchFloatBufferList->mBuffers[i].mDataByteSize = frames * sizeof(float); } UInt32 priorDataByteSize = targetBuffer->mBuffers[0].mDataByteSize; for ( int i=0; imNumberBuffers; i++ ) { targetBuffer->mBuffers[i].mDataByteSize = frames * THIS->_sourceAudioDescription.mBytesPerFrame; } OSStatus result = AudioConverterFillComplexBuffer(THIS->_fromFloatConverter, complexInputDataProc, &(struct complexInputDataProc_t) { .sourceBuffer = THIS->_scratchFloatBufferList }, &frames, targetBuffer, NULL); for ( int i=0; imNumberBuffers; i++ ) { targetBuffer->mBuffers[i].mDataByteSize = priorDataByteSize; } if ( !checkResult(result, "AudioConverterConvertComplexBuffer") ) { return NO; } } else { for ( int i=0; imNumberBuffers; i++ ) { memcpy(targetBuffer->mBuffers[i].mData, sourceBuffers[i], frames * sizeof(float)); } } return YES; } BOOL AEFloatConverterFromFloatBufferList(AEFloatConverter* converter, AudioBufferList *sourceBuffer, AudioBufferList *targetBuffer, UInt32 frames) { assert(sourceBuffer->mNumberBuffers == converter->_floatAudioDescription.mChannelsPerFrame); float *sourceBuffers[sourceBuffer->mNumberBuffers]; for ( int i=0; imNumberBuffers; i++ ) { sourceBuffers[i] = (float*)sourceBuffer->mBuffers[i].mData; } return AEFloatConverterFromFloat(converter, sourceBuffers, targetBuffer, frames); } static OSStatus complexInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) { struct complexInputDataProc_t *arg = (struct complexInputDataProc_t*)inUserData; if ( !arg->sourceBuffer ) { return kNoMoreDataErr; } memcpy(ioData, arg->sourceBuffer, sizeof(AudioBufferList) + (arg->sourceBuffer->mNumberBuffers-1)*sizeof(AudioBuffer)); arg->sourceBuffer = NULL; return noErr; } -(AudioStreamBasicDescription)floatingPointAudioDescription { return _floatAudioDescription; } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudio.h ================================================ // // EZAudio.h // EZAudio // // Created by Syed Haris Ali on 11/21/13. // Copyright (c) 2013 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 #pragma mark - 3rd Party Utilties #import "AEFloatConverter.h" #import "TPCircularBuffer.h" #pragma mark - Core Components #import "EZAudioFile.h" #import "EZMicrophone.h" #import "EZOutput.h" #import "EZRecorder.h" #pragma mark - Extended Components #import "EZAudioPlayer.h" #pragma mark - Interface Components #import "EZPlot.h" #import "EZAudioPlot.h" #import "EZAudioPlotGL.h" #import "EZAudioPlotGLKViewController.h" /** EZAudio is a simple, intuitive framework for iOS and OSX. The goal of EZAudio was to provide a modular, cross-platform framework to simplify performing everyday audio operations like getting microphone input, creating audio waveforms, recording/playing audio files, etc. The visualization tools like the EZAudioPlot and EZAudioPlotGL were created to plug right into the framework's various components and provide highly optimized drawing routines that work in harmony with audio callback loops. All components retain the same namespace whether you're on an iOS device or a Mac computer so an EZAudioPlot understands it will subclass an UIView on an iOS device or an NSView on a Mac. Class methods for EZAudio are provided as utility methods used throughout the other modules within the framework. For instance, these methods help make sense of error codes (checkResult:operation:), map values betwen coordinate systems (MAP:leftMin:leftMax:rightMin:rightMax:), calculate root mean squared values for buffers (RMS:length:), etc. */ @interface EZAudio : NSObject #pragma mark - AudioBufferList Utility ///----------------------------------------------------------- /// @name AudioBufferList Utility ///----------------------------------------------------------- /** Allocates an AudioBufferList structure. Make sure to call freeBufferList when done using AudioBufferList or it will leak. @param frames The number of frames that will be stored within each audio buffer @param channels The number of channels (e.g. 2 for stereo, 1 for mono, etc.) @param interleaved Whether the samples will be interleaved (if not it will be assumed to be non-interleaved and each channel will have an AudioBuffer allocated) @return An AudioBufferList struct that has been allocated in memory */ +(AudioBufferList *)audioBufferListWithNumberOfFrames:(UInt32)frames numberOfChannels:(UInt32)channels interleaved:(BOOL)interleaved; /** Deallocates an AudioBufferList structure from memory. @param bufferList A pointer to the buffer list you would like to free */ +(void)freeBufferList:(AudioBufferList*)bufferList; #pragma mark - AudioStreamBasicDescription Utilties ///----------------------------------------------------------- /// @name Creating An AudioStreamBasicDescription ///----------------------------------------------------------- /** @param channels The desired number of channels @param sampleRate The desired sample rate @return A new AudioStreamBasicDescription with the specified format. */ +(AudioStreamBasicDescription)AIFFFormatWithNumberOfChannels:(UInt32)channels sampleRate:(float)sampleRate; /** @param sampleRate The desired sample rate @return A new AudioStreamBasicDescription with the specified format. */ +(AudioStreamBasicDescription)iLBCFormatWithSampleRate:(float)sampleRate; /** @param channels The desired number of channels @param sampleRate The desired sample rate @return A new AudioStreamBasicDescription with the specified format. */ +(AudioStreamBasicDescription)M4AFormatWithNumberOfChannels:(UInt32)channels sampleRate:(float)sampleRate; /** @param sampleRate The desired sample rate @return A new AudioStreamBasicDescription with the specified format. */ +(AudioStreamBasicDescription)monoFloatFormatWithSampleRate:(float)sampleRate; /** @param sampleRate The desired sample rate @return A new AudioStreamBasicDescription with the specified format. */ +(AudioStreamBasicDescription)monoCanonicalFormatWithSampleRate:(float)sampleRate; /** @param sampleRate The desired sample rate @return A new AudioStreamBasicDescription with the specified format. */ +(AudioStreamBasicDescription)stereoCanonicalNonInterleavedFormatWithSampleRate:(float)sampleRate; /** @param sampleRate The desired sample rate @return A new AudioStreamBasicDescription with the specified format. */ +(AudioStreamBasicDescription)stereoFloatInterleavedFormatWithSampleRate:(float)sampleRate; /** @param sampleRate The desired sample rate @return A new AudioStreamBasicDescription with the specified format. */ +(AudioStreamBasicDescription)stereoFloatNonInterleavedFormatWithSampleRate:(float)sameRate; ///----------------------------------------------------------- /// @name AudioStreamBasicDescription Utilities ///----------------------------------------------------------- /** Nicely logs out the contents of an AudioStreamBasicDescription struct @param asbd The AudioStreamBasicDescription struct with content to print out */ +(void)printASBD:(AudioStreamBasicDescription)asbd; /** Just a wrapper around the setCanonical function provided in the Core Audio Utility C++ class. @param asbd The AudioStreamBasicDescription structure to modify @param nChannels The number of expected channels on the description @param interleaved A flag indicating whether the stereo samples should be interleaved in the buffer */ +(void)setCanonicalAudioStreamBasicDescription:(AudioStreamBasicDescription*)asbd numberOfChannels:(UInt32)nChannels interleaved:(BOOL)interleaved; #pragma mark - Math Utilities ///----------------------------------------------------------- /// @name Math Utilities ///----------------------------------------------------------- /** Appends an array of values to a history buffer and performs an internal shift to add the values to the tail and removes the same number of values from the head. @param buffer A float array of values to append to the tail of the history buffer @param bufferLength The length of the float array being appended to the history buffer @param scrollHistory The target history buffer in which to append the values @param scrollHistoryLength The length of the target history buffer */ +(void)appendBufferAndShift:(float*)buffer withBufferSize:(int)bufferLength toScrollHistory:(float*)scrollHistory withScrollHistorySize:(int)scrollHistoryLength; /** Appends a value to a history buffer and performs an internal shift to add the value to the tail and remove the 0th value. @param value The float value to append to the history array @param scrollHistory The target history buffer in which to append the values @param scrollHistoryLength The length of the target history buffer */ +(void) appendValue:(float)value toScrollHistory:(float*)scrollHistory withScrollHistorySize:(int)scrollHistoryLength; /** Maps a value from one coordinate system into another one. Takes in the current value to map, the minimum and maximum values of the first coordinate system, and the minimum and maximum values of the second coordinate system and calculates the mapped value in the second coordinate system's constraints. @param value The value expressed in the first coordinate system @param leftMin The minimum of the first coordinate system @param leftMax The maximum of the first coordinate system @param rightMin The minimum of the second coordindate system @param rightMax The maximum of the second coordinate system @return The mapped value in terms of the second coordinate system */ +(float)MAP:(float)value leftMin:(float)leftMin leftMax:(float)leftMax rightMin:(float)rightMin rightMax:(float)rightMax; /** Calculates the root mean squared for a buffer. @param buffer A float buffer array of values whose root mean squared to calculate @param bufferSize The size of the float buffer @return The root mean squared of the buffer */ +(float)RMS:(float*)buffer length:(int)bufferSize; /** Calculate the sign function sgn(x) = { -1 , x < 0, { 0 , x = 0, { 1 , x > 0 @param value The float value for which to use as x @return The float sign value */ +(float)SGN:(float)value; #pragma mark - OSStatus Utility ///----------------------------------------------------------- /// @name OSStatus Utility ///----------------------------------------------------------- /** Basic check result function useful for checking each step of the audio setup process @param result The OSStatus representing the result of an operation @param operation A string (const char, not NSString) describing the operation taking place (will print if fails) */ +(void)checkResult:(OSStatus)result operation:(const char*)operation; #pragma mark - Plot Utility ///----------------------------------------------------------- /// @name Plot Utility ///----------------------------------------------------------- +(void)updateScrollHistory:(float**)scrollHistory withLength:(int)scrollHistoryLength atIndex:(int*)index withBuffer:(float*)buffer withBufferSize:(int)bufferSize isResolutionChanging:(BOOL*)isChanging; #pragma mark - TPCircularBuffer Utility ///----------------------------------------------------------- /// @name TPCircularBuffer Utility ///----------------------------------------------------------- /** Appends the data from the audio buffer list to the circular buffer @param circularBuffer Pointer to the instance of the TPCircularBuffer to add the audio data to @param audioBufferList Pointer to the instance of the AudioBufferList with the audio data */ +(void)appendDataToCircularBuffer:(TPCircularBuffer*)circularBuffer fromAudioBufferList:(AudioBufferList*)audioBufferList; /** Initializes the circular buffer (just a wrapper around the C method) * @param circularBuffer Pointer to an instance of the TPCircularBuffer * @param size The length of the TPCircularBuffer (usually 1024) */ +(void)circularBuffer:(TPCircularBuffer*)circularBuffer withSize:(int)size; /** Frees a circular buffer @param circularBuffer Pointer to the circular buffer to clear */ +(void)freeCircularBuffer:(TPCircularBuffer*)circularBuffer; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudio.m ================================================ // // EZAudio.m // EZAudio // // Created by Syed Haris Ali on 11/21/13. // Copyright (c) 2013 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 "EZAudio.h" @implementation EZAudio #pragma mark - AudioBufferList Utility +(AudioBufferList *)audioBufferListWithNumberOfFrames:(UInt32)frames numberOfChannels:(UInt32)channels interleaved:(BOOL)interleaved { AudioBufferList *audioBufferList = (AudioBufferList*)malloc(sizeof(AudioBufferList)); 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); } return audioBufferList; } +(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; } #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); [EZAudio checkResult:AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &propSize, &asbd) operation:"Failed to fill out the rest of the m4a AudioStreamBasicDescription"]; 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); [EZAudio 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 = kAudioFormatFlagsCanonical|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 = kAudioFormatFlagsCanonical|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.mBytesPerPacket = asbd.mChannelsPerFrame * floatByteSize; asbd.mFormatFlags = kAudioFormatFlagIsPacked|kAudioFormatFlagIsFloat; asbd.mFormatID = kAudioFormatLinearPCM; asbd.mFramesPerPacket = 1; asbd.mSampleRate = sampleRate; return asbd; } +(AudioStreamBasicDescription)stereoFloatNonInterleavedFormatWithSampleRate:(float)sampleRate { AudioStreamBasicDescription asbd; UInt32 floatByteSize = sizeof(float); asbd.mBitsPerChannel = 8 * floatByteSize; asbd.mBytesPerFrame = floatByteSize; asbd.mBytesPerPacket = floatByteSize; asbd.mChannelsPerFrame = 2; asbd.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsNonInterleaved; asbd.mFormatID = kAudioFormatLinearPCM; asbd.mFramesPerPacket = 1; asbd.mSampleRate = sampleRate; return asbd; } +(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); } +(void)setCanonicalAudioStreamBasicDescription:(AudioStreamBasicDescription*)asbd numberOfChannels:(UInt32)nChannels interleaved:(BOOL)interleaved { asbd->mFormatID = kAudioFormatLinearPCM; #if TARGET_OS_IPHONE int sampleSize = sizeof(float); asbd->mFormatFlags = kAudioFormatFlagsCanonical; #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 - 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); exit(1); } #pragma mark - Math Utility +(void)appendBufferAndShift:(float*)buffer withBufferSize:(int)bufferLength toScrollHistory:(float*)scrollHistory withScrollHistorySize:(int)scrollHistoryLength { NSAssert(scrollHistoryLength>=bufferLength,@"Scroll history array length must be greater buffer length"); NSAssert(scrollHistoryLength>0,@"Scroll history array length must be greater than 0"); NSAssert(bufferLength>0,@"Buffer array length must be greater than 0"); 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 - 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(kEZAudioPlotMaxHistoryBufferLength,floatByteSize); } // if( !*isChanging ){ float rms = [EZAudio RMS:buffer length:bufferSize]; if( *index < scrollHistoryLength ){ float *hist = *scrollHistory; hist[*index] = rms; (*index)++; } else { [EZAudio appendValue:rms toScrollHistory:*scrollHistory withScrollHistorySize:scrollHistoryLength]; } } } #pragma mark - TPCircularBuffer Utility +(void)circularBuffer:(TPCircularBuffer *)circularBuffer withSize:(int)size { TPCircularBufferInit(circularBuffer,size); } +(void)appendDataToCircularBuffer:(TPCircularBuffer*)circularBuffer fromAudioBufferList:(AudioBufferList*)audioBufferList { TPCircularBufferProduceBytes(circularBuffer, audioBufferList->mBuffers[0].mData, audioBufferList->mBuffers[0].mDataByteSize); } +(void)freeCircularBuffer:(TPCircularBuffer *)circularBuffer { TPCircularBufferClear(circularBuffer); TPCircularBufferCleanup(circularBuffer); } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudioFile.h ================================================ // // EZAudioFile.h // EZAudio // // Created by Syed Haris Ali on 12/1/13. // Copyright (c) 2013 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 #import @class EZAudio; @class EZAudioFile; /** The EZAudioFileDelegate provides event callbacks for the EZAudioFile object. These type of events are triggered by reads and seeks on the file and gives feedback such as the audio data read as a float array for visualizations and the new seek position for UI updating. */ @protocol EZAudioFileDelegate @optional /** Triggered from the EZAudioFile function `readFrames:audioBufferList:bufferSize:eof:` to notify the delegate of the read audio data as a float array instead of a buffer list. Common use case of this would be to visualize the float data using an audio plot or audio data dependent OpenGL sketch. @param audioFile The instance of the EZAudioFile that triggered the event. @param buffer A float array of float arrays holding the audio data. buffer[0] would be the left channel's float array while buffer[1] would be the right channel's float array in a stereo file. @param bufferSize The length of the buffers float arrays @param numberOfChannels The number of channels. 2 for stereo, 1 for mono. */ -(void) audioFile:(EZAudioFile*)audioFile readAudio:(float**)buffer withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels; /** Occurs when the audio file's internal seek position has been updated by the EZAudioFile functions `readFrames:audioBufferList:bufferSize:eof:` or `audioFile:updatedPosition:`. @param audioFile The instance of the EZAudio in which the change occured @param framePosition The new frame index as a 64-bit signed integer */ -(void)audioFile:(EZAudioFile*)audioFile updatedPosition:(SInt64)framePosition; @end /** The EZAudioFile provides a lightweight and intuitive way to asynchronously interact with audio files. These interactions included reading audio data, seeking within an audio file, getting information about the file, and pulling the waveform data for visualizing the contents of the audio file. The EZAudioFileDelegate provides event callbacks for when reads, seeks, and various updates happen within the audio file to allow the caller to interact with the action in meaningful ways. Common use cases here could be to read the audio file's data as AudioBufferList structures for output (see EZOutput) and visualizing the audio file's data as a float array using an audio plot (see EZAudioPlot). */ @interface EZAudioFile : NSObject #pragma mark - Blocks /** A block used when returning back the waveform data. The waveform data itself will be an array of float values and the length indicates the total length of the float array. @param waveformData An array of float values representing the amplitude data from the audio waveform @param length The length of the waveform data's float array */ typedef void (^WaveformDataCompletionBlock)(float *waveformData, UInt32 length); #pragma mark - Properties /** A EZAudioFileDelegate for the audio file that is used to return events such as new seek positions within the file and the read audio data as a float array. */ @property (nonatomic,assign) id audioFileDelegate; /** The resolution of the waveform data. This value specifies how the recommendedDrawingFrameRate chooses itself. A low value like 128 will render a waveform containing 128 points representing a low resolution waveform while a high value like 4096 will render a high quality waveform. Higher resolutions provide more detail, but take more work to render in the audio waveform plots (EZAudioPlot or EZAudioPlotGL) while lower resolutions providel less detail, but work better for displaying many at a time (like in a UITableView) */ @property (nonatomic,assign) UInt32 waveformResolution; #pragma mark - Initializers ///----------------------------------------------------------- /// @name Initializers ///----------------------------------------------------------- /** Creates a new instance of the EZAudioFile using a file path URL. @param url The file path reference of the audio file as an NSURL. @return The newly created EZAudioFile instance. */ -(EZAudioFile*)initWithURL:(NSURL*)url; /** Creates a new instance of the EZAudioFile using a file path URL and allows specifying an EZAudioFileDelegate. @param url The file path reference of the audio file as an NSURL. @param delegate The audio file delegate that receives events specified by the EZAudioFileDelegate protocol @return The newly created EZAudioFile instance. */ -(EZAudioFile*)initWithURL:(NSURL*)url andDelegate:(id)delegate; #pragma mark - Class Initializers ///----------------------------------------------------------- /// @name Class Initializers ///----------------------------------------------------------- /** Class method that creates a new instance of the EZAudioFile using a file path URL. @param url The file path reference of the audio file as an NSURL. @return The newly created EZAudioFile instance. */ +(EZAudioFile*)audioFileWithURL:(NSURL*)url; /** Class method that creates a new instance of the EZAudioFile using a file path URL and allows specifying an EZAudioFileDelegate. @param url The file path reference of the audio file as an NSURL. @param delegate The audio file delegate that receives events specified by the EZAudioFileDelegate protocol @return The newly created EZAudioFile instance. */ +(EZAudioFile*)audioFileWithURL:(NSURL*)url andDelegate:(id)delegate; #pragma mark - Class Methods ///----------------------------------------------------------- /// @name Class Methods ///----------------------------------------------------------- /** Provides an array of the supported audio files types. Each audio file type is provided as a string, i.e. @"caf". Useful for filtering lists of files in an open panel to only the types allowed. @return An array of NSString objects representing the represented file types. */ +(NSArray*)supportedAudioFileTypes; #pragma mark - Events ///----------------------------------------------------------- /// @name Reading The Audio File ///----------------------------------------------------------- /** Reads a specified number of frames from the audio file. In addition, this will notify the EZAudioFileDelegate (if specified) of the read data as a float array with the audioFile:readAudio:withBufferSize:withNumberOfChannels: event and the new seek position within the file with the audioFile:updatedPosition: event. @param frames The number of frames to read from the file. @param audioBufferList An allocated AudioBufferList structure in which to store the read audio data @param bufferSize A pointer to a UInt32 in which to store the read buffersize @param eof A pointer to a BOOL in which to store whether the read operation reached the end of the audio file. */ -(void)readFrames:(UInt32)frames audioBufferList:(AudioBufferList*)audioBufferList bufferSize:(UInt32*)bufferSize eof:(BOOL*)eof; ///----------------------------------------------------------- /// @name Seeking Through The Audio File ///----------------------------------------------------------- /** Seeks through an audio file to a specified frame. This will notify the EZAudioFileDelegate (if specified) with the audioFile:updatedPosition: function. @param frame The new frame position to seek to as a SInt64. */ -(void)seekToFrame:(SInt64)frame; #pragma mark - Getters ///----------------------------------------------------------- /// @name Getting Information About The Audio File ///----------------------------------------------------------- /** Provides the AudioStreamBasicDescription structure used within the app. The file's format will be converted to this format and then sent back as either a float array or a `AudioBufferList` pointer. Use this when communicating with other EZAudio components. @return An AudioStreamBasicDescription structure describing the format of the audio file. */ -(AudioStreamBasicDescription)clientFormat; /** Provides the AudioStreamBasicDescription structure containing the format of the file. @return An AudioStreamBasicDescription structure describing the format of the audio file. */ -(AudioStreamBasicDescription)fileFormat; /** Provides the frame index (a.k.a the seek positon) within the audio file as an integer. This can be helpful when seeking through the audio file. @return The current frame index within the audio file as a SInt64. */ -(SInt64)frameIndex; /** Provides a dictionary containing the metadata (ID3) tags that are included in the header for the audio file. Typically this contains stuff like artist, title, release year, etc. @return An NSDictionary containing the metadata for the audio file. */ -(NSDictionary *)metadata; /** Provides the total duration of the audio file in seconds. @return The total duration of the audio file as a Float32. */ -(Float32)totalDuration; /** Provides the total frame count of the audio file. @return The total number of frames in the audio file as a SInt64. */ -(SInt64)totalFrames; /** Provides the NSURL for the audio file. @return An NSURL representing the path of the EZAudioFile instance. */ -(NSURL*)url; #pragma mark - Helpers ///----------------------------------------------------------- /// @name Manipulating The Audio Data ///----------------------------------------------------------- /** Tells the caller whether the EZAudioFile has cached waveform data that was loaded via the getWaveformDataWithCompletionBlock: function. * @return A BOOL indicating whether there is cached waveform data */ -(BOOL)hasLoadedAudioData; /** Asynchronously pulls the waveform amplitude data into a float array for the receiver. @param waveformDataCompletionBlock A WaveformDataCompletionBlock that executes when the waveform data has been extracted. Provides the waveform data as a float array and the length of the array. */ -(void)getWaveformDataWithCompletionBlock:(WaveformDataCompletionBlock)waveformDataCompletionBlock; /** Provides the minimum number of buffers that would be required with the constant frames read rate provided. @param frameRate A constant frame rate to use when calculating the number of buffers needed as a UInt32. @return The minimum number of buffers required for the constant frames read rate provided as a UInt32. */ -(UInt32)minBuffersWithFrameRate:(UInt32)frameRate; /** Provides a frame rate to use when drawing and averaging a bin of values to create each point in a graph. The ideal amount of end buffers seems to be between 1000-3000 so we determine a frame rate per audio file that can achieve a high degree of detail for the entire waveform. @return A frame rate value as a UInt32 to use when reading frames in a file. */ -(UInt32)recommendedDrawingFrameRate; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudioFile.m ================================================ // // EZAudioFile.m // EZAudio // // Created by Syed Haris Ali on 12/1/13. // Copyright (c) 2013 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 "EZAudioFile.h" #import "EZAudio.h" #define kEZAudioFileWaveformDefaultResolution (1024) @interface EZAudioFile (){ // Reading from the audio file ExtAudioFileRef _audioFile; AudioStreamBasicDescription _clientFormat; AudioStreamBasicDescription _fileFormat; float **_floatBuffers; AEFloatConverter *_floatConverter; SInt64 _frameIndex; CFURLRef _sourceURL; Float32 _totalDuration; SInt64 _totalFrames; // Waveform Data float *_waveformData; UInt32 _waveformFrameRate; UInt32 _waveformTotalBuffers; } @end @implementation EZAudioFile @synthesize audioFileDelegate = _audioFileDelegate; @synthesize waveformResolution = _waveformResolution; #pragma mark - Initializers -(EZAudioFile*)initWithURL:(NSURL*)url { self = [super init]; if(self){ _sourceURL = (__bridge CFURLRef)url; [self _configureAudioFile]; } return self; } -(EZAudioFile *)initWithURL:(NSURL *)url andDelegate:(id)delegate { self = [self initWithURL:url]; if(self){ self.audioFileDelegate = delegate; } return self; } #pragma mark - Class Initializers +(EZAudioFile*)audioFileWithURL:(NSURL*)url { return [[EZAudioFile alloc] initWithURL:url]; } +(EZAudioFile *)audioFileWithURL:(NSURL *)url andDelegate:(id)delegate { return [[EZAudioFile alloc] initWithURL:url andDelegate:delegate]; } #pragma mark - Class Methods +(NSArray *)supportedAudioFileTypes { return @[ @"aac", @"caf", @"aif", @"aiff", @"aifc", @"mp3", @"mp4", @"m4a", @"snd", @"au", @"sd2", @"wav" ]; } #pragma mark - Private Configuation -(void)_configureAudioFile { // Source URL should not be nil NSAssert(_sourceURL,@"Source URL was not specified correctly."); // Try to open the file for reading [EZAudio checkResult:ExtAudioFileOpenURL(_sourceURL,&_audioFile) operation:"Failed to open audio file for reading"]; // Try pulling the stream description UInt32 size = sizeof(_fileFormat); [EZAudio checkResult:ExtAudioFileGetProperty(_audioFile,kExtAudioFileProperty_FileDataFormat, &size, &_fileFormat) operation:"Failed to get audio stream basic description of input file"]; // Try pulling the total frame size size = sizeof(_totalFrames); [EZAudio checkResult:ExtAudioFileGetProperty(_audioFile,kExtAudioFileProperty_FileLengthFrames, &size, &_totalFrames) operation:"Failed to get total frames of input file"]; _totalFrames = MAX(1, _totalFrames); // Total duration _totalDuration = _totalFrames / _fileFormat.mSampleRate; // Set the client format on the stream switch (_fileFormat.mChannelsPerFrame) { case 1: _clientFormat = [EZAudio monoFloatFormatWithSampleRate:_fileFormat.mSampleRate]; break; case 2: _clientFormat = [EZAudio stereoFloatInterleavedFormatWithSampleRate:_fileFormat.mSampleRate]; break; default: break; } [EZAudio checkResult:ExtAudioFileSetProperty(_audioFile, kExtAudioFileProperty_ClientDataFormat, sizeof (AudioStreamBasicDescription), &_clientFormat) operation:"Couldn't set client data format on input ext file"]; // Allocate the float buffers _floatConverter = [[AEFloatConverter alloc] initWithSourceFormat:_clientFormat]; size_t sizeToAllocate = sizeof(float*) * _clientFormat.mChannelsPerFrame; sizeToAllocate = MAX(8, sizeToAllocate); _floatBuffers = (float**)malloc( sizeToAllocate ); UInt32 outputBufferSize = 32 * 1024; // 32 KB for ( int i=0; i< _clientFormat.mChannelsPerFrame; i++ ) { _floatBuffers[i] = (float*)malloc(outputBufferSize); } [EZAudio printASBD:_fileFormat]; // There's no waveform data yet _waveformData = NULL; // Set the default resolution for the waveform data _waveformResolution = kEZAudioFileWaveformDefaultResolution; } #pragma mark - Events -(void)readFrames:(UInt32)frames audioBufferList:(AudioBufferList *)audioBufferList bufferSize:(UInt32 *)bufferSize eof:(BOOL *)eof { [EZAudio checkResult:ExtAudioFileRead(_audioFile, &frames, audioBufferList) operation:"Failed to read audio data from audio file"]; *bufferSize = audioBufferList->mBuffers[0].mDataByteSize/sizeof(float); *eof = frames == 0; _frameIndex += frames; if( self.audioFileDelegate ){ if( [self.audioFileDelegate respondsToSelector:@selector(audioFile:updatedPosition:)] ){ [self.audioFileDelegate audioFile:self updatedPosition:_frameIndex]; } if( [self.audioFileDelegate respondsToSelector:@selector(audioFile:readAudio:withBufferSize:withNumberOfChannels:)] ){ AEFloatConverterToFloat(_floatConverter,audioBufferList,_floatBuffers,frames); [self.audioFileDelegate audioFile:self readAudio:_floatBuffers withBufferSize:frames withNumberOfChannels:_clientFormat.mChannelsPerFrame]; } } } -(void)seekToFrame:(SInt64)frame { [EZAudio checkResult:ExtAudioFileSeek(_audioFile,frame) operation:"Failed to seek frame position within audio file"]; _frameIndex = frame; if( self.audioFileDelegate ){ if( [self.audioFileDelegate respondsToSelector:@selector(audioFile:updatedPosition:)] ){ [self.audioFileDelegate audioFile:self updatedPosition:_frameIndex]; } } } #pragma mark - Getters -(BOOL)hasLoadedAudioData { return _waveformData != NULL; } -(void)getWaveformDataWithCompletionBlock:(WaveformDataCompletionBlock)waveformDataCompletionBlock { SInt64 currentFramePosition = _frameIndex; if( _waveformData != NULL ){ waveformDataCompletionBlock( _waveformData, _waveformTotalBuffers ); return; } _waveformFrameRate = [self recommendedDrawingFrameRate]; _waveformTotalBuffers = [self minBuffersWithFrameRate:_waveformFrameRate]; _waveformData = (float*)malloc(sizeof(float)*_waveformTotalBuffers); if( self.totalFrames == 0 ){ waveformDataCompletionBlock( _waveformData, _waveformTotalBuffers ); return; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0ul), ^{ for( int i = 0; i < _waveformTotalBuffers; i++ ){ // Take a snapshot of each buffer through the audio file to form the waveform AudioBufferList *bufferList = [EZAudio audioBufferListWithNumberOfFrames:_waveformFrameRate numberOfChannels:_clientFormat.mChannelsPerFrame interleaved:YES]; UInt32 bufferSize; BOOL eof; // Read in the specified number of frames [EZAudio checkResult:ExtAudioFileRead(_audioFile, &_waveformFrameRate, bufferList) operation:"Failed to read audio data from audio file"]; bufferSize = bufferList->mBuffers[0].mDataByteSize/sizeof(float); bufferSize = MAX(1, bufferSize); eof = _waveformFrameRate == 0; _frameIndex += _waveformFrameRate; // Calculate RMS of each buffer float rms = [EZAudio RMS:bufferList->mBuffers[0].mData length:bufferSize]; _waveformData[i] = rms; // Since we malloc'ed, we should cleanup [EZAudio freeBufferList:bufferList]; } // Seek the audio file back to the beginning [EZAudio checkResult:ExtAudioFileSeek(_audioFile,currentFramePosition) operation:"Failed to seek frame position within audio file"]; _frameIndex = currentFramePosition; // Once we're done send off the waveform data dispatch_async(dispatch_get_main_queue(), ^{ waveformDataCompletionBlock( _waveformData, _waveformTotalBuffers ); }); }); } -(AudioStreamBasicDescription)clientFormat { return _clientFormat; } -(AudioStreamBasicDescription)fileFormat { return _fileFormat; } -(SInt64)frameIndex { return _frameIndex; } -(NSDictionary *)metadata { AudioFileID audioFileID; UInt32 propSize = sizeof(audioFileID); [EZAudio checkResult:ExtAudioFileGetProperty(_audioFile, kExtAudioFileProperty_AudioFile, &propSize, &audioFileID) operation:"Failed to get audio file id"]; CFDictionaryRef metadata; UInt32 isWritable; [EZAudio checkResult:AudioFileGetPropertyInfo(audioFileID, kAudioFilePropertyInfoDictionary, &propSize, &isWritable) operation:"Failed to get the size of the metadata dictionary"]; [EZAudio checkResult:AudioFileGetProperty(audioFileID, kAudioFilePropertyInfoDictionary, &propSize, &metadata) operation:"Failed to get metadata dictionary"]; return (__bridge NSDictionary *)metadata; } -(Float32)totalDuration { return _totalDuration; } -(SInt64)totalFrames { return _totalFrames; } -(NSURL *)url { return (__bridge NSURL*)_sourceURL; } #pragma mark - Setters -(void)setWaveformResolution:(UInt32)waveformResolution { if( _waveformResolution != waveformResolution ){ _waveformResolution = waveformResolution; if( _waveformData ){ free(_waveformData); _waveformData = NULL; } } } #pragma mark - Helpers -(UInt32)minBuffersWithFrameRate:(UInt32)frameRate { frameRate = frameRate > 0 ? frameRate : 1; UInt32 val = (UInt32) _totalFrames / frameRate + 1; return MAX(1, val); } -(UInt32)recommendedDrawingFrameRate { UInt32 val = 1; if(_waveformResolution > 0){ val = (UInt32) _totalFrames / _waveformResolution; if(val > 1) --val; } return MAX(1, val); } #pragma mark - Cleanup -(void)dealloc { if( _waveformData ){ free(_waveformData); _waveformData = NULL; } // if( _floatBuffers ){ // free(_floatBuffers); // _floatBuffers = NULL; // } _frameIndex = 0; _waveformFrameRate = 0; _waveformTotalBuffers = 0; if( _audioFile ){ [EZAudio checkResult:ExtAudioFileDispose(_audioFile) operation:"Failed to dispose of audio file"]; } } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudioPlayer.h ================================================ // // EZAudioPlayer.h // EZAudio // // Created by Syed Haris Ali on 1/16/14. // Copyright (c) 2014 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 #import "TargetConditionals.h" #import "EZAudio.h" #if TARGET_OS_IPHONE #import #elif TARGET_OS_MAC #endif @class EZAudioPlayer; /** The EZAudioPlayerDelegate provides event callbacks for the EZAudioPlayer. These type of events are triggered by changes in the EZAudioPlayer's state and allow someone implementing the EZAudioPlayer to more easily update their user interface. Events are triggered anytime the EZAudioPlayer resumes/pauses playback, reaches the end of the file, reads audio data and converts it to float data visualizations (using the EZAudioFile), and updates its cursor position within the audio file during playback (use this for the play position on a slider on the user interface). @warning These callbacks don't necessarily occur on the main thread so make sure you wrap any UI code in a GCD block like: dispatch_async(dispatch_get_main_queue(), ^{ // Update UI }); */ @protocol EZAudioPlayerDelegate @optional /** Triggered by the EZAudioPlayer when the playback has been resumed or started. @param audioPlayer The instance of the EZAudioPlayer that triggered the event @param audioFile The instance of the EZAudioFile that the event was triggered from */ -(void)audioPlayer:(EZAudioPlayer*)audioPlayer didResumePlaybackOnAudioFile:(EZAudioFile*)audioFile; /** Triggered by the EZAudioPlayer when the playback has been paused. @param audioPlayer The instance of the EZAudioPlayer that triggered the event @param audioFile The instance of the EZAudioFile that the event was triggered from */ -(void)audioPlayer:(EZAudioPlayer*)audioPlayer didPausePlaybackOnAudioFile:(EZAudioFile*)audioFile; /** Triggered by the EZAudioPlayer when the output has reached the end of the EZAudioFile it's playing. If the EZAudioPlayer has its `shouldLoop` property set to true this will trigger, but playback will continue to loop once its hit the end of the audio file. @param audioPlayer The instance of the EZAudioPlayer that triggered the event @param audioFile The instance of the EZAudioFile that the event was triggered from */ -(void)audioPlayer:(EZAudioPlayer*)audioPlayer reachedEndOfAudioFile:(EZAudioFile*)audioFile; /** Triggered by the EZAudioPlayer's internal EZAudioFile's EZAudioFileDelegate callback and notifies the delegate of the read audio data as a float array instead of a buffer list. Common use case of this would be to visualize the float data using an audio plot or audio data dependent OpenGL sketch. @param audioPlayer The instance of the EZAudioPlayer that triggered the event @param buffer A float array of float arrays holding the audio data. buffer[0] would be the left channel's float array while buffer[1] would be the right channel's float array in a stereo file. @param bufferSize The length of the buffers float arrays @param numberOfChannels The number of channels. 2 for stereo, 1 for mono. @param audioFile The instance of the EZAudioFile that the event was triggered from */ -(void) audioPlayer:(EZAudioPlayer*)audioPlayer readAudio:(float**)buffer withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels inAudioFile:(EZAudioFile*)audioFile;; /** Triggered by EZAudioPlayer's internal EZAudioFile's EZAudioFileDelegate callback and notifies the delegate of the current playback position. The framePosition provides the current frame position and can be calculated against the EZAudioPlayer's total frames using the `totalFrames` function from the EZAudioPlayer. @param audioPlayer The instance of the EZAudioPlayer that triggered the event @param framePosition The new frame index as a 64-bit signed integer @param audioFile The instance of the EZAudioFile that the event was triggered from */ -(void)audioPlayer:(EZAudioPlayer*)audioPlayer updatedPosition:(SInt64)framePosition inAudioFile:(EZAudioFile*)audioFile; @end /** The EZAudioPlayer acts as the master delegate (the EZAudioFileDelegate) over whatever EZAudioFile it is using for playback. Classes that want to get the EZAudioFileDelegate callbacks should implement the EZAudioPlayer's EZAudioPlayerDelegate on the EZAudioPlayer instance. */ @interface EZAudioPlayer : NSObject #pragma mark - Properties ///----------------------------------------------------------- /// @name Properties ///----------------------------------------------------------- /** The EZAudioPlayerDelegate that will handle the audio player callbacks */ @property (nonatomic,assign) id audioPlayerDelegate; /** A BOOL indicating whether the player should loop the file */ @property (nonatomic,assign) BOOL shouldLoop; #pragma mark - Initializers ///----------------------------------------------------------- /// @name Initializers ///----------------------------------------------------------- /** Initializes the EZAudioPlayer with an EZAudioFile instance. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The instance of the EZAudioFile to use for initializing the EZAudioPlayer @return The newly created instance of the EZAudioPlayer */ -(EZAudioPlayer*)initWithEZAudioFile:(EZAudioFile*)audioFile; /** Initializes the EZAudioPlayer with an EZAudioFile instance and provides a way to assign the EZAudioPlayerDelegate on instantiation. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The instance of the EZAudioFile to use for initializing the EZAudioPlayer @param audioPlayerDelegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the initWithEZAudioFile: function instead. @return The newly created instance of the EZAudioPlayer */ -(EZAudioPlayer*)initWithEZAudioFile:(EZAudioFile*)audioFile withDelegate:(id)audioPlayerDelegate; /** Initializes the EZAudioPlayer with an NSURL instance representing the file path of the audio file. @param url The NSURL instance representing the file path of the audio file. @return The newly created instance of the EZAudioPlayer */ -(EZAudioPlayer*)initWithURL:(NSURL*)url; /** Initializes the EZAudioPlayer with an NSURL instance representing the file path of the audio file and a caller to assign as the EZAudioPlayerDelegate on instantiation. @param url The NSURL instance representing the file path of the audio file. @param audioPlayerDelegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the initWithEZAudioFile: function instead. @return The newly created instance of the EZAudioPlayer */ -(EZAudioPlayer*)initWithURL:(NSURL*)url withDelegate:(id)audioPlayerDelegate; #pragma mark - Class Initializers ///----------------------------------------------------------- /// @name Class Initializers ///----------------------------------------------------------- /** Class initializer that initializes the EZAudioPlayer with an EZAudioFile instance. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The instance of the EZAudioFile to use for initializing the EZAudioPlayer @return The newly created instance of the EZAudioPlayer */ +(EZAudioPlayer*)audioPlayerWithEZAudioFile:(EZAudioFile*)audioFile; /** Class initializer that initializes the EZAudioPlayer with an EZAudioFile instance and provides a way to assign the EZAudioPlayerDelegate on instantiation. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The instance of the EZAudioFile to use for initializing the EZAudioPlayer @param audioPlayerDelegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the audioPlayerWithEZAudioFile: function instead. @return The newly created instance of the EZAudioPlayer */ +(EZAudioPlayer*)audioPlayerWithEZAudioFile:(EZAudioFile*)audioFile withDelegate:(id)audioPlayerDelegate; /** Class initializer that initializes the EZAudioPlayer with an NSURL instance representing the file path of the audio file. @param url The NSURL instance representing the file path of the audio file. @return The newly created instance of the EZAudioPlayer */ +(EZAudioPlayer*)audioPlayerWithURL:(NSURL*)url; /** Class initializer that initializes the EZAudioPlayer with an NSURL instance representing the file path of the audio file and a caller to assign as the EZAudioPlayerDelegate on instantiation. @param url The NSURL instance representing the file path of the audio file. @param audioPlayerDelegate The receiver that will act as the EZAudioPlayerDelegate. Set to nil if it should have no delegate or use the audioPlayerWithURL: function instead. @return The newly created instance of the EZAudioPlayer */ +(EZAudioPlayer*)audioPlayerWithURL:(NSURL*)url withDelegate:(id)audioPlayerDelegate; #pragma mark - Singleton ///----------------------------------------------------------- /// @name Shared Instance ///----------------------------------------------------------- /** The shared instance (singleton) of the audio player. Most applications will only have one instance of the EZAudioPlayer that can be reused with multiple different audio files. * @return The shared instance of the EZAudioPlayer. */ +(EZAudioPlayer*)sharedAudioPlayer; #pragma mark - Getters ///----------------------------------------------------------- /// @name Getting The Audio Player's Properties ///----------------------------------------------------------- /** Provides the EZAudioFile instance that is being used as the datasource for playback. @return The EZAudioFile instance that is currently being used for playback. */ -(EZAudioFile*)audioFile; /** Provides the current time (a.k.a. the seek position) in seconds within the audio file that's being used for playback. This can be helpful when displaying the audio player's current time over duration. @return A float representing the current time within the audio file used for playback. */ -(float)currentTime; /** Provides a flag indicating whether the EZAudioPlayer has reached the end of the audio file used for playback. @return A BOOL indicating whether or not the EZAudioPlayer has reached the end of the file it is using for playback. */ -(BOOL)endOfFile; /** Provides the frame index (a.k.a the seek positon) within the audio file being used for playback. This can be helpful when seeking through the audio file. @return An SInt64 representing the current frame index within the audio file used for playback. */ -(SInt64)frameIndex; /** Provides a flag indicating whether the EZAudioPlayer is currently playing back any audio. @return A BOOL indicating whether or not the EZAudioPlayer is performing playback, */ -(BOOL)isPlaying; /** Provides the EZOutput instance that is being used to provide playback to the system output. @return The EZOutput instance that is currently being used for output playback. */ -(EZOutput*)output; /** Provides the total duration of the current audio file being used for playback (in seconds). @return A float representing the total duration of the current audio file being used for playback in seconds. */ -(float)totalDuration; /** Provides the total amount of frames in the current audio file being used for playback. @return A SInt64 representing the total amount of frames in the current audio file being used for playback. */ -(SInt64)totalFrames; /** Provides the file path that's currently being used by the player for playback. @return The NSURL representing the file path of the audio file being used for playback. */ -(NSURL*)url; #pragma mark - Setters ///----------------------------------------------------------- /// @name Setting The File/Output ///----------------------------------------------------------- /** Sets the EZAudioFile to use for playback. This does not use the EZAudioFile by reference, but instead creates a separate EZAudioFile instance with the same file at the given file path provided by the internal NSURL to use for internal seeking so it doesn't cause any locking between the caller's instance of the EZAudioFile. @param audioFile The new EZAudioFile instance that should be used for playback */ -(void)setAudioFile:(EZAudioFile*)audioFile; /** Sets the EZOutput to route playback. By default this uses the [EZOutput sharedOutput] singleton. @param output The new EZOutput instance that should be used for playback */ -(void)setOutput:(EZOutput*)output; #pragma mark - Methods ///----------------------------------------------------------- /// @name Play/Pause/Seeking the Player ///----------------------------------------------------------- /** Starts or resumes playback. */ -(void)play; /** Pauses playback. */ -(void)pause; /** Stops playback. */ -(void)stop; /** Seeks playback to a specified frame within the internal EZAudioFile. This will notify the EZAudioFileDelegate (if specified) with the audioPlayer:updatedPosition:inAudioFile: function. @param frame The new frame position to seek to as a SInt64. */ -(void)seekToFrame:(SInt64)frame; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudioPlayer.m ================================================ // // EZAudioPlayer.m // EZAudio // // Created by Syed Haris Ali on 1/16/14. // Copyright (c) 2014 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 "EZAudioPlayer.h" #if TARGET_OS_IPHONE #elif TARGET_OS_MAC #endif @interface EZAudioPlayer () { BOOL _eof; } @property (nonatomic,strong,setter=setAudioFile:) EZAudioFile *audioFile; @property (nonatomic,strong,setter=setOutput:) EZOutput *output; @end @implementation EZAudioPlayer @synthesize audioFile = _audioFile; @synthesize audioPlayerDelegate = _audioPlayerDelegate; @synthesize output = _output; @synthesize shouldLoop = _shouldLoop; #pragma mark - Initializers -(id)init { self = [super init]; if(self){ [self _configureAudioPlayer]; } return self; } -(EZAudioPlayer*)initWithEZAudioFile:(EZAudioFile *)audioFile { return [self initWithEZAudioFile:audioFile withDelegate:nil]; } -(EZAudioPlayer *)initWithEZAudioFile:(EZAudioFile *)audioFile withDelegate:(id)audioPlayerDelegate { self = [super init]; if(self){ // This should make a separate reference to the audio file [self _configureAudioPlayer]; self.audioFile = audioFile; self.audioPlayerDelegate = audioPlayerDelegate; } return self; } -(EZAudioPlayer *)initWithURL:(NSURL *)url { return [self initWithURL:url withDelegate:nil]; } -(EZAudioPlayer *)initWithURL:(NSURL *)url withDelegate:(id)audioPlayerDelegate { self = [super init]; if(self){ [self _configureAudioPlayer]; self.audioFile = [EZAudioFile audioFileWithURL:url andDelegate:self]; self.audioPlayerDelegate = audioPlayerDelegate; } return self; } #pragma mark - Class Initializers +(EZAudioPlayer *)audioPlayerWithEZAudioFile:(EZAudioFile *)audioFile { return [[EZAudioPlayer alloc] initWithEZAudioFile:audioFile]; } +(EZAudioPlayer *)audioPlayerWithEZAudioFile:(EZAudioFile *)audioFile withDelegate:(id)audioPlayerDelegate { return [[EZAudioPlayer alloc] initWithEZAudioFile:audioFile withDelegate:audioPlayerDelegate]; } +(EZAudioPlayer *)audioPlayerWithURL:(NSURL *)url { return [[EZAudioPlayer alloc] initWithURL:url]; } +(EZAudioPlayer *)audioPlayerWithURL:(NSURL *)url withDelegate:(id)audioPlayerDelegate { return [[EZAudioPlayer alloc] initWithURL:url withDelegate:audioPlayerDelegate]; } #pragma mark - Singleton +(EZAudioPlayer *)sharedAudioPlayer { static EZAudioPlayer *_sharedAudioPlayer = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedAudioPlayer = [[EZAudioPlayer alloc] init]; }); return _sharedAudioPlayer; } #pragma mark - Private Configuration -(void)_configureAudioPlayer { // Defaults self.output = [EZOutput sharedOutput]; #if TARGET_OS_IPHONE // Configure the AVSession AVAudioSession *audioSession = [AVAudioSession sharedInstance]; NSError *err = NULL; [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err]; if( err ){ NSLog(@"There was an error creating the audio session"); } [audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:NULL]; if( err ){ NSLog(@"There was an error sending the audio to the speakers"); } #elif TARGET_OS_MAC #endif } #pragma mark - Getters -(EZAudioFile*)audioFile { return _audioFile; } -(float)currentTime { NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); return [EZAudio MAP:self.audioFile.frameIndex leftMin:0 leftMax:self.audioFile.totalFrames rightMin:0 rightMax:self.audioFile.totalDuration]; } -(BOOL)endOfFile { return _eof; } -(SInt64)frameIndex { NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); return _audioFile.frameIndex; } -(BOOL)isPlaying { return self.output.isPlaying; } -(EZOutput*)output { NSAssert(_output,@"No output was found, this should by default be the EZOutput shared instance"); return _output; } -(float)totalDuration { NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); return _audioFile.totalDuration; } -(SInt64)totalFrames { NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); return _audioFile.totalFrames; } -(NSURL *)url { NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); return _audioFile.url; } #pragma mark - Setters -(void)setAudioFile:(EZAudioFile *)audioFile { if( _audioFile ){ _audioFile.audioFileDelegate = nil; } _eof = NO; _audioFile = [EZAudioFile audioFileWithURL:audioFile.url andDelegate:self]; NSAssert(_output,@"No output was found, this should by default be the EZOutput shared instance"); [_output setAudioStreamBasicDescription:self.audioFile.clientFormat]; } -(void)setOutput:(EZOutput*)output { _output = output; _output.outputDataSource = self; } #pragma mark - Methods -(void)play { NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); if( _audioFile ){ [_output startPlayback]; if( self.frameIndex != self.totalFrames ){ _eof = NO; } if( self.audioPlayerDelegate ){ if( [self.audioPlayerDelegate respondsToSelector:@selector(audioPlayer:didResumePlaybackOnAudioFile:)] ){ // Notify the delegate we're starting playback [self.audioPlayerDelegate audioPlayer:self didResumePlaybackOnAudioFile:_audioFile]; } } } } -(void)pause { NSAssert(self.audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); if( _audioFile ){ [_output stopPlayback]; if( self.audioPlayerDelegate ){ if( [self.audioPlayerDelegate respondsToSelector:@selector(audioPlayer:didPausePlaybackOnAudioFile:)] ){ // Notify the delegate we're pausing playback [self.audioPlayerDelegate audioPlayer:self didPausePlaybackOnAudioFile:_audioFile]; } } } } -(void)seekToFrame:(SInt64)frame { NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); if( _audioFile ){ [_audioFile seekToFrame:frame]; } if( self.frameIndex != self.totalFrames ){ _eof = NO; } } -(void)stop { NSAssert(_audioFile,@"No audio file to perform the seek on, check that EZAudioFile is not nil"); if( _audioFile ){ [_output stopPlayback]; [_audioFile seekToFrame:0]; _eof = NO; } } #pragma mark - EZAudioFileDelegate -(void)audioFile:(EZAudioFile *)audioFile readAudio:(float **)buffer withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels { if( self.audioPlayerDelegate ){ if( [self.audioPlayerDelegate respondsToSelector:@selector(audioPlayer:readAudio:withBufferSize:withNumberOfChannels:inAudioFile:)] ){ [self.audioPlayerDelegate audioPlayer:self readAudio:buffer withBufferSize:bufferSize withNumberOfChannels:numberOfChannels inAudioFile:audioFile]; } } } -(void)audioFile:(EZAudioFile *)audioFile updatedPosition:(SInt64)framePosition { if( self.audioPlayerDelegate ){ if( [self.audioPlayerDelegate respondsToSelector:@selector(audioPlayer:updatedPosition:inAudioFile:)] ){ [self.audioPlayerDelegate audioPlayer:self updatedPosition:framePosition inAudioFile:audioFile]; } } } #pragma mark - EZOutputDataSource -(void) output:(EZOutput *)output shouldFillAudioBufferList:(AudioBufferList *)audioBufferList withNumberOfFrames:(UInt32)frames { if( self.audioFile ) { UInt32 bufferSize; [self.audioFile readFrames:frames audioBufferList:audioBufferList bufferSize:&bufferSize eof:&_eof]; if( _eof && self.shouldLoop ) { [self seekToFrame:0]; } } } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudioPlot.h ================================================ // // EZAudioPlot.h // EZAudio // // Created by Syed Haris Ali on 9/2/13. // Copyright (c) 2013 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 "TargetConditionals.h" #import "EZPlot.h" @class EZAudio; #define kEZAudioPlotMaxHistoryBufferLength (8192) #define kEZAudioPlotDefaultHistoryBufferLength (1024) /** `EZAudioPlot`, a subclass of `EZPlot`, is a cross-platform (iOS and OSX) class that plots an audio waveform using Core Graphics. The caller provides updates a constant stream of updated audio data in the `updateBuffer:withBufferSize:` function, which in turn will be plotted in one of the plot types: * Buffer (`EZPlotTypeBuffer`) - A plot that only consists of the current buffer and buffer size from the last call to `updateBuffer:withBufferSize:`. This looks similar to the default openFrameworks input audio example. * Rolling (`EZPlotTypeRolling`) - A plot that consists of a rolling history of values averaged from each buffer. This is the traditional waveform look. #Parent Methods and Properties# See EZPlot for full API methods and properties (colors, plot type, update function) */ @interface EZAudioPlot : EZPlot { CGPoint *plotData; UInt32 plotLength; } #pragma mark - Adjust Resolution ///----------------------------------------------------------- /// @name Adjusting The Resolution ///----------------------------------------------------------- /** Sets the length of the rolling history display. Can grow or shrink the display up to the maximum size specified by the kEZAudioPlotMaxHistoryBufferLength macro. Will return the actual set value, which will be either the given value if smaller than the kEZAudioPlotMaxHistoryBufferLength or kEZAudioPlotMaxHistoryBufferLength if a larger value is attempted to be set. @param historyLength The new length of the rolling history buffer. @return The new value equal to the historyLength or the kEZAudioPlotMaxHistoryBufferLength. */ -(int)setRollingHistoryLength:(int)historyLength; /** Provides the length of the rolling history buffer * @return An int representing the length of the rolling history buffer */ -(int)rollingHistoryLength; #pragma mark - Subclass Methods /** <#Description#> @param data <#theplotData description#> @param length <#length description#> */ -(void)setSampleData:(float *)data length:(int)length; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudioPlot.m ================================================ // // EZAudioPlot.m // EZAudio // // Created by Syed Haris Ali on 9/2/13. // Copyright (c) 2013 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 "EZAudioPlot.h" #import "EZAudio.h" @interface EZAudioPlot () { // BOOL _hasData; // TPCircularBuffer _historyBuffer; // Rolling History BOOL _setMaxLength; float *_scrollHistory; int _scrollHistoryIndex; UInt32 _scrollHistoryLength; BOOL _changingHistorySize; } @end @implementation EZAudioPlot @synthesize backgroundColor = _backgroundColor; @synthesize color = _color; @synthesize gain = _gain; @synthesize plotType = _plotType; @synthesize shouldFill = _shouldFill; @synthesize shouldMirror = _shouldMirror; #pragma mark - Initialization -(id)init { self = [super init]; if(self){ [self initPlot]; } return self; } -(id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if(self){ [self initPlot]; } return self; } #if TARGET_OS_IPHONE -(id)initWithFrame:(CGRect)frameRect { #elif TARGET_OS_MAC -(id)initWithFrame:(NSRect)frameRect { #endif self = [super initWithFrame:frameRect]; if(self){ [self initPlot]; } return self; } -(void)initPlot { #if TARGET_OS_IPHONE self.backgroundColor = [UIColor blackColor]; self.color = [UIColor colorWithHue:0 saturation:1.0 brightness:1.0 alpha:1.0]; #elif TARGET_OS_MAC self.backgroundColor = [NSColor blackColor]; self.color = [NSColor colorWithCalibratedHue:0 saturation:1.0 brightness:1.0 alpha:1.0]; #endif self.gain = 1.0; self.plotType = EZPlotTypeRolling; self.shouldMirror = NO; self.shouldFill = NO; plotData = NULL; _scrollHistory = NULL; _scrollHistoryLength = kEZAudioPlotDefaultHistoryBufferLength; } #pragma mark - Setters -(void)setBackgroundColor:(id)backgroundColor { _backgroundColor = backgroundColor; [self _refreshDisplay]; } -(void)setColor:(id)color { _color = color; [self _refreshDisplay]; } -(void)setGain:(float)gain { _gain = gain; [self _refreshDisplay]; } -(void)setPlotType:(EZPlotType)plotType { _plotType = plotType; [self _refreshDisplay]; } -(void)setShouldFill:(BOOL)shouldFill { _shouldFill = shouldFill; [self _refreshDisplay]; } -(void)setShouldMirror:(BOOL)shouldMirror { _shouldMirror = shouldMirror; [self _refreshDisplay]; } -(void)_refreshDisplay { #if TARGET_OS_IPHONE [self setNeedsDisplay]; #elif TARGET_OS_MAC [self setNeedsDisplay:YES]; #endif } #pragma mark - Get Data -(void)setSampleData:(float *)data length:(int)length { if( plotData != nil ){ free(plotData); } plotData = (CGPoint *)calloc(sizeof(CGPoint),length); plotLength = length; for(int i = 0; i < length; i++) { data[i] = i == 0 ? 0 : data[i]; plotData[i] = CGPointMake(i,data[i] * _gain); } [self _refreshDisplay]; } #pragma mark - Update -(void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize { if( _plotType == EZPlotTypeRolling ){ // Update the scroll history datasource [EZAudio updateScrollHistory:&_scrollHistory withLength:_scrollHistoryLength atIndex:&_scrollHistoryIndex withBuffer:buffer withBufferSize:bufferSize isResolutionChanging:&_changingHistorySize]; // [self setSampleData:_scrollHistory length:(!_setMaxLength?kEZAudioPlotMaxHistoryBufferLength:_scrollHistoryLength)]; _setMaxLength = YES; } else if( _plotType == EZPlotTypeBuffer ){ [self setSampleData:buffer length:bufferSize]; } else { // Unknown plot type } } #if TARGET_OS_IPHONE - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextSaveGState(ctx); CGRect frame = self.bounds; #elif TARGET_OS_MAC - (void)drawRect:(NSRect)dirtyRect { [[NSGraphicsContext currentContext] saveGraphicsState]; NSGraphicsContext * nsGraphicsContext = [NSGraphicsContext currentContext]; CGContextRef ctx = (CGContextRef) [nsGraphicsContext graphicsPort]; NSRect frame = self.bounds; #endif #if TARGET_OS_IPHONE // Set the background color [(UIColor*)self.backgroundColor set]; UIRectFill(frame); // Set the waveform line color [(UIColor*)self.color set]; #elif TARGET_OS_MAC [(NSColor*)self.backgroundColor set]; NSRectFill(frame); [(NSColor*)self.color set]; #endif if(plotLength > 0) { plotData[plotLength-1] = CGPointMake(plotLength-1,0.0f); CGMutablePathRef halfPath = CGPathCreateMutable(); CGPathAddLines(halfPath, NULL, plotData, plotLength); CGMutablePathRef path = CGPathCreateMutable(); double xscale = (frame.size.width) / (float)plotLength; double halfHeight = floor( frame.size.height / 2.0 ); // iOS drawing origin is flipped by default so make sure we account for that int deviceOriginFlipped = 1; #if TARGET_OS_IPHONE deviceOriginFlipped = -1; #elif TARGET_OS_MAC deviceOriginFlipped = 1; #endif CGAffineTransform xf = CGAffineTransformIdentity; xf = CGAffineTransformTranslate( xf, frame.origin.x , halfHeight + frame.origin.y ); xf = CGAffineTransformScale( xf, xscale, deviceOriginFlipped*halfHeight ); CGPathAddPath( path, &xf, halfPath ); if( self.shouldMirror ){ xf = CGAffineTransformIdentity; xf = CGAffineTransformTranslate( xf, frame.origin.x , halfHeight + frame.origin.y); xf = CGAffineTransformScale( xf, xscale, -deviceOriginFlipped*(halfHeight)); CGPathAddPath( path, &xf, halfPath ); } CGPathRelease( halfPath ); // Now, path contains the full waveform path. CGContextAddPath(ctx, path); // Make this color customizable if( self.shouldFill ){ CGContextFillPath(ctx); } else { CGContextStrokePath(ctx); } CGPathRelease(path); } #if TARGET_OS_IPHONE CGContextRestoreGState(ctx); #elif TARGET_OS_MAC [[NSGraphicsContext currentContext] restoreGraphicsState]; #endif } #pragma mark - Adjust Resolution -(int)setRollingHistoryLength:(int)historyLength { historyLength = MIN(historyLength,kEZAudioPlotMaxHistoryBufferLength); size_t floatByteSize = sizeof(float); _changingHistorySize = YES; if( _scrollHistoryLength != historyLength ){ _scrollHistoryLength = historyLength; } _scrollHistory = realloc(_scrollHistory,_scrollHistoryLength*floatByteSize); if( _scrollHistoryIndex < _scrollHistoryLength ){ memset(&_scrollHistory[_scrollHistoryIndex], 0, (_scrollHistoryLength-_scrollHistoryIndex)*floatByteSize); } else { _scrollHistoryIndex = _scrollHistoryLength; } _changingHistorySize = NO; return historyLength; } -(int)rollingHistoryLength { return _scrollHistoryLength; } -(void)dealloc { if( plotData ){ free(plotData); } } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudioPlotGL.h ================================================ // // EZAudioPlotGL.h // EZAudio // // Created by Syed Haris Ali on 11/22/13. // Copyright (c) 2013 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 "TargetConditionals.h" #import "EZPlot.h" #if TARGET_OS_IPHONE #import @class EZAudioPlotGLKViewController; #elif TARGET_OS_MAC #import #import #import #import #endif #pragma mark - Enumerations /** Constant drawing types wrapping around the OpenGL equivalents. In the audio drawings the line strip will be the stroked graph while the triangle will provide the filled equivalent. */ typedef NS_ENUM(NSUInteger,EZAudioPlotGLDrawType){ /** * Maps to the OpenGL constant for a line strip, which for the audio graph will correspond to a stroked drawing (no fill). */ EZAudioPlotGLDrawTypeLineStrip = GL_LINE_STRIP, /** * Maps to the OpenGL constant for a triangle strip, which for the audio graph will correspond to a filled drawing. */ EZAudioPlotGLDrawTypeTriangleStrip = GL_TRIANGLE_STRIP }; #pragma mark - Structures /** A structure describing a 2D point (x,y) in space for an audio plot. */ typedef struct { GLfloat x; GLfloat y; } EZAudioPlotGLPoint; /** EZAudioPlotGL is a subclass of either the EZPlot on iOS or an NSOpenGLView on OSX. I apologize ahead of time for the weirdness in the docs for this class, but I had to do a bit of hackery to get a universal namespace for something works on both iOS and OSX without any additional components. The EZAudioPlotGL provides an the same utilities and interface as the EZAudioPlot with the added benefit of being GPU-accelerated. This is the recommended plot to use on iOS devices to get super fast real-time drawings of audio streams. For the methods and properties below I've included notes on the bottom just indicating which OS they correspond to. In most (if not all) use cases you can just refer to the EZPlot documentation to see which custom properties can be setup. There update function is the same as the EZPlot as well: `updateBuffer:withBufferSize:` */ #if TARGET_OS_IPHONE @interface EZAudioPlotGL : EZPlot #elif TARGET_OS_MAC @interface EZAudioPlotGL : NSOpenGLView #endif #if TARGET_OS_IPHONE // Inherited from EZPlot #elif TARGET_OS_MAC #pragma mark - Properties ///----------------------------------------------------------- /// @name Customizing The Plot's Appearance ///----------------------------------------------------------- /** The default background color of the plot. For iOS the color is specified as a UIColor while for OSX the color is an NSColor. The default value on both platforms is black. */ @property (nonatomic,strong) id backgroundColor; /** The default color of the plot's data (i.e. waveform, y-axis values). For iOS the color is specified as a UIColor while for OSX the color is an NSColor. The default value on both platforms is red. */ @property (nonatomic,strong) id color; /** The plot's gain value, which controls the scale of the y-axis values. The default value of the gain is 1.0f and should always be greater than 0.0f. */ @property (nonatomic,assign,setter=setGain:) float gain; /** The type of plot as specified by the `EZPlotType` enumeration (i.e. a buffer or rolling plot type). */ @property (nonatomic,assign,setter=setPlotType:) EZPlotType plotType; /** A BOOL indicating whether or not to fill in the graph. A value of YES will make a filled graph (filling in the space between the x-axis and the y-value), while a value of NO will create a stroked graph (connecting the points along the y-axis). */ @property (nonatomic,assign,setter=setShouldFill:) BOOL shouldFill; /** A boolean indicating whether the graph should be rotated along the x-axis to give a mirrored reflection. This is typical for audio plots to produce the classic waveform look. A value of YES will produce a mirrored reflection of the y-values about the x-axis, while a value of NO will only plot the y-values. */ @property (nonatomic,assign,setter=setShouldMirror:) BOOL shouldMirror; #pragma mark - Get Samples ///----------------------------------------------------------- /// @name Updating The Plot ///----------------------------------------------------------- /** Updates the plot with the new buffer data and tells the view to redraw itself. Caller will provide a float array with the values they expect to see on the y-axis. The plot will internally handle mapping the x-axis and y-axis to the current view port, any interpolation for fills effects, and mirroring. @param buffer A float array of values to map to the y-axis. @param bufferSize The size of the float array that will be mapped to the y-axis. @warning The bufferSize is expected to be the same, constant value once initial triggered. For plots using OpenGL a vertex buffer object will be allocated with a maximum buffersize of (2 * the initial given buffer size) to account for any interpolation necessary for filling in the graph. Updates use the glBufferSubData(...) function, which will crash if the buffersize exceeds the initial maximum allocated size. */ -(void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize; #endif #pragma mark - Adjust Resolution ///----------------------------------------------------------- /// @name Adjusting The Resolution ///----------------------------------------------------------- /** Sets the length of the rolling history display. Can grow or shrink the display up to the maximum size specified by the kEZAudioPlotMaxHistoryBufferLength macro. Will return the actual set value, which will be either the given value if smaller than the kEZAudioPlotMaxHistoryBufferLength or kEZAudioPlotMaxHistoryBufferLength if a larger value is attempted to be set. @param historyLength The new length of the rolling history buffer. @return The new value equal to the historyLength or the kEZAudioPlotMaxHistoryBufferLength. */ -(int)setRollingHistoryLength:(int)historyLength; /** Provides the length of the rolling history buffer * @return An int representing the length of the rolling history buffer */ -(int)rollingHistoryLength; #pragma mark - Shared Methods ///----------------------------------------------------------- /// @name Clearing The Plot ///----------------------------------------------------------- /** Clears all data from the audio plot (includes both EZPlotTypeBuffer and EZPlotTypeRolling) */ -(void)clear; ///----------------------------------------------------------- /// @name Shared OpenGL Methods ///----------------------------------------------------------- /** Converts a float array to an array of EZAudioPlotGLPoint structures that hold the (x,y) values the OpenGL buffer needs to properly plot its points. @param graph A pointer to the array that should hold the EZAudioPlotGLPoint structures. @param graphSize The size (or length) of the array with the EZAudioPlotGLPoint structures. @param drawingType The EZAudioPlotGLDrawType constant defining whether the plot should interpolate between points for a triangle strip (filled waveform) or not for a line strip (stroked waveform) @param buffer The float array holding the audio data @param bufferSize The size of the float array holding the audio data @param gain The gain (always greater than 0.0) to apply to the amplitudes (y-values) of the graph. Y-values can only range from -1.0 to 1.0 so any value that's greater will be rounded to -1.0 or 1.0. */ +(void)fillGraph:(EZAudioPlotGLPoint*)graph withGraphSize:(UInt32)graphSize forDrawingType:(EZAudioPlotGLDrawType)drawingType withBuffer:(float*)buffer withBufferSize:(UInt32)bufferSize withGain:(float)gain; /** Determines the proper size of a graph given a EZAudioPlotGLDrawType (line strip or triangle strip) and the size of the incoming buffer. Triangle strips require interpolating between points so the buffer becomes 2*bufferSize @param drawingType The EZAudioPlotGLDraw type (line strip or triangle strip) @param bufferSize The size of the float array holding the audio data coming in. @return A Int32 representing the proper graph size that should be used to account for any necessary interpolating between points. */ +(UInt32)graphSizeForDrawingType:(EZAudioPlotGLDrawType)drawingType withBufferSize:(UInt32)bufferSize; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudioPlotGL.m ================================================ // // EZAudioPlotGL.m // EZAudio // // Created by Syed Haris Ali on 11/22/13. // Copyright (c) 2013 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 "EZAudioPlotGL.h" #import "EZAudio.h" #if TARGET_OS_IPHONE #import "EZAudioPlotGLKViewController.h" @interface EZAudioPlotGL () @property (nonatomic,strong,readonly) EZAudioPlotGLKViewController *glViewController; @end #elif TARGET_OS_MAC @interface EZAudioPlotGL (){ // Flags indicating whether the plots have been instantiated BOOL _hasBufferPlotData; BOOL _hasRollingPlotData; // Vertex Array Buffers GLuint _bufferPlotVAB; GLuint _rollingPlotVAB; // Vertex Buffer Objects GLuint _bufferPlotVBO; GLuint _rollingPlotVBO; // Display Link CVDisplayLinkRef _displayLink; // Buffers size UInt32 _bufferPlotGraphSize; UInt32 _rollingPlotGraphSize; // Rolling History BOOL _setMaxLength; float *_scrollHistory; int _scrollHistoryIndex; UInt32 _scrollHistoryLength; BOOL _changingHistorySize; // Copied buffer data float *_copiedBuffer; UInt32 _copiedBufferSize; } @property (nonatomic,assign,readonly) EZAudioPlotGLDrawType drawingType; @property (nonatomic,strong) GLKBaseEffect *baseEffect; @end #endif @implementation EZAudioPlotGL #if TARGET_OS_IPHONE @synthesize glViewController = _glViewController; #elif TARGET_OS_MAC @synthesize baseEffect = _baseEffect; #endif @synthesize backgroundColor = _backgroundColor; @synthesize color = _color; @synthesize gain = _gain; @synthesize plotType = _plotType; @synthesize shouldFill = _shouldFill; @synthesize shouldMirror = _shouldMirror; #pragma mark - Initialization -(id)init { self = [super init]; if (self) { [self initializeView]; } return self; } -(id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if(self){ [self initializeView]; } return self; } -(id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self initializeView]; } return self; } #pragma mark - Initialize Properties Here -(void)initializeView { #if TARGET_OS_IPHONE // Initialize the subview controller _glViewController = [[EZAudioPlotGLKViewController alloc] init]; _glViewController.view.frame = self.bounds; [self insertSubview:self.glViewController.view atIndex:0]; #elif TARGET_OS_MAC _copiedBuffer = NULL; #endif // Set the default properties self.gain = 1.0; self.plotType = EZPlotTypeBuffer; #if TARGET_OS_IPHONE self.backgroundColor = [UIColor colorWithRed:0.796 green:0.749 blue:0.663 alpha:1]; self.color = [UIColor colorWithRed:0.481 green:0.548 blue:0.637 alpha:1]; #elif TARGET_OS_MAC _scrollHistory = NULL; _scrollHistoryLength = kEZAudioPlotDefaultHistoryBufferLength; #endif self.shouldFill = NO; self.shouldMirror = NO; } #pragma mark - Setters -(void)setBackgroundColor:(id)backgroundColor { _backgroundColor = backgroundColor; #if TARGET_OS_IPHONE self.glViewController.backgroundColor = backgroundColor; #elif TARGET_OS_MAC [self _refreshWithBackgroundColor:backgroundColor]; #endif } -(void)setColor:(id)color { _color = color; #if TARGET_OS_IPHONE self.glViewController.color = color; #elif TARGET_OS_MAC [self _refreshWithColor:color]; #endif } #if TARGET_OS_IPHONE #elif TARGET_OS_MAC -(void)setDrawingType:(EZAudioPlotGLDrawType)drawingType { CGLLockContext([[self openGLContext] CGLContextObj]); _drawingType = drawingType; CGLUnlockContext([[self openGLContext] CGLContextObj]); } #endif #if TARGET_OS_IPHONE -(void)setGain:(float)gain { _gain = gain; self.glViewController.gain = gain; } #elif TARGET_OS_MAC // Gain changed mac #endif #if TARGET_OS_IPHONE -(void)setPlotType:(EZPlotType)plotType { _plotType = plotType; self.glViewController.plotType = plotType; } #elif TARGET_OS_MAC // Plot type changed mac #endif -(void)setShouldFill:(BOOL)shouldFill { _shouldFill = shouldFill; #if TARGET_OS_IPHONE self.glViewController.drawingType = shouldFill ? EZAudioPlotGLDrawTypeTriangleStrip : EZAudioPlotGLDrawTypeLineStrip; #elif TARGET_OS_MAC // Fill flag changed mac self.drawingType = shouldFill ? EZAudioPlotGLDrawTypeTriangleStrip : EZAudioPlotGLDrawTypeLineStrip; #endif } #if TARGET_OS_IPHONE -(void)setShouldMirror:(BOOL)shouldMirror { _shouldMirror = shouldMirror; self.glViewController.shouldMirror = shouldMirror; } #elif TARGET_OS_MAC // Mirror flag changed mac #endif #pragma mark - Get Samples -(void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize { #if TARGET_OS_IPHONE [self.glViewController updateBuffer:buffer withBufferSize:bufferSize]; #elif TARGET_OS_MAC if( _copiedBuffer == NULL ){ _copiedBuffer = (float*)malloc(bufferSize*sizeof(float)); } _copiedBufferSize = bufferSize; // Copy the buffer memcpy(_copiedBuffer, buffer, bufferSize*sizeof(float)); // Draw based on plot type switch(_plotType) { case EZPlotTypeBuffer: [self _updateBufferPlotBufferWithAudioReceived:_copiedBuffer withBufferSize:_copiedBufferSize]; break; case EZPlotTypeRolling: [self _updateRollingPlotBufferWithAudioReceived:_copiedBuffer withBufferSize:_copiedBufferSize]; break; default: break; } #endif } #pragma mark - OSX Specific GL Implementation #if TARGET_OS_IPHONE // Handled by the embedded GLKViewController #elif TARGET_OS_MAC #pragma mark - Awake -(void)awakeFromNib { // Setup the base effect [self _setupBaseEffect]; // Setup the OpenGL Pixel Format and Context [self _setupProfile]; // Setup view [self _setupView]; } -(void)_setupBaseEffect { self.baseEffect = [[GLKBaseEffect alloc] init]; self.baseEffect.useConstantColor = GL_TRUE; self.baseEffect.constantColor = GLKVector4Make(0.489, 0.34, 0.185, 1.0); } -(void)_setupProfile { NSOpenGLPixelFormatAttribute attrs[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFAMultisample, NSOpenGLPFASampleBuffers, 1, NSOpenGLPFASamples, 4, NSOpenGLPFADepthSize, 24, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, 0 }; NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; if (!pf) { NSLog(@"No OpenGL pixel format"); } NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; // Debug only CGLEnable([context CGLContextObj], kCGLCECrashOnRemovedFunctions); self.pixelFormat = pf; self.openGLContext = context; } -(void)_setupView { self.backgroundColor = [NSColor colorWithCalibratedRed: 0.796 green: 0.749 blue: 0.663 alpha: 1]; self.color = [NSColor colorWithCalibratedRed: 0.481 green: 0.548 blue: 0.637 alpha: 1]; } #pragma mark - Prepare -(void)prepareOpenGL { [super prepareOpenGL]; GLint swapInt = 1; [self.openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; //////////////////////////////////////////////////////////////////////////// // Setup VABs and VBOs // //////////////////////////////////////////////////////////////////////////// // Buffer glGenVertexArrays(1,&_bufferPlotVAB); glBindVertexArray(_bufferPlotVAB); glGenBuffers(1,&_bufferPlotVBO); glBindBuffer(GL_ARRAY_BUFFER,_bufferPlotVBO); // Rolling glGenVertexArrays(1,&_rollingPlotVAB); glBindVertexArray(_rollingPlotVAB); glGenBuffers(1,&_rollingPlotVBO); glBindBuffer(GL_ARRAY_BUFFER,_rollingPlotVBO); if( self.shouldFill ){ glBindVertexArray(_rollingPlotVAB); glBindBuffer(GL_ARRAY_BUFFER,_rollingPlotVBO); } // Enable anti-aliasing glEnable(GL_MULTISAMPLE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glClearColor(0, 0, 0, 0); self.layer = nil; // Set the background color [self _refreshWithBackgroundColor:self.backgroundColor]; [self _refreshWithColor:self.color]; // Setup the display link (rendering loop) [self _setupDisplayLink]; } -(void)_setupDisplayLink { // Create a display link capable of being used with all active displays CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); // Set the renderer output callback function CVDisplayLinkSetOutputCallback(_displayLink, &DisplayLinkCallback, (__bridge void *)(self)); // Set the display link for the current renderer CGLContextObj cglContext = self.openGLContext.CGLContextObj; CGLPixelFormatObj cglPixelFormat = self.pixelFormat.CGLPixelFormatObj; CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat); // Activate the display link CVDisplayLinkStart(_displayLink); // Register to be notified when the window closes so we can stop the displaylink [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillClose:) name:NSWindowWillCloseNotification object:[self window]]; } - (void) windowWillClose:(NSNotification*)notification { // Stop the display link when the window is closing because default // OpenGL render buffers will be destroyed. If display link continues to // fire without renderbuffers, OpenGL draw calls will set errors. CVDisplayLinkStop(_displayLink); } #pragma mark - Display Link Callback // This is the renderer output callback function static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) { CVReturn result = [(__bridge EZAudioPlotGL*)displayLinkContext getFrameForTime:outputTime]; return result; } - (CVReturn)getFrameForTime:(const CVTimeStamp*)outputTime { @autoreleasepool { [self drawFrame]; } return kCVReturnSuccess; } #pragma mark - Buffer Updating By Type -(void)_updateBufferPlotBufferWithAudioReceived:(float*)buffer withBufferSize:(UInt32)bufferSize { // Lock CGLLockContext([[self openGLContext] CGLContextObj]); // Bind to buffer VBO glBindVertexArray(_bufferPlotVAB); glBindBuffer(GL_ARRAY_BUFFER,_bufferPlotVBO); // If starting with a VBO of half of our max size make sure we initialize it to anticipate // a filled graph (which needs 2 * bufferSize) to allocate its resources properly if( !_hasBufferPlotData && _drawingType == EZAudioPlotGLDrawTypeLineStrip ){ EZAudioPlotGLPoint maxGraph[2*bufferSize]; glBufferData(GL_ARRAY_BUFFER, sizeof(maxGraph), maxGraph, GL_STREAM_DRAW ); _hasBufferPlotData = YES; } // Setup the buffer plot's graph size _bufferPlotGraphSize = [EZAudioPlotGL graphSizeForDrawingType:_drawingType withBufferSize:bufferSize]; // Setup the graph EZAudioPlotGLPoint graph[_bufferPlotGraphSize]; // Fill in graph data [EZAudioPlotGL fillGraph:graph withGraphSize:_bufferPlotGraphSize forDrawingType:_drawingType withBuffer:buffer withBufferSize:bufferSize withGain:self.gain]; // Update the drawing if( !_hasBufferPlotData ){ glBufferData(GL_ARRAY_BUFFER, sizeof(graph) , graph, GL_STREAM_DRAW); _hasBufferPlotData = YES; } else { glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(graph), graph); } // Unlock CGLUnlockContext([[self openGLContext] CGLContextObj]); } -(void)_updateRollingPlotBufferWithAudioReceived:(float*)buffer withBufferSize:(UInt32)bufferSize { // Lock CGLLockContext([[self openGLContext] CGLContextObj]); // Bind to rolling VBO glBindVertexArray(_rollingPlotVAB); glBindBuffer(GL_ARRAY_BUFFER,_rollingPlotVBO); // If starting with a VBO of half of our max size make sure we initialize it to anticipate // a filled graph (which needs 2 * bufferSize) to allocate its resources properly if( !_hasRollingPlotData ){ EZAudioPlotGLPoint maxGraph[2*kEZAudioPlotMaxHistoryBufferLength]; glBufferData(GL_ARRAY_BUFFER, sizeof(maxGraph), maxGraph, GL_STREAM_DRAW ); _hasRollingPlotData = YES; } // Setup the plot _rollingPlotGraphSize = [EZAudioPlotGL graphSizeForDrawingType:_drawingType withBufferSize:_scrollHistoryLength]; // Fill the graph with data EZAudioPlotGLPoint graph[_rollingPlotGraphSize]; // Update the scroll history datasource [EZAudio updateScrollHistory:&_scrollHistory withLength:_scrollHistoryLength atIndex:&_scrollHistoryIndex withBuffer:buffer withBufferSize:bufferSize isResolutionChanging:&_changingHistorySize]; // Fill in graph data [EZAudioPlotGL fillGraph:graph withGraphSize:_rollingPlotGraphSize forDrawingType:_drawingType withBuffer:_scrollHistory withBufferSize:_scrollHistoryLength withGain:self.gain]; // Update the drawing if( !_hasRollingPlotData ){ glBufferData(GL_ARRAY_BUFFER, sizeof(graph), graph, GL_STREAM_DRAW); _hasRollingPlotData = YES; } else { glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(graph), graph); } // Unlock CGLUnlockContext([[self openGLContext] CGLContextObj]); } //#pragma mark - Render -(void)drawFrame { // Avoid flickering during resize by drawing [[self openGLContext] makeCurrentContext]; // Lock CGLLockContext([[self openGLContext] CGLContextObj]); // Draw frame glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); if( _hasBufferPlotData || _hasRollingPlotData ){ // Plot either a buffer plot or a rolling plot switch(_plotType) { case EZPlotTypeBuffer: [self _drawBufferPlot]; break; case EZPlotTypeRolling: [self _drawRollingPlot]; break; default: break; } } // Flush and unlock CGLFlushDrawable([[self openGLContext] CGLContextObj]); CGLUnlockContext([[self openGLContext] CGLContextObj]); } -(void)_drawBufferPlot { glBindVertexArray(_bufferPlotVAB); glBindBuffer(GL_ARRAY_BUFFER,_bufferPlotVBO); [self.baseEffect prepareToDraw]; self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeXRotation(0); // Enable the vertex data glEnableVertexAttribArray(GLKVertexAttribPosition); // Define the vertex data size & layout glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(EZAudioPlotGLPoint), NULL); // Draw the triangle glDrawArrays(_drawingType,0,_bufferPlotGraphSize); // Mirrored if( self.shouldMirror ){ [self.baseEffect prepareToDraw]; self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeXRotation(M_PI); // Enable the vertex data glEnableVertexAttribArray(GLKVertexAttribPosition); // Define the vertex data size & layout glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(EZAudioPlotGLPoint), NULL); // Draw the triangle glDrawArrays(_drawingType, 0, _bufferPlotGraphSize); } } -(void)_drawRollingPlot { glBindVertexArray(_rollingPlotVAB); glBindBuffer(GL_ARRAY_BUFFER,_rollingPlotVBO); [self.baseEffect prepareToDraw]; self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeXRotation(0); // Enable the vertex data glEnableVertexAttribArray(GLKVertexAttribPosition); // Define the vertex data size & layout glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(EZAudioPlotGLPoint), NULL); // Draw the triangle glDrawArrays(_drawingType, 0,_rollingPlotGraphSize); // Mirrored if( self.shouldMirror ){ [self.baseEffect prepareToDraw]; self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeXRotation(M_PI); // Enable the vertex data glEnableVertexAttribArray(GLKVertexAttribPosition); // Define the vertex data size & layout glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(EZAudioPlotGLPoint), NULL); // Draw the triangle glDrawArrays(_drawingType, 0,_rollingPlotGraphSize); } } -(void)drawRect:(NSRect)dirtyRect { [self drawFrame]; } #pragma mark - Reshape -(void)reshape { [super reshape]; // We draw on a secondary thread through the display link. However, when // resizing the view, -drawRect is called on the main thread. // Add a mutex around to avoid the threads accessing the context // simultaneously when resizing. CGLLockContext([[self openGLContext] CGLContextObj]); // Get the view size in Points NSRect viewRectPoints = [self bounds]; NSRect viewRectPixels = [self convertRectToBacking:viewRectPoints]; // Set the new dimensions in our renderer glViewport(0, 0, viewRectPixels.size.width, viewRectPixels.size.height); CGLUnlockContext([[self openGLContext] CGLContextObj]); } #pragma mark - Private Setters -(void)_refreshWithBackgroundColor:(NSColor*)backgroundColor { CGLLockContext([[self openGLContext] CGLContextObj]); // Extract colors CGFloat red; CGFloat green; CGFloat blue; CGFloat alpha; [backgroundColor getRed:&red green:&green blue:&blue alpha:&alpha]; // Set them on the context glClearColor((GLclampf)red,(GLclampf)green,(GLclampf)blue,(GLclampf)alpha); CGLUnlockContext([[self openGLContext] CGLContextObj]); } -(void)_refreshWithColor:(NSColor*)color { CGLLockContext([[self openGLContext] CGLContextObj]); // Extract colors CGFloat red; CGFloat green; CGFloat blue; CGFloat alpha; [color getRed:&red green:&green blue:&blue alpha:&alpha]; // Set them on the base shader self.baseEffect.constantColor = GLKVector4Make((GLclampf)red,(GLclampf)green,(GLclampf)blue,(GLclampf)alpha); CGLUnlockContext([[self openGLContext] CGLContextObj]); } #pragma mark - Cleanup - (void) dealloc { // Stop the display link BEFORE releasing anything in the view // otherwise the display link thread may call into the view and crash // when it encounters something that has been release CVDisplayLinkStop(_displayLink); CVDisplayLinkRelease(_displayLink); if( _copiedBuffer != NULL ){ free( _copiedBuffer ); } } #endif #pragma mark - Adjust Resolution -(int)setRollingHistoryLength:(int)historyLength { #if TARGET_OS_IPHONE int result = [self.glViewController setRollingHistoryLength:historyLength]; return result; #elif TARGET_OS_MAC historyLength = MIN(historyLength,kEZAudioPlotMaxHistoryBufferLength); size_t floatByteSize = sizeof(float); _changingHistorySize = YES; if( _scrollHistoryLength != historyLength ){ _scrollHistoryLength = historyLength; } _scrollHistory = realloc(_scrollHistory,_scrollHistoryLength*floatByteSize); if( _scrollHistoryIndex < _scrollHistoryLength ){ memset(&_scrollHistory[_scrollHistoryIndex], 0, (_scrollHistoryLength-_scrollHistoryIndex)*floatByteSize); } else { _scrollHistoryIndex = _scrollHistoryLength; } _changingHistorySize = NO; return historyLength; #endif return kEZAudioPlotDefaultHistoryBufferLength; } -(int)rollingHistoryLength { #if TARGET_OS_IPHONE return self.glViewController.rollingHistoryLength; #elif TARGET_OS_MAC return _scrollHistoryLength; #endif } #pragma mark - Clearing -(void)clear { #if TARGET_OS_IPHONE [self.glViewController clear]; #elif TARGET_OS_MAC #endif } #pragma mark - Graph Methods +(void)fillGraph:(EZAudioPlotGLPoint*)graph withGraphSize:(UInt32)graphSize forDrawingType:(EZAudioPlotGLDrawType)drawingType withBuffer:(float*)buffer withBufferSize:(UInt32)bufferSize withGain:(float)gain { if( drawingType == EZAudioPlotGLDrawTypeLineStrip ){ // graph size = buffer size to stroke waveform for(int i = 0; i < graphSize; i++){ float x = [EZAudio MAP:i leftMin:0 leftMax:bufferSize rightMin:-1.0 rightMax:1.0]; graph[i].x = x; graph[i].y = gain*buffer[i]; } } else if( drawingType == EZAudioPlotGLDrawTypeTriangleStrip ) { // graph size = 2 * buffer size to draw triangles and fill regions properly for(int i = 0; i < graphSize; i+=2){ int bufferIndex = (int)[EZAudio MAP:i leftMin:0 leftMax:graphSize rightMin:0 rightMax:bufferSize]; float x = [EZAudio MAP:bufferIndex leftMin:0 leftMax:bufferSize rightMin:-1.0 rightMax:1.0]; graph[i].x = x; graph[i].y = 0.0f; } for(int i = 0; i < graphSize; i+=2){ int bufferIndex = (int)[EZAudio MAP:i leftMin:0 leftMax:graphSize rightMin:0 rightMax:bufferSize]; float x = [EZAudio MAP:bufferIndex leftMin:0 leftMax:bufferSize rightMin:-1.0 rightMax:1.0]; graph[i+1].x = x; graph[i+1].y = gain*buffer[bufferIndex]; } } } +(UInt32)graphSizeForDrawingType:(EZAudioPlotGLDrawType)drawingType withBufferSize:(UInt32)bufferSize { UInt32 graphSize = bufferSize; switch(drawingType) { case EZAudioPlotGLDrawTypeLineStrip: graphSize = bufferSize; break; case EZAudioPlotGLDrawTypeTriangleStrip: graphSize = 2*bufferSize; break; default: break; } return graphSize; } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudioPlotGLKViewController.h ================================================ // // EZAudioPlotGLKViewController.h // EZAudio // // Created by Syed Haris Ali on 11/22/13. // Copyright (c) 2013 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 "TargetConditionals.h" #if TARGET_OS_IPHONE #import "EZAudioPlotGL.h" @class EZAudio; /** EZAudioPlotGLKViewController is a subclass of the GLKViewController and handles the OpenGL drawing routine for iOS OpenGL ES views. This class has not been used outside the scope of the EZAudioPlotGL, but should be safe to use by itself if the intended use case is to have a view controller take up the whole screen. */ @interface EZAudioPlotGLKViewController : GLKViewController #pragma mark - Properties ///----------------------------------------------------------- /// @name Customizing The Plot's Appearance ///----------------------------------------------------------- /** The default background color of the plot. For iOS the color is specified as a UIColor while for OSX the color is an NSColor. The default value on both platforms is black. */ @property (nonatomic,strong) UIColor *backgroundColor; /** The default shader to use to fill the graph. */ @property (nonatomic,strong) GLKBaseEffect *baseEffect; /** The default color of the plot's data (i.e. waveform, y-axis values). For iOS the color is specified as a UIColor while for OSX the color is an NSColor. The default value on both platforms is red. */ @property (nonatomic,strong) UIColor *color; /** The OpenGL ES context (EAGLContext) in which to perform the drawing */ @property (nonatomic,strong) EAGLContext *context; /** The EZAudioPlotGLDrawType specifying which OpenGL primitive to use for drawing (either line strip for stroke and no fill or triangle strip for fill) */ @property (nonatomic,assign) EZAudioPlotGLDrawType drawingType; /** The plot's gain value, which controls the scale of the y-axis values. The default value of the gain is 1.0f and should always be greater than 0.0f. */ @property (nonatomic,assign,setter=setGain:) float gain; /** The type of plot as specified by the `EZPlotType` enumeration (i.e. a buffer or rolling plot type). */ @property (nonatomic,assign,setter=setPlotType:) EZPlotType plotType; /** A boolean indicating whether the graph should be rotated along the x-axis to give a mirrored reflection. This is typical for audio plots to produce the classic waveform look. A value of YES will produce a mirrored reflection of the y-values about the x-axis, while a value of NO will only plot the y-values. */ @property (nonatomic,assign,setter=setShouldMirror:) BOOL shouldMirror; #pragma mark - Adjust Resolution ///----------------------------------------------------------- /// @name Adjusting The Resolution ///----------------------------------------------------------- /** Sets the length of the rolling history display. Can grow or shrink the display up to the maximum size specified by the kEZAudioPlotMaxHistoryBufferLength macro. Will return the actual set value, which will be either the given value if smaller than the kEZAudioPlotMaxHistoryBufferLength or kEZAudioPlotMaxHistoryBufferLength if a larger value is attempted to be set. @param historyLength The new length of the rolling history buffer. @return The new value equal to the historyLength or the kEZAudioPlotMaxHistoryBufferLength. */ -(int)setRollingHistoryLength:(int)historyLength; /** Provides the length of the rolling history buffer * @return An int representing the length of the rolling history buffer */ -(int)rollingHistoryLength; #pragma mark - Clearing ///----------------------------------------------------------- /// @name Clearing The Plot ///----------------------------------------------------------- /** Clears all data from the audio plot (includes both EZPlotTypeBuffer and EZPlotTypeRolling) */ -(void)clear; #pragma mark - Get Samples ///----------------------------------------------------------- /// @name Updating The Plot ///----------------------------------------------------------- /** Updates the plot with the new buffer data and tells the view to redraw itself. Caller will provide a float array with the values they expect to see on the y-axis. The plot will internally handle mapping the x-axis and y-axis to the current view port, any interpolation for fills effects, and mirroring. @param buffer A float array of values to map to the y-axis. @param bufferSize The size of the float array that will be mapped to the y-axis. @warning The bufferSize is expected to be the same, constant value once initial triggered. For plots using OpenGL a vertex buffer object will be allocated with a maximum buffersize of (2 * the initial given buffer size) to account for any interpolation necessary for filling in the graph. Updates use the glBufferSubData(...) function, which will crash if the buffersize exceeds the initial maximum allocated size. */ -(void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize; @end #elif TARGET_OS_MAC #endif ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZAudioPlotGLKViewController.m ================================================ // // EZAudioPlotGLKViewController.m // EZAudio // // Created by Syed Haris Ali on 11/22/13. // Copyright (c) 2013 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. #if TARGET_OS_IPHONE #import "EZAudioPlotGLKViewController.h" #import "EZAudio.h" @interface EZAudioPlotGLKViewController () { // Flags indicating whether the plots have been instantiated BOOL _hasBufferPlotData; BOOL _hasRollingPlotData; // The buffers GLuint _bufferPlotVBO; GLuint _rollingPlotVBO; // Buffers size UInt32 _bufferPlotGraphSize; UInt32 _rollingPlotGraphSize; // Rolling History BOOL _setMaxLength; float *_scrollHistory; int _scrollHistoryIndex; UInt32 _scrollHistoryLength; BOOL _changingHistorySize; } @end @implementation EZAudioPlotGLKViewController @synthesize baseEffect = _baseEffect; @synthesize context = _context; @synthesize drawingType = _drawingType; @synthesize plotType = _plotType; @synthesize shouldMirror = _shouldMirror; #pragma mark - Initialization -(id)init { self = [super init]; if (self) { [self initializeView]; } return self; } -(id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if(self){ [self initializeView]; } return self; } -(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if(self){ [self initializeView]; } return self; } #pragma mark - Initialize Properties Here -(void)initializeView { // Setup the base effect self.baseEffect = [[GLKBaseEffect alloc] init]; self.baseEffect.useConstantColor = GL_TRUE; self.preferredFramesPerSecond = 60; _scrollHistory = NULL; _scrollHistoryLength = kEZAudioPlotDefaultHistoryBufferLength; } #pragma mark - View Did Load -(void)viewDidLoad { [super viewDidLoad]; // Setup the context if( ![EAGLContext currentContext] ) { self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; } else { self.context = [EAGLContext currentContext]; } if (!self.context) { NSLog(@"Failed to create ES context"); } else { EAGLContext.currentContext = self.context; } // Set the view's context GLKView *view = (GLKView *)self.view; view.context = self.context; view.drawableMultisample = GLKViewDrawableMultisample4X; // Generate both the buffer id references glGenBuffers(1, &_bufferPlotVBO); glGenBuffers(1, &_rollingPlotVBO); // Refresh color values [self _refreshWithBackgroundColor: self.backgroundColor]; [self _refreshWithColor: self.color]; // Set the line width for the context glLineWidth(2.0); } #pragma mark - Adjust Resolution -(int)setRollingHistoryLength:(int)historyLength { _changingHistorySize = YES; historyLength = MIN(historyLength,kEZAudioPlotMaxHistoryBufferLength); size_t floatByteSize = sizeof(float); if( _scrollHistoryLength != historyLength ){ _scrollHistoryLength = historyLength; } _scrollHistory = realloc(_scrollHistory,_scrollHistoryLength*floatByteSize); if( _scrollHistoryIndex < _scrollHistoryLength ){ memset(&_scrollHistory[_scrollHistoryIndex], 0, (_scrollHistoryLength-_scrollHistoryIndex)*floatByteSize); } else { _scrollHistoryIndex = _scrollHistoryLength; } [self _updateRollingPlotDisplay]; _changingHistorySize = NO; return historyLength; } -(int)rollingHistoryLength { return _scrollHistoryLength; } #pragma mark - Clearing -(void)clear { _scrollHistoryIndex = 0; [self _clearBufferPlot]; [self _clearRollingPlot]; } -(void)_clearBufferPlot { if( _hasBufferPlotData ) { float empty[_bufferPlotGraphSize]; memset( empty, 0.0f, sizeof(float) ); [self _updateBufferPlotBufferWithAudioReceived:empty withBufferSize:_bufferPlotGraphSize]; } } -(void)_clearRollingPlot { if( _hasRollingPlotData ) { float empty[_rollingPlotGraphSize]; EZAudioPlotGLPoint graph[_rollingPlotGraphSize]; // Figure out better way to do this for(int i = 0; i < _rollingPlotGraphSize; i++ ) { empty[i] = 0.0f; } for(int i = 0; i < _scrollHistoryLength; i++) { _scrollHistory[i] = 0.0f; } // Update the scroll history datasource [EZAudio updateScrollHistory:&_scrollHistory withLength:_scrollHistoryLength atIndex:&_scrollHistoryIndex withBuffer:empty withBufferSize:_rollingPlotGraphSize isResolutionChanging:&_changingHistorySize]; // Fill in graph data [EZAudioPlotGL fillGraph:graph withGraphSize:_rollingPlotGraphSize forDrawingType:_drawingType withBuffer:_scrollHistory withBufferSize:_scrollHistoryLength withGain:self.gain]; // Update the drawing glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(graph), graph); } } #pragma mark - Get Samples -(void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize { // Make sure the update render loop is active if( self.paused ) self.paused = NO; // Make sure we are updating the buffers on the correct gl context. EAGLContext.currentContext = self.context; // Draw based on plot type switch(_plotType) { case EZPlotTypeBuffer: [self _updateBufferPlotBufferWithAudioReceived:buffer withBufferSize:bufferSize]; break; case EZPlotTypeRolling: [self _updateRollingPlotBufferWithAudioReceived:buffer withBufferSize:bufferSize]; break; default: break; } } #pragma mark - Buffer Updating By Type -(void)_updateBufferPlotBufferWithAudioReceived:(float*)buffer withBufferSize:(UInt32)bufferSize { glBindBuffer(GL_ARRAY_BUFFER, _bufferPlotVBO); // If starting with a VBO of half of our max size make sure we initialize it to anticipate // a filled graph (which needs 2 * bufferSize) to allocate its resources properly if( !_hasBufferPlotData && _drawingType == EZAudioPlotGLDrawTypeLineStrip ){ EZAudioPlotGLPoint maxGraph[2*bufferSize]; glBufferData(GL_ARRAY_BUFFER, sizeof(maxGraph), maxGraph, GL_STREAM_DRAW ); _hasBufferPlotData = YES; } // Setup the buffer plot's graph size _bufferPlotGraphSize = [EZAudioPlotGL graphSizeForDrawingType:_drawingType withBufferSize:bufferSize]; // Setup the graph EZAudioPlotGLPoint graph[_bufferPlotGraphSize]; // Fill in graph data [EZAudioPlotGL fillGraph:graph withGraphSize:_bufferPlotGraphSize forDrawingType:_drawingType withBuffer:buffer withBufferSize:bufferSize withGain:self.gain]; if( !_hasBufferPlotData ){ glBufferData( GL_ARRAY_BUFFER, sizeof(graph), graph, GL_STREAM_DRAW ); _hasBufferPlotData = YES; } else { glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(graph), graph); } glBindBuffer(GL_ARRAY_BUFFER, 0); } -(void)_updateRollingPlotBufferWithAudioReceived:(float*)buffer withBufferSize:(UInt32)bufferSize { glBindBuffer(GL_ARRAY_BUFFER, _rollingPlotVBO); // If starting with a VBO of half of our max size make sure we initialize it to anticipate // a filled graph (which needs 2 * bufferSize) to allocate its resources properly if( !_hasRollingPlotData ){ EZAudioPlotGLPoint maxGraph[2*kEZAudioPlotMaxHistoryBufferLength]; glBufferData( GL_ARRAY_BUFFER, sizeof(maxGraph), maxGraph, GL_STREAM_DRAW ); _hasRollingPlotData = YES; } // Setup the plot _rollingPlotGraphSize = [EZAudioPlotGL graphSizeForDrawingType:_drawingType withBufferSize:_scrollHistoryLength]; // Fill the graph with data EZAudioPlotGLPoint graph[_rollingPlotGraphSize]; // Update the scroll history datasource [EZAudio updateScrollHistory:&_scrollHistory withLength:_scrollHistoryLength atIndex:&_scrollHistoryIndex withBuffer:buffer withBufferSize:bufferSize isResolutionChanging:&_changingHistorySize]; // Fill in graph data [EZAudioPlotGL fillGraph:graph withGraphSize:_rollingPlotGraphSize forDrawingType:_drawingType withBuffer:_scrollHistory withBufferSize:_scrollHistoryLength withGain:self.gain]; // Update the drawing if( !_hasRollingPlotData ){ glBufferData( GL_ARRAY_BUFFER, sizeof(graph) , graph, GL_STREAM_DRAW ); _hasRollingPlotData = YES; } else { glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(graph), graph); } glBindBuffer(GL_ARRAY_BUFFER, 0); } -(void)_updateRollingPlotDisplay { // Setup the plot _rollingPlotGraphSize = [EZAudioPlotGL graphSizeForDrawingType:_drawingType withBufferSize:_scrollHistoryLength]; // Fill the graph with data EZAudioPlotGLPoint graph[_rollingPlotGraphSize]; // Fill in graph data [EZAudioPlotGL fillGraph:graph withGraphSize:_rollingPlotGraphSize forDrawingType:_drawingType withBuffer:_scrollHistory withBufferSize:_scrollHistoryLength withGain:self.gain]; // Update the drawing if( _hasRollingPlotData ){ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(graph), graph); } } #pragma mark - Drawing -(void)glkView:(GLKView *)view drawInRect:(CGRect)rect { EAGLContext.currentContext = self.context; // Clear the context glClear(GL_COLOR_BUFFER_BIT); if( _hasBufferPlotData || _hasRollingPlotData ){ // Prepare the effect for drawing [self.baseEffect prepareToDraw]; // Plot either a buffer plot or a rolling plot switch(_plotType) { case EZPlotTypeBuffer: [self _drawBufferPlotWithView:view drawInRect:rect]; break; case EZPlotTypeRolling: [self _drawRollingPlotWithView:view drawInRect:rect]; break; default: break; } } } #pragma mark - Private Drawing -(void)_drawBufferPlotWithView:(GLKView*)view drawInRect:(CGRect)rect { if( _hasBufferPlotData ){ glBindBuffer(GL_ARRAY_BUFFER, _bufferPlotVBO); glEnableVertexAttribArray(GLKVertexAttribPosition); glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(EZAudioPlotGLPoint), NULL); // Normal plot self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeXRotation(0); glDrawArrays(_drawingType, 0, _bufferPlotGraphSize); if( self.shouldMirror ){ // Mirrored plot [self.baseEffect prepareToDraw]; self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeXRotation(M_PI); glDrawArrays(_drawingType, 0, _bufferPlotGraphSize); } glBindBuffer(GL_ARRAY_BUFFER,0); } } -(void)_drawRollingPlotWithView:(GLKView*)view drawInRect:(CGRect)rect { if( _hasRollingPlotData ){ // Normal plot glBindBuffer(GL_ARRAY_BUFFER, _rollingPlotVBO); glEnableVertexAttribArray(GLKVertexAttribPosition); glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(EZAudioPlotGLPoint), NULL); // Normal plot self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeXRotation(0); glDrawArrays(_drawingType, 0, _rollingPlotGraphSize); if( self.shouldMirror ){ // Mirrored plot [self.baseEffect prepareToDraw]; self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeXRotation(3.14159265359); glDrawArrays(_drawingType, 0, _rollingPlotGraphSize); } glBindBuffer(GL_ARRAY_BUFFER,0); } } #pragma mark - Setters -(void)setBackgroundColor:(UIColor *)backgroundColor { // Set the background color _backgroundColor = backgroundColor; // Refresh background color (map to GL vector) [self _refreshWithBackgroundColor:backgroundColor]; } -(void)setColor:(UIColor *)color { // Set the color _color = color; // Refresh the color (map to GL vector) [self _refreshWithColor:color]; } #pragma mark - Private Setters -(void)_refreshWithBackgroundColor:(UIColor*)backgroundColor { // Extract colors CGFloat red; CGFloat green; CGFloat blue; CGFloat alpha; [backgroundColor getRed:&red green:&green blue:&blue alpha:&alpha]; // Set them on the context glClearColor((GLclampf)red,(GLclampf)green,(GLclampf)blue,(GLclampf)alpha); } -(void)_refreshWithColor:(UIColor*)color { // Extract colors CGFloat red; CGFloat green; CGFloat blue; CGFloat alpha; [color getRed:&red green:&green blue:&blue alpha:&alpha]; // Set them on the base shader self.baseEffect.constantColor = GLKVector4Make((GLclampf)red,(GLclampf)green,(GLclampf)blue,(GLclampf)alpha); } @end #elif TARGET_OS_MAC #endif ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZMicrophone.h ================================================ // // EZMicrophone.h // EZAudio // // Created by Syed Haris Ali on 9/2/13. // Copyright (c) 2013 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 #import #import "AEFloatConverter.h" #import "TargetConditionals.h" @class EZAudio; @class EZMicrophone; #pragma mark - EZMicrophoneDelegate /** The delegate for the EZMicrophone provides a receiver for the incoming audio data events. When the microphone has been successfully internally configured it will try to send its delegate an AudioStreamBasicDescription describing the format of the incoming audio data. The audio data itself is sent back to the delegate in various forms: -`microphone:hasAudioReceived:withBufferSize:withNumberOfChannels:` Provides float arrays instead of the AudioBufferList structure to hold the audio data. There could be a number of float arrays depending on the number of channels (see the function description below). These are useful for doing any visualizations that would like to make use of the raw audio data. -`microphone:hasBufferList:withBufferSize:withNumberOfChannels:` Provides the AudioBufferList structures holding the audio data. These are the native structures Core Audio uses to hold the buffer information and useful for piping out directly to an output (see EZOutput). */ @protocol EZMicrophoneDelegate @optional ///----------------------------------------------------------- /// @name Audio Data Description ///----------------------------------------------------------- /** Returns back the audio stream basic description as soon as it has been initialized. This is guaranteed to occur before the stream callbacks, `microphone:hasBufferList:withBufferSize:withNumberOfChannels:` or `microphone:hasAudioReceived:withBufferSize:withNumberOfChannels:` @param microphone The instance of the EZMicrophone that triggered the event. @param audioStreamBasicDescription The AudioStreamBasicDescription that was created for the microphone instance. */ -(void) microphone:(EZMicrophone *)microphone hasAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription; ///----------------------------------------------------------- /// @name Audio Data Callbacks ///----------------------------------------------------------- /** Returns back a float array of the audio received. This occurs on the background thread so any drawing code must explicity perform its functions on the main thread. @param microphone The instance of the EZMicrophone that triggered the event. @param buffer The audio data as an array of float arrays. In a stereo signal buffer[0] represents the left channel while buffer[1] would represent the right channel. @param bufferSize The size of each of the buffers (the length of each float array). @param numberOfChannels The number of channels for the incoming audio. @warning This function executes on a background thread to avoid blocking any audio operations. If operations should be performed on any other thread (like the main thread) it should be performed within a dispatch block like so: dispatch_async(dispatch_get_main_queue(), ^{ ...Your Code... }) */ -(void) microphone:(EZMicrophone*)microphone hasAudioReceived:(float**)buffer withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels; /** Returns back the buffer list containing the audio received. This occurs on the background thread so any drawing code must explicity perform its functions on the main thread. @param microphone The instance of the EZMicrophone that triggered the event. @param bufferList The AudioBufferList holding the audio data. @param bufferSize The size of each of the buffers of the AudioBufferList. @param numberOfChannels The number of channels for the incoming audio. @warning This function executes on a background thread to avoid blocking any audio operations. If operations should be performed on any other thread (like the main thread) it should be performed within a dispatch block like so: dispatch_async(dispatch_get_main_queue(), ^{ ...Your Code... }) */ -(void) microphone:(EZMicrophone*)microphone hasBufferList:(AudioBufferList*)bufferList withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels; @end #pragma mark - EZMicrophone /** The EZMicrophone provides a component to get audio data from the default device microphone. On OSX this is the default selected input device in the system preferences while on iOS this defaults to use the default RemoteIO audio unit. The microphone data is converted to a float buffer array and returned back to the caller via the EZMicrophoneDelegate protocol. */ @interface EZMicrophone : NSObject /** The EZMicrophoneDelegate for which to handle the microphone callbacks */ @property (nonatomic,assign) id microphoneDelegate; /** A bool describing whether the microphone is on and passing back audio data to its delegate. */ @property (nonatomic,assign) BOOL microphoneOn; #pragma mark - Initializers ///----------------------------------------------------------- /// @name Initializers ///----------------------------------------------------------- /** Creates an instance of the EZMicrophone with a delegate to respond to the audioReceived callback. This will not start fetching the audio until startFetchingAudio has been called. Use initWithMicrophoneDelegate:startsImmediately: to instantiate this class and immediately start fetching audio data. @param microphoneDelegate A EZMicrophoneDelegate delegate that will receive the audioReceived callback. @return An instance of the EZMicrophone class. This should be strongly retained. */ -(EZMicrophone*)initWithMicrophoneDelegate:(id)microphoneDelegate; /** Creates an instance of the EZMicrophone with a custom AudioStreamBasicDescription and provides the caller to specify a delegate to respond to the audioReceived callback. This will not start fetching the audio until startFetchingAudio has been called. Use initWithMicrophoneDelegate:startsImmediately: to instantiate this class and immediately start fetching audio data. @param microphoneDelegate A EZMicrophoneDelegate delegate that will receive the audioReceived callback. @param audioStreamBasicDescription A custom AudioStreamBasicFormat for the microphone input. @return An instance of the EZMicrophone class. This should be strongly retained. */ -(EZMicrophone*)initWithMicrophoneDelegate:(id)microphoneDelegate withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription; /** Creates an instance of the EZMicrophone with a delegate to respond to the audioReceived callback and allows the caller to specify whether they'd immediately like to start fetching the audio data. @param microphoneDelegate A EZMicrophoneDelegate delegate that will receive the audioReceived callback. @param startsImmediately A boolean indicating whether to start fetching the data immediately. IF YES, the delegate's audioReceived callback will immediately start getting called. @return An instance of the EZMicrophone class. This should be strongly retained. */ -(EZMicrophone*)initWithMicrophoneDelegate:(id)microphoneDelegate startsImmediately:(BOOL)startsImmediately; /** Creates an instance of the EZMicrophone with a custom AudioStreamBasicDescription and provides the caller with a delegate to respond to the audioReceived callback and allows the caller to specify whether they'd immediately like to start fetching the audio data. @param microphoneDelegate A EZMicrophoneDelegate delegate that will receive the audioReceived callback. @param audioStreamBasicDescription A custom AudioStreamBasicFormat for the microphone input. @param startsImmediately A boolean indicating whether to start fetching the data immediately. IF YES, the delegate's audioReceived callback will immediately start getting called. @return An instance of the EZMicrophone class. This should be strongly retained. */ -(EZMicrophone*)initWithMicrophoneDelegate:(id)microphoneDelegate withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription startsImmediately:(BOOL)startsImmediately; #pragma mark - Class Initializers ///----------------------------------------------------------- /// @name Class Initializers ///----------------------------------------------------------- /** Creates an instance of the EZMicrophone with a delegate to respond to the audioReceived callback. This will not start fetching the audio until startFetchingAudio has been called. Use microphoneWithDelegate:startsImmediately: to instantiate this class and immediately start fetching audio data. @param microphoneDelegate A EZMicrophoneDelegate delegate that will receive the audioReceived callback. @return An instance of the EZMicrophone class. This should be declared as a strong property! */ +(EZMicrophone*)microphoneWithDelegate:(id)microphoneDelegate; /** Creates an instance of the EZMicrophone with a delegate to respond to the audioReceived callback. This will not start fetching the audio until startFetchingAudio has been called. Use microphoneWithDelegate:startsImmediately: to instantiate this class and immediately start fetching audio data. @param microphoneDelegate A EZMicrophoneDelegate delegate that will receive the audioReceived callback. @param audioStreamBasicDescription A custom AudioStreamBasicFormat for the microphone input. @return An instance of the EZMicrophone class. This should be declared as a strong property! */ +(EZMicrophone*)microphoneWithDelegate:(id)microphoneDelegate withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription; /** Creates an instance of the EZMicrophone with a delegate to respond to the audioReceived callback and allows the caller to specify whether they'd immediately like to start fetching the audio data. @param microphoneDelegate A EZMicrophoneDelegate delegate that will receive the audioReceived callback. @param startsImmediately A boolean indicating whether to start fetching the data immediately. IF YES, the delegate's audioReceived callback will immediately start getting called. @return An instance of the EZMicrophone class. This should be strongly retained. */ +(EZMicrophone*)microphoneWithDelegate:(id)microphoneDelegate startsImmediately:(BOOL)startsImmediately; /** Creates an instance of the EZMicrophone with a delegate to respond to the audioReceived callback and allows the caller to specify whether they'd immediately like to start fetching the audio data. @param microphoneDelegate A EZMicrophoneDelegate delegate that will receive the audioReceived callback. @param audioStreamBasicDescription A custom AudioStreamBasicFormat for the microphone input. @param startsImmediately A boolean indicating whether to start fetching the data immediately. IF YES, the delegate's audioReceived callback will immediately start getting called. @return An instance of the EZMicrophone class. This should be strongly retained. */ +(EZMicrophone*)microphoneWithDelegate:(id)microphoneDelegate withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription startsImmediately:(BOOL)startsImmediately; #pragma mark - Singleton ///----------------------------------------------------------- /// @name Shared Instance ///----------------------------------------------------------- /** A shared instance of the microphone component. Most applications will only need to use one instance of the microphone component across multiple views. Make sure to call the `startFetchingAudio` method to receive the audio data in the microphone delegate. @return A shared instance of the `EZAudioMicrophone` component. */ +(EZMicrophone*)sharedMicrophone; #pragma mark - Events ///----------------------------------------------------------- /// @name Starting/Stopping The Microphone ///----------------------------------------------------------- /** Starts fetching audio from the default microphone. Will notify delegate with audioReceived callback. */ -(void)startFetchingAudio; /** Stops fetching audio. Will stop notifying the delegate's audioReceived callback. */ -(void)stopFetchingAudio; #pragma mark - Getters ///----------------------------------------------------------- /// @name Getting The Microphone's Audio Format ///----------------------------------------------------------- /** Provides the AudioStreamBasicDescription structure containing the format of the microphone's audio. @return An AudioStreamBasicDescription structure describing the format of the microphone's audio. */ -(AudioStreamBasicDescription)audioStreamBasicDescription; #pragma mark - Setters ///----------------------------------------------------------- /// @name Customizing The Microphone Input Format ///----------------------------------------------------------- /** Sets the AudioStreamBasicDescription on the microphone input. @warning Do not set this while fetching audio (startFetchingAudio) @param asbd The new AudioStreamBasicDescription to use in place of the current audio format description. */ -(void)setAudioStreamBasicDescription:(AudioStreamBasicDescription)asbd; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZMicrophone.m ================================================ // // EZMicrophone.m // EZAudio // // Created by Syed Haris Ali on 9/2/13. // Copyright (c) 2013 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 "EZMicrophone.h" #ifndef MAC_OS_X_VERSION_10_7 // CoreServices defines eofErr, replaced in 10.7 by kAudioFileEndOfFileError #include #endif #import "EZAudio.h" /// Buses static const AudioUnitScope kEZAudioMicrophoneInputBus = 1; static const AudioUnitScope kEZAudioMicrophoneOutputBus = 0; /// Flags #if TARGET_OS_IPHONE static const UInt32 kEZAudioMicrophoneDisableFlag = 1; #elif TARGET_OS_MAC static const UInt32 kEZAudioMicrophoneDisableFlag = 0; #endif static const UInt32 kEZAudioMicrophoneEnableFlag = 1; @interface EZMicrophone (){ /// Internal BOOL _customASBD; BOOL _isConfigured; BOOL _isFetching; /// Stream Description AEFloatConverter *converter; AudioStreamBasicDescription streamFormat; /// Audio Graph and Input/Output Units AudioUnit microphoneInput; /// Audio Buffers float **floatBuffers; AudioBufferList *microphoneInputBuffer; /// Device Parameters Float64 _deviceSampleRate; Float32 _deviceBufferDuration; UInt32 _deviceBufferFrameSize; #if TARGET_OS_IPHONE #elif TARGET_OS_MAC Float64 inputScopeSampleRate; #endif } @end @implementation EZMicrophone @synthesize microphoneDelegate = _microphoneDelegate; @synthesize microphoneOn = _microphoneOn; #pragma mark - Callbacks static OSStatus inputCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData ) { EZMicrophone *microphone = (__bridge EZMicrophone*)inRefCon; OSStatus result = noErr; // Render audio into buffer result = AudioUnitRender(microphone->microphoneInput, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, microphone->microphoneInputBuffer); if( !result ){ // ----- Notify delegate (OF-style) ----- // Audio Received (float array) if( microphone.microphoneDelegate ){ // THIS IS NOT OCCURING ON THE MAIN THREAD if( [microphone.microphoneDelegate respondsToSelector:@selector(microphone:hasAudioReceived:withBufferSize:withNumberOfChannels:)] ){ AEFloatConverterToFloat(microphone->converter, microphone->microphoneInputBuffer, microphone->floatBuffers, inNumberFrames); [microphone.microphoneDelegate microphone:microphone hasAudioReceived:microphone->floatBuffers withBufferSize:inNumberFrames withNumberOfChannels:microphone->streamFormat.mChannelsPerFrame]; } } // Audio Received (buffer list) if( microphone.microphoneDelegate ){ if( [microphone.microphoneDelegate respondsToSelector:@selector(microphone:hasBufferList:withBufferSize:withNumberOfChannels:)] ){ [microphone.microphoneDelegate microphone:microphone hasBufferList:microphone->microphoneInputBuffer withBufferSize:inNumberFrames withNumberOfChannels:microphone->streamFormat.mChannelsPerFrame]; } } } return result; } #pragma mark - Initialization -(id)init { self = [super init]; if(self){ // Clear the float buffer floatBuffers = NULL; // We're not fetching anything yet _isConfigured = NO; _isFetching = NO; if( !_isConfigured ){ // Create the input audio graph [self _createInputUnit]; // We're configured meow _isConfigured = YES; } } return self; } -(EZMicrophone *)initWithMicrophoneDelegate:(id)microphoneDelegate { self = [super init]; if(self){ self.microphoneDelegate = microphoneDelegate; // Clear the float buffer floatBuffers = NULL; // We're not fetching anything yet _isConfigured = NO; _isFetching = NO; if( !_isConfigured ){ // Create the input audio graph [self _createInputUnit]; // We're configured meow _isConfigured = YES; } } return self; } -(EZMicrophone *)initWithMicrophoneDelegate:(id)microphoneDelegate withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription { self = [self initWithMicrophoneDelegate:microphoneDelegate]; if(self){ _customASBD = YES; streamFormat = audioStreamBasicDescription; } return self; } -(EZMicrophone *)initWithMicrophoneDelegate:(id)microphoneDelegate startsImmediately:(BOOL)startsImmediately { self = [self initWithMicrophoneDelegate:microphoneDelegate]; if(self){ startsImmediately ? [self startFetchingAudio] : -1; } return self; } -(EZMicrophone *)initWithMicrophoneDelegate:(id)microphoneDelegate withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription startsImmediately:(BOOL)startsImmediately { self = [self initWithMicrophoneDelegate:microphoneDelegate withAudioStreamBasicDescription:audioStreamBasicDescription]; if(self){ startsImmediately ? [self startFetchingAudio] : -1; } return self; } #pragma mark - Class Initializers +(EZMicrophone *)microphoneWithDelegate:(id)microphoneDelegate { return [[EZMicrophone alloc] initWithMicrophoneDelegate:microphoneDelegate]; } +(EZMicrophone *)microphoneWithDelegate:(id)microphoneDelegate withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription { return [[EZMicrophone alloc] initWithMicrophoneDelegate:microphoneDelegate withAudioStreamBasicDescription:audioStreamBasicDescription]; } +(EZMicrophone *)microphoneWithDelegate:(id)microphoneDelegate startsImmediately:(BOOL)startsImmediately { return [[EZMicrophone alloc] initWithMicrophoneDelegate:microphoneDelegate startsImmediately:startsImmediately]; } +(EZMicrophone *)microphoneWithDelegate:(id)microphoneDelegate withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription startsImmediately:(BOOL)startsImmediately { return [[EZMicrophone alloc] initWithMicrophoneDelegate:microphoneDelegate withAudioStreamBasicDescription:audioStreamBasicDescription startsImmediately:startsImmediately]; } #pragma mark - Singleton +(EZMicrophone*)sharedMicrophone { static EZMicrophone *_sharedMicrophone = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedMicrophone = [[EZMicrophone alloc] init]; }); return _sharedMicrophone; } #pragma mark - Events -(void)startFetchingAudio { if( !_isFetching ){ // Start fetching input [EZAudio checkResult:AudioOutputUnitStart(self->microphoneInput) operation:"Microphone failed to start fetching audio"]; _isFetching = YES; self.microphoneOn = YES; } } -(void)stopFetchingAudio { // Stop fetching input data if( _isConfigured ){ if( _isFetching ){ [EZAudio checkResult:AudioOutputUnitStop(self->microphoneInput) operation:"Microphone failed to stop fetching audio"]; _isFetching = NO; self.microphoneOn = NO; } } } #pragma mark - Getters -(AudioStreamBasicDescription)audioStreamBasicDescription { return streamFormat; } #pragma mark - Setter -(void)setMicrophoneOn:(BOOL)microphoneOn { _microphoneOn = microphoneOn; if( microphoneOn ){ [self startFetchingAudio]; } else { [self stopFetchingAudio]; } } -(void)setAudioStreamBasicDescription:(AudioStreamBasicDescription)asbd { if( self.microphoneOn ){ NSAssert(self.microphoneOn,@"Cannot set the AudioStreamBasicDescription while microphone is fetching audio"); } else { _customASBD = YES; streamFormat = asbd; [self _configureStreamFormatWithSampleRate:_deviceSampleRate]; } } #pragma mark - Configure The Input Unit -(void)_createInputUnit { // Get component description for input AudioComponentDescription inputComponentDescription = [self _getInputAudioComponentDescription]; // Get the input component AudioComponent inputComponent = [self _getInputComponentWithAudioComponentDescription:inputComponentDescription]; // Create a new instance of the component and store it for internal use [self _createNewInstanceForInputComponent:inputComponent]; // Enable Input Scope [self _enableInputScope]; // Disable Output Scope [self _disableOutputScope]; // Get the default device if we need to (OSX only, iOS uses RemoteIO) #if TARGET_OS_IPHONE // Do nothing (using RemoteIO) #elif TARGET_OS_MAC [self _configureDefaultDevice]; #endif // Configure device and pull hardware specific sampling rate (default = 44.1 kHz) _deviceSampleRate = [self _configureDeviceSampleRateWithDefault:44100.0]; // Configure device and pull hardware specific buffer duration (default = 0.0232) _deviceBufferDuration = [self _configureDeviceBufferDurationWithDefault:0.0232]; // Configure the stream format with the hardware sample rate [self _configureStreamFormatWithSampleRate:_deviceSampleRate]; // Notify delegate the audio stream basic description was successfully created [self _notifyDelegateOfStreamFormat]; // Get buffer frame size _deviceBufferFrameSize = [self _getBufferFrameSize]; // Create the audio buffer list and pre-malloc the buffers in the list [self _configureAudioBufferListWithFrameSize:_deviceBufferFrameSize]; // Set the float converter's stream format [self _configureFloatConverterWithFrameSize:_deviceBufferFrameSize]; // Setup input callback [self _configureInputCallback]; // Disable buffer allocation (optional - do this if we want to pass in our own) [self _disableCallbackBufferAllocation]; // Initialize the audio unit [EZAudio checkResult:AudioUnitInitialize( microphoneInput ) operation:"Couldn't initialize the input unit"]; } #pragma mark - Audio Component Initialization -(AudioComponentDescription)_getInputAudioComponentDescription { // Create an input component description for mic input AudioComponentDescription inputComponentDescription; inputComponentDescription.componentType = kAudioUnitType_Output; inputComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple; inputComponentDescription.componentFlags = 0; inputComponentDescription.componentFlagsMask = 0; #if TARGET_OS_IPHONE inputComponentDescription.componentSubType = kAudioUnitSubType_RemoteIO; #elif TARGET_OS_MAC inputComponentDescription.componentSubType = kAudioUnitSubType_HALOutput; #endif // Return the successfully created input component description return inputComponentDescription; } -(AudioComponent)_getInputComponentWithAudioComponentDescription:(AudioComponentDescription)audioComponentDescription { // Try and find the component AudioComponent inputComponent = AudioComponentFindNext( NULL , &audioComponentDescription ); NSAssert(inputComponent,@"Couldn't get input component unit!"); return inputComponent; } -(void)_createNewInstanceForInputComponent:(AudioComponent)audioComponent { [EZAudio checkResult:AudioComponentInstanceNew(audioComponent, µphoneInput ) operation:"Couldn't open component for microphone input unit."]; } #pragma mark - Input/Output Scope Initialization -(void)_disableOutputScope { [EZAudio checkResult:AudioUnitSetProperty(microphoneInput, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, kEZAudioMicrophoneOutputBus, &kEZAudioMicrophoneDisableFlag, sizeof(kEZAudioMicrophoneDisableFlag)) operation:"Couldn't disable output on I/O unit."]; } -(void)_enableInputScope { [EZAudio checkResult:AudioUnitSetProperty(microphoneInput, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, kEZAudioMicrophoneInputBus, &kEZAudioMicrophoneEnableFlag, sizeof(kEZAudioMicrophoneEnableFlag)) operation:"Couldn't enable input on I/O unit."]; } #pragma mark - Pull Default Device (OSX) #if TARGET_OS_IPHONE // Not needed, using RemoteIO #elif TARGET_OS_MAC -(void)_configureDefaultDevice { // Get the default audio input device (pulls an abstract type from system preferences) AudioDeviceID defaultDevice = kAudioObjectUnknown; UInt32 propSize = sizeof(defaultDevice); AudioObjectPropertyAddress defaultDeviceProperty; defaultDeviceProperty.mSelector = kAudioHardwarePropertyDefaultInputDevice; defaultDeviceProperty.mScope = kAudioObjectPropertyScopeGlobal; defaultDeviceProperty.mElement = kAudioObjectPropertyElementMaster; [EZAudio checkResult:AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultDeviceProperty, 0, NULL, &propSize, &defaultDevice) operation:"Couldn't get default input device"]; // Set the default device on the microphone input unit propSize = sizeof(defaultDevice); [EZAudio checkResult:AudioUnitSetProperty(microphoneInput, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, kEZAudioMicrophoneOutputBus, &defaultDevice, propSize) operation:"Couldn't set default device on I/O unit"]; // Get the stream format description from the newly created input unit and assign it to the output of the input unit AudioStreamBasicDescription inputScopeFormat; propSize = sizeof(AudioStreamBasicDescription); [EZAudio checkResult:AudioUnitGetProperty(microphoneInput, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kEZAudioMicrophoneInputBus, &inputScopeFormat, &propSize) operation:"Couldn't get ASBD from input unit (1)"]; // Assign the same stream format description from the output of the input unit and pull the sample rate AudioStreamBasicDescription outputScopeFormat; propSize = sizeof(AudioStreamBasicDescription); [EZAudio checkResult:AudioUnitGetProperty(microphoneInput, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kEZAudioMicrophoneInputBus, &outputScopeFormat, &propSize) operation:"Couldn't get ASBD from input unit (2)"]; // Store the input scope's sample rate inputScopeSampleRate = inputScopeFormat.mSampleRate; } #endif #pragma mark - Pull Sample Rate -(Float64)_configureDeviceSampleRateWithDefault:(float)defaultSampleRate { Float64 hardwareSampleRate = defaultSampleRate; #if TARGET_OS_IPHONE // Use approximations for simulator and pull from real device if connected #if !(TARGET_IPHONE_SIMULATOR) // Sample Rate hardwareSampleRate = [[AVAudioSession sharedInstance] sampleRate]; #endif #elif TARGET_OS_MAC hardwareSampleRate = inputScopeSampleRate; #endif return hardwareSampleRate; } #pragma mark - Pull Buffer Duration -(Float32)_configureDeviceBufferDurationWithDefault:(float)defaultBufferDuration { Float32 bufferDuration = defaultBufferDuration; // Type 1/43 by default #if TARGET_OS_IPHONE // Use approximations for simulator and pull from real device if connected #if !(TARGET_IPHONE_SIMULATOR) NSError *err; [[AVAudioSession sharedInstance] setPreferredIOBufferDuration:bufferDuration error:&err]; if (err) { NSLog(@"Error setting preferredIOBufferDuration for audio session: %@", err.localizedDescription); } // Buffer Size bufferDuration = [[AVAudioSession sharedInstance] IOBufferDuration]; #endif #elif TARGET_OS_MAC #endif return bufferDuration; } #pragma mark - Pull Buffer Frame Size -(UInt32)_getBufferFrameSize { UInt32 bufferFrameSize; UInt32 propSize = sizeof(bufferFrameSize); [EZAudio checkResult:AudioUnitGetProperty(microphoneInput, #if TARGET_OS_IPHONE kAudioUnitProperty_MaximumFramesPerSlice, #elif TARGET_OS_MAC kAudioDevicePropertyBufferFrameSize, #endif kAudioUnitScope_Global, kEZAudioMicrophoneOutputBus, &bufferFrameSize, &propSize) operation:"Failed to get buffer frame size"]; return bufferFrameSize; } #pragma mark - Stream Format Initialization -(void)_configureStreamFormatWithSampleRate:(Float64)sampleRate { // Set the stream format if( !_customASBD ){ streamFormat = [EZAudio stereoCanonicalNonInterleavedFormatWithSampleRate:sampleRate]; } else { streamFormat.mSampleRate = sampleRate; } UInt32 propSize = sizeof(streamFormat); // Set the stream format for output on the microphone's input scope [EZAudio checkResult:AudioUnitSetProperty(microphoneInput, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kEZAudioMicrophoneOutputBus, &streamFormat, propSize) operation:"Could not set microphone's stream format bus 0"]; // Set the stream format for the input on the microphone's output scope [EZAudio checkResult:AudioUnitSetProperty(microphoneInput, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kEZAudioMicrophoneInputBus, &streamFormat, propSize) operation:"Could not set microphone's stream format bus 1"]; } -(void)_notifyDelegateOfStreamFormat { if( _microphoneDelegate ){ if( [_microphoneDelegate respondsToSelector:@selector(microphone:hasAudioStreamBasicDescription:) ] ){ [_microphoneDelegate microphone:self hasAudioStreamBasicDescription:streamFormat]; } } } #pragma mark - AudioBufferList Initialization -(void)_configureAudioBufferListWithFrameSize:(UInt32)bufferFrameSize { UInt32 bufferSizeBytes = bufferFrameSize * streamFormat.mBytesPerFrame; UInt32 propSize = offsetof( AudioBufferList, mBuffers[0] ) + ( sizeof( AudioBuffer ) *streamFormat.mChannelsPerFrame ); microphoneInputBuffer = (AudioBufferList*)malloc(propSize); microphoneInputBuffer->mNumberBuffers = streamFormat.mChannelsPerFrame; for( UInt32 i = 0; i < microphoneInputBuffer->mNumberBuffers; i++ ){ microphoneInputBuffer->mBuffers[i].mNumberChannels = streamFormat.mChannelsPerFrame; microphoneInputBuffer->mBuffers[i].mDataByteSize = bufferSizeBytes; microphoneInputBuffer->mBuffers[i].mData = malloc(bufferSizeBytes); } } #pragma mark - Float Converter Initialization -(void)_configureFloatConverterWithFrameSize:(UInt32)bufferFrameSize { UInt32 bufferSizeBytes = bufferFrameSize * streamFormat.mBytesPerFrame; converter = [[AEFloatConverter alloc] initWithSourceFormat:streamFormat]; floatBuffers = (float**)malloc(sizeof(float*)*streamFormat.mChannelsPerFrame); assert(floatBuffers); for ( int i=0; i #import #if TARGET_OS_IPHONE #elif TARGET_OS_MAC #import #endif #import "TPCircularBuffer.h" @class EZOutput; /** The EZOutputDataSource (required for the EZOutput) specifies a receiver to provide audio data when the EZOutput is started. Only ONE datasource method is expected to be implemented and priority is given as such: 1.) `output:callbackWithActionFlags:inTimeStamp:inBusNumber:inNumberFrames:ioData:` 2.) `outputShouldUseCircularBuffer:` 3.) `output:needsBufferListWithFrames:withBufferSize:` */ @protocol EZOutputDataSource @optional ///----------------------------------------------------------- /// @name Pulling The Audio Data ///----------------------------------------------------------- /** Provides complete override of the output callback function. The delegate is expected to @param output The instance of the EZOutput that asked for the data @param ioActionFlags AudioUnitRenderActionFlags provided by the output callback @param inTimeStamp AudioTimeStamp reference provided by the output callback @param inBusNumber UInt32 representing the bus number provided by the output callback @param inNumberFrames UInt32 representing the number of frames provided by the output callback @param ioData AudioBufferList pointer representing the audio data that will be used for output provided by the output callback (fill this!) */ -(void)output:(EZOutput*)output callbackWithActionFlags:(AudioUnitRenderActionFlags*)ioActionFlags inTimeStamp:(const AudioTimeStamp*)inTimeStamp inBusNumber:(UInt32)inBusNumber inNumberFrames:(UInt32)inNumberFrames ioData:(AudioBufferList*)ioData; /** Provides output using a circular @param output The instance of the EZOutput that asked for the data @return The EZOutputDataSource's TPCircularBuffer structure holding the audio data in a circular buffer */ -(TPCircularBuffer*)outputShouldUseCircularBuffer:(EZOutput *)output; /** Provides a way to provide output with data anytime the EZOutput needs audio data to play. This function provides an already allocated AudioBufferList to use for providing audio data into the output buffer. @param output The instance of the EZOutput that asked for the data. @param audioBufferList The AudioBufferList structure pointer that needs to be filled with audio data @param frames The amount of frames as a UInt32 that output will need to properly fill its output buffer. @return A pointer to the AudioBufferList structure holding the audio data. If nil or NULL, will output silence. */ -(void) output:(EZOutput *)output shouldFillAudioBufferList:(AudioBufferList*)audioBufferList withNumberOfFrames:(UInt32)frames; @end /** The EZOutput component provides a generic output to glue all the other EZAudio components together and push whatever sound you've created to the default output device (think opposite of the microphone). The EZOutputDataSource provides the required AudioBufferList needed to populate the output buffer. */ @interface EZOutput : NSObject #pragma mark - Properties /** The EZOutputDataSource that provides the required AudioBufferList to the output callback function */ @property (nonatomic,assign) idoutputDataSource; #pragma mark - Initializers ///----------------------------------------------------------- /// @name Initializers ///----------------------------------------------------------- /** Creates a new instance of the EZOutput and allows the caller to specify an EZOutputDataSource. @param dataSource The EZOutputDataSource that will be used to pull the audio data for the output callback. @return A newly created instance of the EZOutput class. */ -(id)initWithDataSource:(id)dataSource; /** Creates a new instance of the EZOutput and allows the caller to specify an EZOutputDataSource. @param dataSource The EZOutputDataSource that will be used to pull the audio data for the output callback. @param audioStreamBasicDescription The AudioStreamBasicDescription of the EZOutput. @warning AudioStreamBasicDescriptions that are invalid will cause the EZOutput to fail to initialize @return A newly created instance of the EZOutput class. */ -(id) initWithDataSource:(id)dataSource withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription; #pragma mark - Class Initializers ///----------------------------------------------------------- /// @name Class Initializers ///----------------------------------------------------------- /** Class method to create a new instance of the EZOutput and allows the caller to specify an EZOutputDataSource. @param dataSource The EZOutputDataSource that will be used to pull the audio data for the output callback. @return A newly created instance of the EZOutput class. */ +(EZOutput*)outputWithDataSource:(id)dataSource; /** Class method to create a new instance of the EZOutput and allows the caller to specify an EZOutputDataSource. @param dataSource The EZOutputDataSource that will be used to pull the audio data for the output callback. @param audioStreamBasicDescription The AudioStreamBasicDescription of the EZOutput. @warning AudioStreamBasicDescriptions that are invalid will cause the EZOutput to fail to initialize @return A newly created instance of the EZOutput class. */ +(EZOutput*)outputWithDataSource:(id)dataSource withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription; #pragma mark - Singleton ///----------------------------------------------------------- /// @name Shared Instance ///----------------------------------------------------------- /** Creates a shared instance of the EZOutput (one app will usually only need one output and share the role of the EZOutputDataSource). @return The shared instance of the EZOutput class. */ +(EZOutput*)sharedOutput; #pragma mark - Events ///----------------------------------------------------------- /// @name Starting/Stopping The Output ///----------------------------------------------------------- /** Starts pulling audio data from the EZOutputDataSource to the default device output. */ -(void)startPlayback; /** Stops pulling audio data from the EZOutputDataSource to the default device output. */ -(void)stopPlayback; #pragma mark - Getters ///----------------------------------------------------------- /// @name Getting The Output Audio Format ///----------------------------------------------------------- /** Provides the AudioStreamBasicDescription structure containing the format of the microphone's audio. @return An AudioStreamBasicDescription structure describing the format of the microphone's audio. */ -(AudioStreamBasicDescription)audioStreamBasicDescription; ///----------------------------------------------------------- /// @name Getting The State Of The Output ///----------------------------------------------------------- /** Provides a flag indicating whether the EZOutput is pulling audio data from the EZOutputDataSource for playback. @return YES if the EZOutput is pulling audio data to the output device, NO if it is stopped */ -(BOOL)isPlaying; #pragma mark - Setters ///----------------------------------------------------------- /// @name Customizing The Output Format ///----------------------------------------------------------- /** Sets the AudioStreamBasicDescription on the output. @warning Do not set this during playback. @param asbd The new AudioStreamBasicDescription to use in place of the current audio format description. */ -(void)setAudioStreamBasicDescription:(AudioStreamBasicDescription)asbd; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZOutput.m ================================================ // // EZOutput.m // EZAudio // // Created by Syed Haris Ali on 12/2/13. // Copyright (c) 2013 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 "EZOutput.h" #import "EZAudio.h" @interface EZOutput (){ BOOL _customASBD; BOOL _isPlaying; AudioStreamBasicDescription _outputASBD; AudioUnit _outputUnit; } @end @implementation EZOutput @synthesize outputDataSource = _outputDataSource; static OSStatus OutputRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData){ // NSLog(@"output something"); EZOutput *output = (__bridge EZOutput*)inRefCon; // Manual override if( [output.outputDataSource respondsToSelector:@selector(output:callbackWithActionFlags:inTimeStamp:inBusNumber:inNumberFrames:ioData:)] ){ [output.outputDataSource output:output callbackWithActionFlags:ioActionFlags inTimeStamp:inTimeStamp inBusNumber:inBusNumber inNumberFrames:inNumberFrames ioData:ioData]; } else if( [output.outputDataSource respondsToSelector:@selector(outputShouldUseCircularBuffer:)] ){ TPCircularBuffer *circularBuffer = [output.outputDataSource outputShouldUseCircularBuffer:output]; if( !circularBuffer ){ float *left = (float*)ioData->mBuffers[0].mData; float *right = (float*)ioData->mBuffers[1].mData; for(int i = 0; i < inNumberFrames; i++ ){ left[ i ] = 0.0f; right[ i ] = 0.0f; } return noErr; }; /** Thank you Michael Tyson (A Tasty Pixel) for writing the TPCircularBuffer, you are amazing! */ // Get the desired amount of bytes to copy int32_t bytesToCopy = ioData->mBuffers[0].mDataByteSize; float *left = (float*)ioData->mBuffers[0].mData; float *right = (float*)ioData->mBuffers[1].mData; // Get the available bytes in the circular buffer int32_t availableBytes; float *buffer = TPCircularBufferTail(circularBuffer,&availableBytes); // Ideally we'd have all the bytes to be copied, but compare it against the available bytes (get min) int32_t amount = MIN(bytesToCopy,availableBytes); memcpy( left, buffer, amount ); memcpy( right, buffer, amount ); // Consume those bytes ( this will internally push the head of the circular buffer ) TPCircularBufferConsume(circularBuffer,amount); } // Provided an AudioBufferList (defaults to silence) else if( [output.outputDataSource respondsToSelector:@selector(output:shouldFillAudioBufferList:withNumberOfFrames:)] ) { [output.outputDataSource output:output shouldFillAudioBufferList:ioData withNumberOfFrames:inNumberFrames]; } return noErr; } #pragma mark - Initialization -(id)init { self = [super init]; if(self){ [self _configureOutput]; } return self; } -(id)initWithDataSource:(id)dataSource { self = [super init]; if(self){ self.outputDataSource = dataSource; [self _configureOutput]; } return self; } -(id) initWithDataSource:(id)dataSource withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription { self = [super init]; if(self){ _customASBD = YES; _outputASBD = audioStreamBasicDescription; self.outputDataSource = dataSource; [self _configureOutput]; } return self; } #pragma mark - Class Initializers +(EZOutput*)outputWithDataSource:(id)dataSource { return [[EZOutput alloc] initWithDataSource:dataSource]; } +(EZOutput *)outputWithDataSource:(id)dataSource withAudioStreamBasicDescription:(AudioStreamBasicDescription)audioStreamBasicDescription { return [[EZOutput alloc] initWithDataSource:dataSource withAudioStreamBasicDescription:audioStreamBasicDescription]; } #pragma mark - Singleton +(EZOutput*)sharedOutput { static EZOutput *_sharedOutput = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedOutput = [[EZOutput alloc] init]; }); return _sharedOutput; } #pragma mark - Audio Component Initialization -(AudioComponentDescription)_getOutputAudioComponentDescription { // Create an output component description for default output device AudioComponentDescription outputComponentDescription; outputComponentDescription.componentFlags = 0; outputComponentDescription.componentFlagsMask = 0; outputComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple; #if TARGET_OS_IPHONE outputComponentDescription.componentSubType = kAudioUnitSubType_RemoteIO; #elif TARGET_OS_MAC outputComponentDescription.componentSubType = kAudioUnitSubType_DefaultOutput; #endif outputComponentDescription.componentType = kAudioUnitType_Output; return outputComponentDescription; } -(AudioComponent)_getOutputComponentWithAudioComponentDescription:(AudioComponentDescription)outputComponentDescription { // Try and find the component AudioComponent outputComponent = AudioComponentFindNext( NULL , &outputComponentDescription ); NSAssert(outputComponent,@"Couldn't get input component unit!"); return outputComponent; } -(void)_createNewInstanceForOutputComponent:(AudioComponent)outputComponent { // [EZAudio checkResult:AudioComponentInstanceNew( outputComponent, &_outputUnit ) operation:"Failed to open component for output unit"]; } #pragma mark - Configure The Output Unit //-(void)_configureOutput { // // // Get component description for output // AudioComponentDescription outputComponentDescription = [self _getOutputAudioComponentDescription]; // // // Get the output component // AudioComponent outputComponent = [self _getOutputComponentWithAudioComponentDescription:outputComponentDescription]; // // // Create a new instance of the component and store it for internal use // [self _createNewInstanceForOutputComponent:outputComponent]; // //} #if TARGET_OS_IPHONE -(void)_configureOutput { // AudioComponentDescription outputcd; outputcd.componentFlags = 0; outputcd.componentFlagsMask = 0; outputcd.componentManufacturer = kAudioUnitManufacturer_Apple; outputcd.componentSubType = kAudioUnitSubType_RemoteIO; outputcd.componentType = kAudioUnitType_Output; // AudioComponent comp = AudioComponentFindNext(NULL,&outputcd); [EZAudio checkResult:AudioComponentInstanceNew(comp,&_outputUnit) operation:"Failed to get output unit"]; // Setup the output unit for playback UInt32 oneFlag = 1; AudioUnitElement bus0 = 0; [EZAudio checkResult:AudioUnitSetProperty(_outputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, bus0, &oneFlag, sizeof(oneFlag)) operation:"Failed to enable output unit"]; // Get the hardware sample rate Float64 hardwareSampleRate = 44100; #if !(TARGET_IPHONE_SIMULATOR) hardwareSampleRate = [[AVAudioSession sharedInstance] sampleRate]; #endif // Setup an ASBD in canonical format by default if( !_customASBD ){ _outputASBD = [EZAudio stereoCanonicalNonInterleavedFormatWithSampleRate:hardwareSampleRate]; } // Set the format for output [EZAudio checkResult:AudioUnitSetProperty(_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, bus0, &_outputASBD, sizeof(_outputASBD)) operation:"Couldn't set the ASBD for input scope/bos 0"]; // AURenderCallbackStruct input; input.inputProc = OutputRenderCallback; input.inputProcRefCon = (__bridge void *)self; [EZAudio checkResult:AudioUnitSetProperty(_outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, bus0, &input, sizeof(input)) operation:"Failed to set the render callback on the output unit"]; // [EZAudio checkResult:AudioUnitInitialize(_outputUnit) operation:"Couldn't initialize output unit"]; } #elif TARGET_OS_MAC -(void)_configureOutput { // AudioComponentDescription outputcd; outputcd.componentType = kAudioUnitType_Output; outputcd.componentSubType = kAudioUnitSubType_DefaultOutput; outputcd.componentManufacturer = kAudioUnitManufacturer_Apple; // AudioComponent comp = AudioComponentFindNext(NULL,&outputcd); if( comp == NULL ){ NSLog(@"Failed to get output unit"); exit(-1); } [EZAudio checkResult:AudioComponentInstanceNew(comp,&_outputUnit) operation:"Failed to open component for output unit"]; // Setup an ASBD in canonical format by default if( !_customASBD ){ _outputASBD = [EZAudio stereoCanonicalNonInterleavedFormatWithSampleRate:44100]; } // Set the format for output [EZAudio checkResult:AudioUnitSetProperty(_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &_outputASBD, sizeof(_outputASBD)) operation:"Couldn't set the ASBD for input scope/bos 0"]; // AURenderCallbackStruct input; input.inputProc = OutputRenderCallback; input.inputProcRefCon = (__bridge void *)(self); [EZAudio checkResult:AudioUnitSetProperty(_outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, sizeof(input)) operation:"Failed to set the render callback on the output unit"]; // [EZAudio checkResult:AudioUnitInitialize(_outputUnit) operation:"Couldn't initialize output unit"]; } #endif #pragma mark - Events -(void)startPlayback { if( !_isPlaying ){ [EZAudio checkResult:AudioOutputUnitStart(_outputUnit) operation:"Failed to start output unit"]; _isPlaying = YES; } } -(void)stopPlayback { if( _isPlaying ){ [EZAudio checkResult:AudioOutputUnitStop(_outputUnit) operation:"Failed to stop output unit"]; _isPlaying = NO; } } #pragma mark - Getters -(AudioStreamBasicDescription)audioStreamBasicDescription { return _outputASBD; } -(BOOL)isPlaying { return _isPlaying; } #pragma mark - Setters -(void)setAudioStreamBasicDescription:(AudioStreamBasicDescription)asbd { BOOL wasPlaying = NO; if( self.isPlaying ){ [self stopPlayback]; wasPlaying = YES; } _customASBD = YES; _outputASBD = asbd; // Set the format for output [EZAudio checkResult:AudioUnitSetProperty(_outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &_outputASBD, sizeof(_outputASBD)) operation:"Couldn't set the ASBD for input scope/bos 0"]; if( wasPlaying ) { [self startPlayback]; } } -(void)dealloc { [EZAudio checkResult:AudioOutputUnitStop(_outputUnit) operation:"Failed to uninitialize output unit"]; [EZAudio checkResult:AudioUnitUninitialize(_outputUnit) operation:"Failed to uninitialize output unit"]; [EZAudio checkResult:AudioComponentInstanceDispose(_outputUnit) operation:"Failed to uninitialize output unit"]; } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZPlot.h ================================================ // // EZPlot.h // EZAudio // // Created by Syed Haris Ali on 11/24/13. // Copyright (c) 2013 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. #pragma mark - Enumerations ///----------------------------------------------------------- /// @name Plot Types ///----------------------------------------------------------- /** The types of plots that can be displayed in the view using the data. */ typedef NS_ENUM(NSInteger,EZPlotType){ /** Plot that displays only the samples of the current buffer */ EZPlotTypeBuffer, /** Plot that displays a rolling history of values using the RMS calculated for each incoming buffer */ EZPlotTypeRolling }; /** EZPlot is a cross-platform (iOS and OSX) class used to subclass the default view type (either UIView or NSView, respectively). ## Subclassing Notes This class isn't meant to be directly used in practice, but instead establishes the default properties and behaviors subclasses should obey to provide consistent behavior accross multiple types of graphs (i.e. set background color, plot type, should fill in, etc.). Subclasses should make use of the inherited properties from this class to allow all child plots to benefit from the same */ #if TARGET_OS_IPHONE #import @interface EZPlot : UIView #elif TARGET_OS_MAC #import @interface EZPlot : NSView #endif #pragma mark - Properties ///----------------------------------------------------------- /// @name Customizing The Plot's Appearance ///----------------------------------------------------------- /** The default background color of the plot. For iOS the color is specified as a UIColor while for OSX the color is an NSColor. The default value on both platforms is black. */ @property (nonatomic,strong) id backgroundColor; /** The default color of the plot's data (i.e. waveform, y-axis values). For iOS the color is specified as a UIColor while for OSX the color is an NSColor. The default value on both platforms is red. */ @property (nonatomic,strong) id color; /** The plot's gain value, which controls the scale of the y-axis values. The default value of the gain is 1.0f and should always be greater than 0.0f. */ @property (nonatomic,assign,setter=setGain:) float gain; /** The type of plot as specified by the `EZPlotType` enumeration (i.e. a buffer or rolling plot type). */ @property (nonatomic,assign,setter=setPlotType:) EZPlotType plotType; /** A boolean indicating whether or not to fill in the graph. A value of YES will make a filled graph (filling in the space between the x-axis and the y-value), while a value of NO will create a stroked graph (connecting the points along the y-axis). */ @property (nonatomic,assign,setter=setShouldFill:) BOOL shouldFill; /** A boolean indicating whether the graph should be rotated along the x-axis to give a mirrored reflection. This is typical for audio plots to produce the classic waveform look. A value of YES will produce a mirrored reflection of the y-values about the x-axis, while a value of NO will only plot the y-values. */ @property (nonatomic,assign,setter=setShouldMirror:) BOOL shouldMirror; #pragma mark - Clearing ///----------------------------------------------------------- /// @name Clearing The Plot ///----------------------------------------------------------- /** Clears all data from the audio plot (includes both EZPlotTypeBuffer and EZPlotTypeRolling) */ -(void)clear; #pragma mark - Get Samples ///----------------------------------------------------------- /// @name Updating The Plot ///----------------------------------------------------------- /** Updates the plot with the new buffer data and tells the view to redraw itself. Caller will provide a float array with the values they expect to see on the y-axis. The plot will internally handle mapping the x-axis and y-axis to the current view port, any interpolation for fills effects, and mirroring. @param buffer A float array of values to map to the y-axis. @param bufferSize The size of the float array that will be mapped to the y-axis. @warning The bufferSize is expected to be the same, constant value once initial triggered. For plots using OpenGL a vertex buffer object will be allocated with a maximum buffersize of (2 * the initial given buffer size) to account for any interpolation necessary for filling in the graph. Updates use the glBufferSubData(...) function, which will crash if the buffersize exceeds the initial maximum allocated size. */ -(void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZPlot.m ================================================ // // EZPlot.m // EZAudio // // Created by Syed Haris Ali on 11/24/13. // Copyright (c) 2013 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 "EZPlot.h" @interface EZPlot () @end @implementation EZPlot #pragma mark - Clearing -(void)clear { // Override in subclass } #pragma mark - Get Samples -(void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize { // Override in subclass } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZRecorder.h ================================================ // // EZRecorder.h // EZAudio // // Created by Syed Haris Ali on 12/1/13. // Copyright (c) 2013 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 #import /** To ensure valid recording formats are used when recording to a file the EZRecorderFileType describes the most common file types that a file can be encoded in. Each of these types can be used to output recordings as such: EZRecorderFileTypeAIFF - .aif, .aiff, .aifc, .aac EZRecorderFileTypeM4A - .m4a, .mp4 EZRecorderFileTypeWAV - .wav */ typedef NS_ENUM(NSInteger, EZRecorderFileType) { /** Recording format that describes AIFF file types. These are uncompressed, LPCM files that are completely lossless, but are large in file size. */ EZRecorderFileTypeAIFF, /** Recording format that describes M4A file types. These are compressed, but yield great results especially when file size is an issue. */ EZRecorderFileTypeM4A, /** Recording format that describes WAV file types. These are uncompressed, LPCM files that are completely lossless, but are large in file size. */ EZRecorderFileTypeWAV }; /** The EZRecorder provides a flexible way to create an audio file and append raw audio data to it. The EZRecorder will convert the incoming audio on the fly to the destination format so no conversion is needed between this and any other component. Right now the only supported output format is 'caf'. Each output file should have its own EZRecorder instance (think 1 EZRecorder = 1 audio file). */ @interface EZRecorder : NSObject #pragma mark - Initializers ///----------------------------------------------------------- /// @name Initializers ///----------------------------------------------------------- /** Creates a new instance of an EZRecorder using a destination file path URL and the source format of the incoming audio. @param url An NSURL specifying the file path location of where the audio file should be written to. @param sourceFormat The AudioStreamBasicDescription for the incoming audio that will be written to the file. @param destinationFileType A constant described by the EZRecorderFileType that corresponds to the type of destination file that should be written. For instance, an AAC file written using an '.m4a' extension would correspond to EZRecorderFileTypeM4A. See EZRecorderFileType for all the constants and mapping combinations. @return The newly created EZRecorder instance. */ -(EZRecorder*)initWithDestinationURL:(NSURL*)url sourceFormat:(AudioStreamBasicDescription)sourceFormat destinationFileType:(EZRecorderFileType)destinationFileType; #pragma mark - Class Initializers ///----------------------------------------------------------- /// @name Class Initializers ///----------------------------------------------------------- /** Class method to create a new instance of an EZRecorder using a destination file path URL and the source format of the incoming audio. @param url An NSURL specifying the file path location of where the audio file should be written to. @param sourceFormat The AudioStreamBasicDescription for the incoming audio that will be written to the file. @param destinationFileType A constant described by the EZRecorderFileType that corresponds to the type of destination file that should be written. For instance, an AAC file written using an '.m4a' extension would correspond to EZRecorderFileTypeM4A. See EZRecorderFileType for all the constants and mapping combinations. @return The newly created EZRecorder instance. */ +(EZRecorder*)recorderWithDestinationURL:(NSURL*)url sourceFormat:(AudioStreamBasicDescription)sourceFormat destinationFileType:(EZRecorderFileType)destinationFileType; #pragma mark - Getters ///----------------------------------------------------------- /// @name Getting The Recorder's Properties ///----------------------------------------------------------- /** Provides the file path that's currently being used by the recorder. @return The NSURL representing the file path of the audio file path being used for recording. */ -(NSURL*)url; #pragma mark - Events ///----------------------------------------------------------- /// @name Appending Data To The Audio File ///----------------------------------------------------------- /** Appends audio data to the tail of the output file from an AudioBufferList. @param bufferList The AudioBufferList holding the audio data to append @param bufferSize The size of each of the buffers in the buffer list. */ -(void)appendDataFromBufferList:(AudioBufferList*)bufferList withBufferSize:(UInt32)bufferSize; ///----------------------------------------------------------- /// @name Closing The Audio File ///----------------------------------------------------------- /** Finishes writes to the audio file and closes it. */ -(void)closeAudioFile; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/EZRecorder.m ================================================ // // EZRecorder.m // EZAudio // // Created by Syed Haris Ali on 12/1/13. // Copyright (c) 2013 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 "EZRecorder.h" #import "EZAudio.h" @interface EZRecorder (){ ExtAudioFileRef _destinationFile; AudioFileTypeID _destinationFileTypeID; CFURLRef _destinationFileURL; AudioStreamBasicDescription _destinationFormat; AudioStreamBasicDescription _sourceFormat; } @end @implementation EZRecorder #pragma mark - Initializers -(EZRecorder*)initWithDestinationURL:(NSURL*)url sourceFormat:(AudioStreamBasicDescription)sourceFormat destinationFileType:(EZRecorderFileType)destinationFileType { self = [super init]; if( self ) { // Set defaults _destinationFile = NULL; _destinationFileURL = (__bridge CFURLRef)url; _sourceFormat = sourceFormat; _destinationFormat = [EZRecorder recorderFormatForFileType:destinationFileType withSourceFormat:_sourceFormat]; _destinationFileTypeID = [EZRecorder recorderFileTypeIdForFileType:destinationFileType withSourceFormat:_sourceFormat]; // Initializer the recorder instance [self _initializeRecorder]; } return self; } #pragma mark - Class Initializers +(EZRecorder*)recorderWithDestinationURL:(NSURL*)url sourceFormat:(AudioStreamBasicDescription)sourceFormat destinationFileType:(EZRecorderFileType)destinationFileType { return [[EZRecorder alloc] initWithDestinationURL:url sourceFormat:sourceFormat destinationFileType:destinationFileType]; } #pragma mark - Private Configuration +(AudioStreamBasicDescription)recorderFormatForFileType:(EZRecorderFileType)fileType withSourceFormat:(AudioStreamBasicDescription)sourceFormat { AudioStreamBasicDescription asbd; switch ( fileType ) { case EZRecorderFileTypeAIFF: asbd = [EZAudio AIFFFormatWithNumberOfChannels:sourceFormat.mChannelsPerFrame sampleRate:sourceFormat.mSampleRate]; break; case EZRecorderFileTypeM4A: asbd = [EZAudio M4AFormatWithNumberOfChannels:sourceFormat.mChannelsPerFrame sampleRate:sourceFormat.mSampleRate]; break; case EZRecorderFileTypeWAV: asbd = [EZAudio stereoFloatInterleavedFormatWithSampleRate:sourceFormat.mSampleRate]; break; default: asbd = [EZAudio stereoCanonicalNonInterleavedFormatWithSampleRate:sourceFormat.mSampleRate]; break; } return asbd; } +(AudioFileTypeID)recorderFileTypeIdForFileType:(EZRecorderFileType)fileType withSourceFormat:(AudioStreamBasicDescription)sourceFormat { AudioFileTypeID audioFileTypeID; switch ( fileType ) { case EZRecorderFileTypeAIFF: audioFileTypeID = kAudioFileAIFFType; break; case EZRecorderFileTypeM4A: audioFileTypeID = kAudioFileM4AType; break; case EZRecorderFileTypeWAV: audioFileTypeID = kAudioFileWAVEType; break; default: audioFileTypeID = kAudioFileWAVEType; break; } return audioFileTypeID; } -(void)_initializeRecorder { // Finish filling out the destination format description UInt32 propSize = sizeof(_destinationFormat); [EZAudio checkResult:AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &propSize, &_destinationFormat) operation:"Failed to fill out rest of destination format"]; // Create the audio file [EZAudio checkResult:ExtAudioFileCreateWithURL(_destinationFileURL, _destinationFileTypeID, &_destinationFormat, NULL, kAudioFileFlags_EraseFile, &_destinationFile) operation:"Failed to create audio file"]; // Set the client format (which should be equal to the source format) [EZAudio checkResult:ExtAudioFileSetProperty(_destinationFile, kExtAudioFileProperty_ClientDataFormat, sizeof(_sourceFormat), &_sourceFormat) operation:"Failed to set client format on recorded audio file"]; } #pragma mark - Events -(void)appendDataFromBufferList:(AudioBufferList *)bufferList withBufferSize:(UInt32)bufferSize { if( _destinationFile ) { [EZAudio checkResult:ExtAudioFileWriteAsync(_destinationFile, bufferSize, bufferList) operation:"Failed to write audio data to recorded audio file"]; } } -(void)closeAudioFile { if( _destinationFile ) { // Dispose of the audio file reference [EZAudio checkResult:ExtAudioFileDispose(_destinationFile) operation:"Failed to close audio file"]; // Null out the file reference _destinationFile = NULL; } } -(NSURL *)url { return (__bridge NSURL*)_destinationFileURL; } #pragma mark - Dealloc -(void)dealloc { [self closeAudioFile]; } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/TPCircularBuffer.c ================================================ // // TPCircularBuffer.c // Circular/Ring buffer implementation // // https://github.com/michaeltyson/TPCircularBuffer // // Created by Michael Tyson on 10/12/2011. // // Copyright (C) 2012-2013 A Tasty Pixel // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. // #include "TPCircularBuffer.h" #include #include #define reportResult(result,operation) (_reportResult((result),(operation),strrchr(__FILE__, '/')+1,__LINE__)) static inline bool _reportResult(kern_return_t result, const char *operation, const char* file, int line) { if ( result != ERR_SUCCESS ) { printf("%s:%d: %s: %s\n", file, line, operation, mach_error_string(result)); return false; } return true; } bool TPCircularBufferInit(TPCircularBuffer *buffer, int length) { // Keep trying until we get our buffer, needed to handle race conditions int retries = 3; while ( true ) { buffer->length = (int32_t)round_page(length); // We need whole page sizes // Temporarily allocate twice the length, so we have the contiguous address space to // support a second instance of the buffer directly after vm_address_t bufferAddress; kern_return_t result = vm_allocate(mach_task_self(), &bufferAddress, buffer->length * 2, VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit if ( result != ERR_SUCCESS ) { if ( retries-- == 0 ) { reportResult(result, "Buffer allocation"); return false; } // Try again if we fail continue; } // Now replace the second half of the allocation with a virtual copy of the first half. Deallocate the second half... result = vm_deallocate(mach_task_self(), bufferAddress + buffer->length, buffer->length); if ( result != ERR_SUCCESS ) { if ( retries-- == 0 ) { reportResult(result, "Buffer deallocation"); return false; } // If this fails somehow, deallocate the whole region and try again vm_deallocate(mach_task_self(), bufferAddress, buffer->length); continue; } // Re-map the buffer to the address space immediately after the buffer vm_address_t virtualAddress = bufferAddress + buffer->length; vm_prot_t cur_prot, max_prot; result = vm_remap(mach_task_self(), &virtualAddress, // mirror target buffer->length, // size of mirror 0, // auto alignment 0, // force remapping to virtualAddress mach_task_self(), // same task bufferAddress, // mirror source 0, // MAP READ-WRITE, NOT COPY &cur_prot, // unused protection struct &max_prot, // unused protection struct VM_INHERIT_DEFAULT); if ( result != ERR_SUCCESS ) { if ( retries-- == 0 ) { reportResult(result, "Remap buffer memory"); return false; } // If this remap failed, we hit a race condition, so deallocate and try again vm_deallocate(mach_task_self(), bufferAddress, buffer->length); continue; } if ( virtualAddress != bufferAddress+buffer->length ) { // If the memory is not contiguous, clean up both allocated buffers and try again if ( retries-- == 0 ) { printf("Couldn't map buffer memory to end of buffer\n"); return false; } vm_deallocate(mach_task_self(), virtualAddress, buffer->length); vm_deallocate(mach_task_self(), bufferAddress, buffer->length); continue; } buffer->buffer = (void*)bufferAddress; buffer->fillCount = 0; buffer->head = buffer->tail = 0; return true; } return false; } void TPCircularBufferCleanup(TPCircularBuffer *buffer) { vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2); memset(buffer, 0, sizeof(TPCircularBuffer)); } void TPCircularBufferClear(TPCircularBuffer *buffer) { int32_t fillCount; if ( TPCircularBufferTail(buffer, &fillCount) ) { TPCircularBufferConsume(buffer, fillCount); } } ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/TPCircularBuffer.h ================================================ // // TPCircularBuffer.h // Circular/Ring buffer implementation // // https://github.com/michaeltyson/TPCircularBuffer // // Created by Michael Tyson on 10/12/2011. // // // This implementation makes use of a virtual memory mapping technique that inserts a virtual copy // of the buffer memory directly after the buffer's end, negating the need for any buffer wrap-around // logic. Clients can simply use the returned memory address as if it were contiguous space. // // The implementation is thread-safe in the case of a single producer and single consumer. // // Virtual memory technique originally proposed by Philip Howard (http://vrb.slashusr.org/), and // adapted to Darwin by Kurt Revis (http://www.snoize.com, // http://www.snoize.com/Code/PlayBufferedSoundFile.tar.gz) // // // Copyright (C) 2012-2013 A Tasty Pixel // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source distribution. // #ifndef TPCircularBuffer_h #define TPCircularBuffer_h #include #include #include #ifdef __cplusplus extern "C" { #endif typedef struct { void *buffer; int32_t length; int32_t tail; int32_t head; volatile int32_t fillCount; } TPCircularBuffer; /*! * Initialise buffer * * Note that the length is advisory only: Because of the way the * memory mirroring technique works, the true buffer length will * be multiples of the device page size (e.g. 4096 bytes) * * @param buffer Circular buffer * @param length Length of buffer */ bool TPCircularBufferInit(TPCircularBuffer *buffer, int32_t length); /*! * Cleanup buffer * * Releases buffer resources. */ void TPCircularBufferCleanup(TPCircularBuffer *buffer); /*! * Clear buffer * * Resets buffer to original, empty state. * * This is safe for use by consumer while producer is accessing * buffer. */ void TPCircularBufferClear(TPCircularBuffer *buffer); // Reading (consuming) /*! * Access end of buffer * * This gives you a pointer to the end of the buffer, ready * for reading, and the number of available bytes to read. * * @param buffer Circular buffer * @param availableBytes On output, the number of bytes ready for reading * @return Pointer to the first bytes ready for reading, or NULL if buffer is empty */ static __inline__ __attribute__((always_inline)) void* TPCircularBufferTail(TPCircularBuffer *buffer, int32_t* availableBytes) { *availableBytes = buffer->fillCount; if ( *availableBytes == 0 ) return NULL; return (void*)((char*)buffer->buffer + buffer->tail); } /*! * Consume bytes in buffer * * This frees up the just-read bytes, ready for writing again. * * @param buffer Circular buffer * @param amount Number of bytes to consume */ static __inline__ __attribute__((always_inline)) void TPCircularBufferConsume(TPCircularBuffer *buffer, int32_t amount) { buffer->tail = (buffer->tail + amount) % buffer->length; OSAtomicAdd32Barrier(-amount, &buffer->fillCount); assert(buffer->fillCount >= 0); } /*! * Version of TPCircularBufferConsume without the memory barrier, for more optimal use in single-threaded contexts */ static __inline__ __attribute__((always_inline)) void TPCircularBufferConsumeNoBarrier(TPCircularBuffer *buffer, int32_t amount) { buffer->tail = (buffer->tail + amount) % buffer->length; buffer->fillCount -= amount; assert(buffer->fillCount >= 0); } /*! * Access front of buffer * * This gives you a pointer to the front of the buffer, ready * for writing, and the number of available bytes to write. * * @param buffer Circular buffer * @param availableBytes On output, the number of bytes ready for writing * @return Pointer to the first bytes ready for writing, or NULL if buffer is full */ static __inline__ __attribute__((always_inline)) void* TPCircularBufferHead(TPCircularBuffer *buffer, int32_t* availableBytes) { *availableBytes = (buffer->length - buffer->fillCount); if ( *availableBytes == 0 ) return NULL; return (void*)((char*)buffer->buffer + buffer->head); } // Writing (producing) /*! * Produce bytes in buffer * * This marks the given section of the buffer ready for reading. * * @param buffer Circular buffer * @param amount Number of bytes to produce */ static __inline__ __attribute__((always_inline)) void TPCircularBufferProduce(TPCircularBuffer *buffer, int amount) { buffer->head = (buffer->head + amount) % buffer->length; OSAtomicAdd32Barrier(amount, &buffer->fillCount); assert(buffer->fillCount <= buffer->length); } /*! * Version of TPCircularBufferProduce without the memory barrier, for more optimal use in single-threaded contexts */ static __inline__ __attribute__((always_inline)) void TPCircularBufferProduceNoBarrier(TPCircularBuffer *buffer, int amount) { buffer->head = (buffer->head + amount) % buffer->length; buffer->fillCount += amount; assert(buffer->fillCount <= buffer->length); } /*! * Helper routine to copy bytes to buffer * * This copies the given bytes to the buffer, and marks them ready for writing. * * @param buffer Circular buffer * @param src Source buffer * @param len Number of bytes in source buffer * @return true if bytes copied, false if there was insufficient space */ static __inline__ __attribute__((always_inline)) bool TPCircularBufferProduceBytes(TPCircularBuffer *buffer, const void* src, int32_t len) { int32_t space; void *ptr = TPCircularBufferHead(buffer, &space); if ( space < len ) return false; memcpy(ptr, src, len); TPCircularBufferProduce(buffer, len); return true; } #ifdef __cplusplus } #endif #endif ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/VERSION/CHANGELOG ================================================ EZAudio Created by Syed Haris Ali Copyright (c) 2013 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. ========================================================================================== 0.0.1 Initial release. Components include: EZAudioFile, EZAudioPlot, EZAudioPlotGL, EZMicrophone, EZOutput, and EZRecorder. Provided 6 example projects: CoreGraphicsWaveform, OpenGLWaveform, WaveformFromFile, PassThrough, Record, and PlayFile. 0.0.2 Fix for Cocoapod spec. Forgot to include files with .c extension. 0.0.3 Changing EZAudioPlot and EZAudioPlotGL to scroll for the EZPlotTypeRolling instead of wiping the screen clean when hitting the end Allowed EZMicrophone EZOutput to have custom AudioStreamBasicDescription setters Fixed bug in EZAudioFile's getWaveformData function where it was not exiting after sending back cached waveform data (rereading from the audio file) Added stereo support for EZMicrophone, EZAudioFile, EZRecorder, and EZOutput Added more memory cleanup for EZAudioFile Added adjustable rolling length for EZAudioPlot and EZAudioPlotGL so those rolling graphs can now range from 128 to 8192 whereas before it was fixed at 1024 Added adjustable resolution for waveform data coming from the EZAudioFile so output from the getWaveformDataWithCompletionBlock: function can literally be of any size. Try 128 for a low resolution waveform or 8192 for a much higher resolution waveform. Added quick fix for EZOutput to properly route stereo data coming from a circular buffer datasource. Next version (0.0.4) needs to add EZConverter to allow quick conversions between non-interleaved and interleaved formats. 0.0.4 Added ‘closeAudioFile’ to EZRecorder to properly dispose of internal audio file prior to trying to reload it using the EZAudioFile. Added new EZOutputDataSource method that provides pre-allocated AudioBufferList to fill instead of caller allocating and disposing on AudioBufferList. Much less errors. Added EZAudioPlayer for playback and visualization of local audio files (no network streaming yet). Merged bug fixes from community for EZAudio file. 0.0.5 Added multiple destination recording formats to the EZRecorder (EZRecorderFileType) ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudio/VERSION/VERSION ================================================ EZAudio Created by Syed Haris Ali Copyright (c) 2013 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. ========================================================================================== 0.0.5 ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudioRecordExample-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.axcel.$(PRODUCT_NAME:rfc1034identifier) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UIMainStoryboardFile Main_iPhone UIMainStoryboardFile~ipad Main_iPad UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/EZAudioRecordExample-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #import #ifndef __IPHONE_5_0 #warning "This project uses features only available in iOS SDK 5.0 and later." #endif #ifdef __OBJC__ #import #import #endif ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/Images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "subtype" : "retina4", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/RecordViewController.h ================================================ // // RecordViewController.h // EZAudioRecordExample // // Created by Syed Haris Ali on 12/15/13. // Copyright (c) 2013 Syed Haris Ali. All rights reserved. // #import // Import EZAudio header #import "EZAudio.h" #import "ZLHistogramAudioPlot.h" // Import AVFoundation to play the file (will save EZAudioFile and EZOutput for // separate example) #import // By default this will record a file to the application's documents directory // (within the application's sandbox) #define kAudioFilePath @"EZAudioTest.m4a" @interface RecordViewController : UIViewController /** Use a OpenGL based plot to visualize the data coming in */ @property (nonatomic, weak) IBOutlet ZLHistogramAudioPlot *audioPlot; /** A flag indicating whether we are recording or not */ @property (nonatomic, assign) BOOL isRecording; /** The microphone component */ @property (nonatomic, strong) EZMicrophone *microphone; /** The recorder component */ @property (nonatomic, strong) EZRecorder *recorder; #pragma mark - Actions /** Stops the recorder and starts playing whatever has been recorded. */ - (IBAction)playFile:(id)sender; /** Toggles the microphone on and off. When the microphone is on it will send its delegate (aka this view controller) the audio data in various ways (check out the EZMicrophoneDelegate documentation for more details); */ - (IBAction)toggleMicrophone:(id)sender; /** Toggles the microphone on and off. When the microphone is on it will send its delegate (aka this view controller) the audio data in various ways (check out the EZMicrophoneDelegate documentation for more details); */ - (IBAction)toggleRecording:(id)sender; @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/RecordViewController.m ================================================ // // RecordViewController.m // EZAudioRecordExample // // Created by Syed Haris Ali on 12/15/13. // Copyright (c) 2013 Syed Haris Ali. All rights reserved. // #import "RecordViewController.h" @interface RecordViewController () // Using AVPlayer for example @property (nonatomic, strong) AVAudioPlayer *audioPlayer; @property (nonatomic, weak) IBOutlet UISwitch *microphoneSwitch; @property (nonatomic, weak) IBOutlet UILabel *microphoneTextField; @property (nonatomic, weak) IBOutlet UIButton *playButton; @property (nonatomic, weak) IBOutlet UILabel *playingTextField; @property (nonatomic, weak) IBOutlet UISwitch *recordSwitch; @property (nonatomic, weak) IBOutlet UILabel *recordingTextField; @property (weak, nonatomic) IBOutlet UILabel *numOfBinsLabel; @property (weak, nonatomic) IBOutlet UILabel *paddingLabel; @property (strong, nonatomic) NSArray *murmurColors; @property (strong, nonatomic) NSArray *audioCopyColors; @end @implementation RecordViewController @synthesize audioPlot; @synthesize microphone; @synthesize microphoneSwitch; @synthesize microphoneTextField; @synthesize playButton; @synthesize playingTextField; @synthesize recorder; @synthesize recordSwitch; @synthesize recordingTextField; #pragma mark - Initialization - (id)init { self = [super init]; if (self) { [self initializeViewController]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self initializeViewController]; } return self; } #pragma mark - Initialize View Controller Here - (void)initializeViewController { // Create an instance of the microphone and tell it to use this view // controller instance as the delegate self.microphone = [EZMicrophone microphoneWithDelegate:self]; } #pragma mark - Customize the Audio Plot - (void)viewDidLoad { [super viewDidLoad]; /* Customizing the audio plot's look */ // Background color self.audioPlot.backgroundColor = [UIColor colorWithRed:0.984 green:0.71 blue:0.365 alpha:1]; // 250.92 181.05 93.075 // Waveform color // self.audioPlot.color = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 // alpha:1.0]; // Plot type self.audioPlot.plotType = EZPlotTypeBuffer; // Fill self.audioPlot.shouldFill = YES; // Mirror self.audioPlot.shouldMirror = YES; self.murmurColors = @[ [UIColor colorWithRed:242 / 255.0 green:128 / 255.0 blue:78 / 255.0 alpha:1], [UIColor colorWithRed:40 / 255.0 green:56 / 255.0 blue:72 / 255.0 alpha:1], [UIColor colorWithRed:244 / 255.0 green:234 / 255.0 blue:119 / 255.0 alpha:1], [UIColor colorWithRed:255 / 255.0 green:197 / 255.0 blue:69 / 255.0 alpha:1], [UIColor colorWithRed:193 / 255.0 green:75 / 255.0 blue:43 / 255.0 alpha:1], [UIColor colorWithRed:40 / 255.0 green:181 / 255.0 blue:164 / 255.0 alpha:1], [UIColor colorWithRed:208 / 255.0 green:221 / 255.0 blue:38 / 255.0 alpha:1], ]; self.audioCopyColors = @[ [UIColor colorWithRed:1 green:0.467 blue:0 alpha:1], [UIColor blackColor], [UIColor colorWithRed:0.157 green:0.6 blue:0.765 alpha:1], [UIColor colorWithRed:0.125 green:0.675 blue:0.910 alpha:1], [UIColor colorWithRed:0.310 green:0.765 blue:0.341 alpha:1] ]; self.audioPlot.colors = self.murmurColors; self.audioPlot.color = [UIColor colorWithWhite:0.598 alpha:1.000]; [self setNumOfBins:self.audioPlot.numOfBins]; /* Start the microphone */ [self.microphone startFetchingAudio]; self.microphoneTextField.text = @"Microphone On"; self.recordingTextField.text = @"Not Recording"; self.playingTextField.text = @"Not Playing"; // Hide the play button self.playButton.hidden = YES; /* Log out where the file is being written to within the app's documents directory */ NSLog(@"File written to application sandbox's documents directory: %@", [self testFilePathURL]); } #pragma mark - Actions - (void)playFile:(id)sender { // Update microphone state [self.microphone stopFetchingAudio]; self.microphoneTextField.text = @"Microphone Off"; self.microphoneSwitch.on = NO; // Update recording state self.isRecording = NO; self.recordingTextField.text = @"Not Recording"; self.recordSwitch.on = NO; // Create Audio Player if (self.audioPlayer) { if (self.audioPlayer.playing) { [self.audioPlayer stop]; } self.audioPlayer = nil; } // Close the audio file if (self.recorder) { [self.recorder closeAudioFile]; } NSError *err; self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[self testFilePathURL] error:&err]; [self.audioPlayer play]; self.audioPlayer.delegate = self; self.playingTextField.text = @"Playing"; } - (IBAction)changeAudioPlotType:(UISegmentedControl *)sender { self.audioPlot.plotType = sender.selectedSegmentIndex == 0 ? EZPlotTypeBuffer : EZPlotTypeRolling; } - (IBAction)changeColors:(UISegmentedControl *)sender { self.audioPlot.colors = sender.selectedSegmentIndex == 0 ? self.murmurColors : self.audioCopyColors; } - (IBAction)changeAudioPlotNumOfBins:(UISlider *)sender { self.audioPlot.numOfBins = 0 + sender.value * 30; [self setNumOfBins:self.audioPlot.numOfBins]; } - (IBAction)changePadding:(UISlider *)sender { self.audioPlot.padding = sender.value / 2; [self setPadding:self.audioPlot.padding]; } - (void)toggleMicrophone:(id)sender { self.playingTextField.text = @"Not Playing"; if (self.audioPlayer) { if (self.audioPlayer.playing) [self.audioPlayer stop]; self.audioPlayer = nil; } if (![(UISwitch *)sender isOn]) { [self.microphone stopFetchingAudio]; self.microphoneTextField.text = @"Microphone Off"; } else { [self.microphone startFetchingAudio]; self.microphoneTextField.text = @"Microphone On"; } } - (void)toggleRecording:(id)sender { self.playingTextField.text = @"Not Playing"; if (self.audioPlayer) { if (self.audioPlayer.playing) { [self.audioPlayer stop]; } self.audioPlayer = nil; } self.playButton.hidden = NO; if ([sender isOn]) { /* Create the recorder */ self.recorder = [EZRecorder recorderWithDestinationURL:[self testFilePathURL] sourceFormat:self.microphone .audioStreamBasicDescription destinationFileType:EZRecorderFileTypeM4A]; } else { [self.recorder closeAudioFile]; } self.isRecording = (BOOL)[sender isOn]; self.recordingTextField.text = self.isRecording ? @"Recording" : @"Not Recording"; } #pragma mark - EZMicrophoneDelegate #warning Thread Safety // Note that any callback that provides streamed audio data (like streaming // microphone input) happens on a separate audio thread that should not be // blocked. When we feed audio data into any of the UI components we need to // explicity create a GCD block on the main thread to properly get the UI to // work. - (void)microphone:(EZMicrophone *)microphone hasAudioReceived:(float **)buffer withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels { // Getting audio data as an array of float buffer arrays. What does that // mean? Because the audio is coming in as a stereo signal the data is split // into a left and right channel. So buffer[0] corresponds to the float* // data for the left channel while buffer[1] corresponds to the float* data // for the right channel. // See the Thread Safety warning above, but in a nutshell these callbacks // happen on a separate audio thread. We wrap any UI updating in a GCD block // on the main thread to avoid blocking that audio flow. dispatch_async(dispatch_get_main_queue(), ^{ // All the audio plot needs is the buffer data (float*) and the size. // Internally the audio plot will handle all the drawing related code, // history management, and freeing its own resources. Hence, one badass // line of code gets you a pretty plot :) [self.audioPlot updateBuffer:buffer[0] withBufferSize:bufferSize]; }); } - (void)microphone:(EZMicrophone *)microphone hasBufferList:(AudioBufferList *)bufferList withBufferSize:(UInt32)bufferSize withNumberOfChannels:(UInt32)numberOfChannels { // Getting audio data as a buffer list that can be directly fed into the // EZRecorder. This is happening on the audio thread - any UI updating needs // a GCD main queue block. This will keep appending data to the tail of the // audio file. if (self.isRecording) { [self.recorder appendDataFromBufferList:bufferList withBufferSize:bufferSize]; } } #pragma mark - AVAudioPlayerDelegate /* Occurs when the audio player instance completes playback */ - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { self.audioPlayer = nil; self.playingTextField.text = @"Finished Playing"; [self.microphone startFetchingAudio]; self.microphoneSwitch.on = YES; self.microphoneTextField.text = @"Microphone On"; } #pragma mark - Utility - (NSArray *)applicationDocuments { return NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); } - (NSString *)applicationDocumentsDirectory { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil; return basePath; } - (NSURL *)testFilePathURL { return [NSURL fileURLWithPath: [NSString stringWithFormat:@"%@/%@", [self applicationDocumentsDirectory], kAudioFilePath]]; } - (void)setNumOfBins:(NSInteger)num { self.numOfBinsLabel.text = [NSString stringWithFormat:@"NumOfBins: %li", (long)num]; } - (void)setPadding:(CGFloat)padding { self.paddingLabel.text = [NSString stringWithFormat:@"Padding: %.02f", padding]; } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample/main.m ================================================ // // main.m // EZAudioRecordExample // // Created by Syed Haris Ali on 12/15/13. // Copyright (c) 2013 Syed Haris Ali. All rights reserved. // #import #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 094761E21A2151D1004960A8 /* ZLHistogramAudioPlot.m in Sources */ = {isa = PBXBuildFile; fileRef = 094761E11A2151D1004960A8 /* ZLHistogramAudioPlot.m */; }; 09E3EB891A19ED44000022FB /* AEFloatConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB6F1A19ED44000022FB /* AEFloatConverter.m */; }; 09E3EB8A1A19ED44000022FB /* EZAudio.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB711A19ED44000022FB /* EZAudio.m */; }; 09E3EB8B1A19ED44000022FB /* EZAudioFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB731A19ED44000022FB /* EZAudioFile.m */; }; 09E3EB8C1A19ED44000022FB /* EZAudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB751A19ED44000022FB /* EZAudioPlayer.m */; }; 09E3EB8D1A19ED44000022FB /* EZAudioPlot.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB771A19ED44000022FB /* EZAudioPlot.m */; }; 09E3EB8E1A19ED44000022FB /* EZAudioPlotGL.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB791A19ED44000022FB /* EZAudioPlotGL.m */; }; 09E3EB8F1A19ED44000022FB /* EZAudioPlotGLKViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB7B1A19ED44000022FB /* EZAudioPlotGLKViewController.m */; }; 09E3EB901A19ED44000022FB /* EZMicrophone.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB7D1A19ED44000022FB /* EZMicrophone.m */; }; 09E3EB911A19ED44000022FB /* EZOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB7F1A19ED44000022FB /* EZOutput.m */; }; 09E3EB921A19ED44000022FB /* EZPlot.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB811A19ED44000022FB /* EZPlot.m */; }; 09E3EB931A19ED44000022FB /* EZRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB831A19ED44000022FB /* EZRecorder.m */; }; 09E3EB941A19ED44000022FB /* TPCircularBuffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 09E3EB841A19ED44000022FB /* TPCircularBuffer.c */; }; 09E3EB951A19ED44000022FB /* CHANGELOG in Resources */ = {isa = PBXBuildFile; fileRef = 09E3EB871A19ED44000022FB /* CHANGELOG */; }; 09E3EB961A19ED44000022FB /* VERSION in Resources */ = {isa = PBXBuildFile; fileRef = 09E3EB881A19ED44000022FB /* VERSION */; }; 940570CC185E7F8300EB94BA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 940570CB185E7F8300EB94BA /* Foundation.framework */; }; 940570CE185E7F8300EB94BA /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 940570CD185E7F8300EB94BA /* CoreGraphics.framework */; }; 940570D0185E7F8300EB94BA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 940570CF185E7F8300EB94BA /* UIKit.framework */; }; 940570D6185E7F8300EB94BA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 940570D4185E7F8300EB94BA /* InfoPlist.strings */; }; 940570D8185E7F8300EB94BA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 940570D7185E7F8300EB94BA /* main.m */; }; 940570DC185E7F8300EB94BA /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 940570DB185E7F8300EB94BA /* AppDelegate.m */; }; 940570DF185E7F8300EB94BA /* Main_iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 940570DD185E7F8300EB94BA /* Main_iPhone.storyboard */; }; 940570E2185E7F8300EB94BA /* Main_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 940570E0185E7F8300EB94BA /* Main_iPad.storyboard */; }; 940570E7185E7F8300EB94BA /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 940570E6185E7F8300EB94BA /* Images.xcassets */; }; 940570EE185E7F8300EB94BA /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 940570ED185E7F8300EB94BA /* XCTest.framework */; }; 940570EF185E7F8300EB94BA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 940570CB185E7F8300EB94BA /* Foundation.framework */; }; 940570F0185E7F8300EB94BA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 940570CF185E7F8300EB94BA /* UIKit.framework */; }; 940570F8185E7F8300EB94BA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 940570F6185E7F8300EB94BA /* InfoPlist.strings */; }; 940570FA185E7F8300EB94BA /* EZAudioRecordExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 940570F9185E7F8300EB94BA /* EZAudioRecordExampleTests.m */; }; 94057105185E805900EB94BA /* RecordViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94057104185E805900EB94BA /* RecordViewController.m */; }; 948D38C4185EA1140075398B /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 948D38C3185EA1140075398B /* AVFoundation.framework */; }; 948D38C6185EADFE0075398B /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 948D38C5185EADFE0075398B /* AudioToolbox.framework */; }; 948D38C8185EAE160075398B /* GLKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 948D38C7185EAE160075398B /* GLKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 940570F1185E7F8300EB94BA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 940570C0185E7F8300EB94BA /* Project object */; proxyType = 1; remoteGlobalIDString = 940570C7185E7F8300EB94BA; remoteInfo = EZAudioRecordExample; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 094761E01A2151D1004960A8 /* ZLHistogramAudioPlot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZLHistogramAudioPlot.h; sourceTree = ""; }; 094761E11A2151D1004960A8 /* ZLHistogramAudioPlot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZLHistogramAudioPlot.m; sourceTree = ""; }; 09E3EB6E1A19ED44000022FB /* AEFloatConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEFloatConverter.h; sourceTree = ""; }; 09E3EB6F1A19ED44000022FB /* AEFloatConverter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEFloatConverter.m; sourceTree = ""; }; 09E3EB701A19ED44000022FB /* EZAudio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZAudio.h; sourceTree = ""; }; 09E3EB711A19ED44000022FB /* EZAudio.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZAudio.m; sourceTree = ""; }; 09E3EB721A19ED44000022FB /* EZAudioFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZAudioFile.h; sourceTree = ""; }; 09E3EB731A19ED44000022FB /* EZAudioFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZAudioFile.m; sourceTree = ""; }; 09E3EB741A19ED44000022FB /* EZAudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZAudioPlayer.h; sourceTree = ""; }; 09E3EB751A19ED44000022FB /* EZAudioPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZAudioPlayer.m; sourceTree = ""; }; 09E3EB761A19ED44000022FB /* EZAudioPlot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZAudioPlot.h; sourceTree = ""; }; 09E3EB771A19ED44000022FB /* EZAudioPlot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZAudioPlot.m; sourceTree = ""; }; 09E3EB781A19ED44000022FB /* EZAudioPlotGL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZAudioPlotGL.h; sourceTree = ""; }; 09E3EB791A19ED44000022FB /* EZAudioPlotGL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZAudioPlotGL.m; sourceTree = ""; }; 09E3EB7A1A19ED44000022FB /* EZAudioPlotGLKViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZAudioPlotGLKViewController.h; sourceTree = ""; }; 09E3EB7B1A19ED44000022FB /* EZAudioPlotGLKViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZAudioPlotGLKViewController.m; sourceTree = ""; }; 09E3EB7C1A19ED44000022FB /* EZMicrophone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZMicrophone.h; sourceTree = ""; }; 09E3EB7D1A19ED44000022FB /* EZMicrophone.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZMicrophone.m; sourceTree = ""; }; 09E3EB7E1A19ED44000022FB /* EZOutput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZOutput.h; sourceTree = ""; }; 09E3EB7F1A19ED44000022FB /* EZOutput.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZOutput.m; sourceTree = ""; }; 09E3EB801A19ED44000022FB /* EZPlot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZPlot.h; sourceTree = ""; }; 09E3EB811A19ED44000022FB /* EZPlot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZPlot.m; sourceTree = ""; }; 09E3EB821A19ED44000022FB /* EZRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EZRecorder.h; sourceTree = ""; }; 09E3EB831A19ED44000022FB /* EZRecorder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EZRecorder.m; sourceTree = ""; }; 09E3EB841A19ED44000022FB /* TPCircularBuffer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = TPCircularBuffer.c; sourceTree = ""; }; 09E3EB851A19ED44000022FB /* TPCircularBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPCircularBuffer.h; sourceTree = ""; }; 09E3EB871A19ED44000022FB /* CHANGELOG */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CHANGELOG; sourceTree = ""; }; 09E3EB881A19ED44000022FB /* VERSION */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = VERSION; sourceTree = ""; }; 940570C8185E7F8300EB94BA /* EZAudioRecordExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EZAudioRecordExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 940570CB185E7F8300EB94BA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 940570CD185E7F8300EB94BA /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 940570CF185E7F8300EB94BA /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 940570D3185E7F8300EB94BA /* EZAudioRecordExample-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "EZAudioRecordExample-Info.plist"; sourceTree = ""; }; 940570D5185E7F8300EB94BA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 940570D7185E7F8300EB94BA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 940570D9185E7F8300EB94BA /* EZAudioRecordExample-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "EZAudioRecordExample-Prefix.pch"; sourceTree = ""; }; 940570DA185E7F8300EB94BA /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 940570DB185E7F8300EB94BA /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 940570DE185E7F8300EB94BA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main_iPhone.storyboard; sourceTree = ""; }; 940570E1185E7F8300EB94BA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main_iPad.storyboard; sourceTree = ""; }; 940570E6185E7F8300EB94BA /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 940570EC185E7F8300EB94BA /* EZAudioRecordExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EZAudioRecordExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 940570ED185E7F8300EB94BA /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 940570F5185E7F8300EB94BA /* EZAudioRecordExampleTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "EZAudioRecordExampleTests-Info.plist"; sourceTree = ""; }; 940570F7185E7F8300EB94BA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 940570F9185E7F8300EB94BA /* EZAudioRecordExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZAudioRecordExampleTests.m; sourceTree = ""; }; 94057103185E805900EB94BA /* RecordViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecordViewController.h; sourceTree = ""; }; 94057104185E805900EB94BA /* RecordViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RecordViewController.m; sourceTree = ""; }; 948D38C3185EA1140075398B /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 948D38C5185EADFE0075398B /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 948D38C7185EAE160075398B /* GLKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GLKit.framework; path = System/Library/Frameworks/GLKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 940570C5185E7F8300EB94BA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 948D38C8185EAE160075398B /* GLKit.framework in Frameworks */, 948D38C6185EADFE0075398B /* AudioToolbox.framework in Frameworks */, 948D38C4185EA1140075398B /* AVFoundation.framework in Frameworks */, 940570CE185E7F8300EB94BA /* CoreGraphics.framework in Frameworks */, 940570D0185E7F8300EB94BA /* UIKit.framework in Frameworks */, 940570CC185E7F8300EB94BA /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 940570E9185E7F8300EB94BA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 940570EE185E7F8300EB94BA /* XCTest.framework in Frameworks */, 940570F0185E7F8300EB94BA /* UIKit.framework in Frameworks */, 940570EF185E7F8300EB94BA /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 094761DF1A2151D1004960A8 /* ZLHistogramAudioPlot */ = { isa = PBXGroup; children = ( 094761E01A2151D1004960A8 /* ZLHistogramAudioPlot.h */, 094761E11A2151D1004960A8 /* ZLHistogramAudioPlot.m */, ); name = ZLHistogramAudioPlot; path = ../../ZLHistogramAudioPlot; sourceTree = ""; }; 09E3EB6D1A19ED44000022FB /* EZAudio */ = { isa = PBXGroup; children = ( 09E3EB6E1A19ED44000022FB /* AEFloatConverter.h */, 09E3EB6F1A19ED44000022FB /* AEFloatConverter.m */, 09E3EB701A19ED44000022FB /* EZAudio.h */, 09E3EB711A19ED44000022FB /* EZAudio.m */, 09E3EB721A19ED44000022FB /* EZAudioFile.h */, 09E3EB731A19ED44000022FB /* EZAudioFile.m */, 09E3EB741A19ED44000022FB /* EZAudioPlayer.h */, 09E3EB751A19ED44000022FB /* EZAudioPlayer.m */, 09E3EB761A19ED44000022FB /* EZAudioPlot.h */, 09E3EB771A19ED44000022FB /* EZAudioPlot.m */, 09E3EB781A19ED44000022FB /* EZAudioPlotGL.h */, 09E3EB791A19ED44000022FB /* EZAudioPlotGL.m */, 09E3EB7A1A19ED44000022FB /* EZAudioPlotGLKViewController.h */, 09E3EB7B1A19ED44000022FB /* EZAudioPlotGLKViewController.m */, 09E3EB7C1A19ED44000022FB /* EZMicrophone.h */, 09E3EB7D1A19ED44000022FB /* EZMicrophone.m */, 09E3EB7E1A19ED44000022FB /* EZOutput.h */, 09E3EB7F1A19ED44000022FB /* EZOutput.m */, 09E3EB801A19ED44000022FB /* EZPlot.h */, 09E3EB811A19ED44000022FB /* EZPlot.m */, 09E3EB821A19ED44000022FB /* EZRecorder.h */, 09E3EB831A19ED44000022FB /* EZRecorder.m */, 09E3EB841A19ED44000022FB /* TPCircularBuffer.c */, 09E3EB851A19ED44000022FB /* TPCircularBuffer.h */, 09E3EB861A19ED44000022FB /* VERSION */, ); path = EZAudio; sourceTree = ""; }; 09E3EB861A19ED44000022FB /* VERSION */ = { isa = PBXGroup; children = ( 09E3EB871A19ED44000022FB /* CHANGELOG */, 09E3EB881A19ED44000022FB /* VERSION */, ); path = VERSION; sourceTree = ""; }; 940570BF185E7F8300EB94BA = { isa = PBXGroup; children = ( 940570D1185E7F8300EB94BA /* EZAudioRecordExample */, 940570F3185E7F8300EB94BA /* EZAudioRecordExampleTests */, 940570CA185E7F8300EB94BA /* Frameworks */, 940570C9185E7F8300EB94BA /* Products */, ); sourceTree = ""; }; 940570C9185E7F8300EB94BA /* Products */ = { isa = PBXGroup; children = ( 940570C8185E7F8300EB94BA /* EZAudioRecordExample.app */, 940570EC185E7F8300EB94BA /* EZAudioRecordExampleTests.xctest */, ); name = Products; sourceTree = ""; }; 940570CA185E7F8300EB94BA /* Frameworks */ = { isa = PBXGroup; children = ( 948D38C7185EAE160075398B /* GLKit.framework */, 948D38C5185EADFE0075398B /* AudioToolbox.framework */, 948D38C3185EA1140075398B /* AVFoundation.framework */, 940570CB185E7F8300EB94BA /* Foundation.framework */, 940570CD185E7F8300EB94BA /* CoreGraphics.framework */, 940570CF185E7F8300EB94BA /* UIKit.framework */, 940570ED185E7F8300EB94BA /* XCTest.framework */, ); name = Frameworks; sourceTree = ""; }; 940570D1185E7F8300EB94BA /* EZAudioRecordExample */ = { isa = PBXGroup; children = ( 09E3EB6D1A19ED44000022FB /* EZAudio */, 094761DF1A2151D1004960A8 /* ZLHistogramAudioPlot */, 940570DA185E7F8300EB94BA /* AppDelegate.h */, 940570DB185E7F8300EB94BA /* AppDelegate.m */, 940570DD185E7F8300EB94BA /* Main_iPhone.storyboard */, 940570E0185E7F8300EB94BA /* Main_iPad.storyboard */, 94057103185E805900EB94BA /* RecordViewController.h */, 94057104185E805900EB94BA /* RecordViewController.m */, 940570E6185E7F8300EB94BA /* Images.xcassets */, 940570D2185E7F8300EB94BA /* Supporting Files */, ); path = EZAudioRecordExample; sourceTree = ""; }; 940570D2185E7F8300EB94BA /* Supporting Files */ = { isa = PBXGroup; children = ( 940570D3185E7F8300EB94BA /* EZAudioRecordExample-Info.plist */, 940570D4185E7F8300EB94BA /* InfoPlist.strings */, 940570D7185E7F8300EB94BA /* main.m */, 940570D9185E7F8300EB94BA /* EZAudioRecordExample-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; 940570F3185E7F8300EB94BA /* EZAudioRecordExampleTests */ = { isa = PBXGroup; children = ( 940570F9185E7F8300EB94BA /* EZAudioRecordExampleTests.m */, 940570F4185E7F8300EB94BA /* Supporting Files */, ); path = EZAudioRecordExampleTests; sourceTree = ""; }; 940570F4185E7F8300EB94BA /* Supporting Files */ = { isa = PBXGroup; children = ( 940570F5185E7F8300EB94BA /* EZAudioRecordExampleTests-Info.plist */, 940570F6185E7F8300EB94BA /* InfoPlist.strings */, ); name = "Supporting Files"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 940570C7185E7F8300EB94BA /* EZAudioRecordExample */ = { isa = PBXNativeTarget; buildConfigurationList = 940570FD185E7F8300EB94BA /* Build configuration list for PBXNativeTarget "EZAudioRecordExample" */; buildPhases = ( 940570C4185E7F8300EB94BA /* Sources */, 940570C5185E7F8300EB94BA /* Frameworks */, 940570C6185E7F8300EB94BA /* Resources */, ); buildRules = ( ); dependencies = ( ); name = EZAudioRecordExample; productName = EZAudioRecordExample; productReference = 940570C8185E7F8300EB94BA /* EZAudioRecordExample.app */; productType = "com.apple.product-type.application"; }; 940570EB185E7F8300EB94BA /* EZAudioRecordExampleTests */ = { isa = PBXNativeTarget; buildConfigurationList = 94057100185E7F8300EB94BA /* Build configuration list for PBXNativeTarget "EZAudioRecordExampleTests" */; buildPhases = ( 940570E8185E7F8300EB94BA /* Sources */, 940570E9185E7F8300EB94BA /* Frameworks */, 940570EA185E7F8300EB94BA /* Resources */, ); buildRules = ( ); dependencies = ( 940570F2185E7F8300EB94BA /* PBXTargetDependency */, ); name = EZAudioRecordExampleTests; productName = EZAudioRecordExampleTests; productReference = 940570EC185E7F8300EB94BA /* EZAudioRecordExampleTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 940570C0185E7F8300EB94BA /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0500; ORGANIZATIONNAME = "Syed Haris Ali"; TargetAttributes = { 940570EB185E7F8300EB94BA = { TestTargetID = 940570C7185E7F8300EB94BA; }; }; }; buildConfigurationList = 940570C3185E7F8300EB94BA /* Build configuration list for PBXProject "EZAudioRecordExample" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 940570BF185E7F8300EB94BA; productRefGroup = 940570C9185E7F8300EB94BA /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 940570C7185E7F8300EB94BA /* EZAudioRecordExample */, 940570EB185E7F8300EB94BA /* EZAudioRecordExampleTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 940570C6185E7F8300EB94BA /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 940570E2185E7F8300EB94BA /* Main_iPad.storyboard in Resources */, 940570E7185E7F8300EB94BA /* Images.xcassets in Resources */, 940570DF185E7F8300EB94BA /* Main_iPhone.storyboard in Resources */, 09E3EB961A19ED44000022FB /* VERSION in Resources */, 940570D6185E7F8300EB94BA /* InfoPlist.strings in Resources */, 09E3EB951A19ED44000022FB /* CHANGELOG in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 940570EA185E7F8300EB94BA /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 940570F8185E7F8300EB94BA /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 940570C4185E7F8300EB94BA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 09E3EB8D1A19ED44000022FB /* EZAudioPlot.m in Sources */, 09E3EB8C1A19ED44000022FB /* EZAudioPlayer.m in Sources */, 09E3EB891A19ED44000022FB /* AEFloatConverter.m in Sources */, 09E3EB921A19ED44000022FB /* EZPlot.m in Sources */, 09E3EB8F1A19ED44000022FB /* EZAudioPlotGLKViewController.m in Sources */, 09E3EB941A19ED44000022FB /* TPCircularBuffer.c in Sources */, 094761E21A2151D1004960A8 /* ZLHistogramAudioPlot.m in Sources */, 09E3EB931A19ED44000022FB /* EZRecorder.m in Sources */, 09E3EB8B1A19ED44000022FB /* EZAudioFile.m in Sources */, 09E3EB8E1A19ED44000022FB /* EZAudioPlotGL.m in Sources */, 94057105185E805900EB94BA /* RecordViewController.m in Sources */, 09E3EB901A19ED44000022FB /* EZMicrophone.m in Sources */, 09E3EB8A1A19ED44000022FB /* EZAudio.m in Sources */, 940570DC185E7F8300EB94BA /* AppDelegate.m in Sources */, 09E3EB911A19ED44000022FB /* EZOutput.m in Sources */, 940570D8185E7F8300EB94BA /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 940570E8185E7F8300EB94BA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 940570FA185E7F8300EB94BA /* EZAudioRecordExampleTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 940570F2185E7F8300EB94BA /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 940570C7185E7F8300EB94BA /* EZAudioRecordExample */; targetProxy = 940570F1185E7F8300EB94BA /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 940570D4185E7F8300EB94BA /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 940570D5185E7F8300EB94BA /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; 940570DD185E7F8300EB94BA /* Main_iPhone.storyboard */ = { isa = PBXVariantGroup; children = ( 940570DE185E7F8300EB94BA /* Base */, ); name = Main_iPhone.storyboard; sourceTree = ""; }; 940570E0185E7F8300EB94BA /* Main_iPad.storyboard */ = { isa = PBXVariantGroup; children = ( 940570E1185E7F8300EB94BA /* Base */, ); name = Main_iPad.storyboard; sourceTree = ""; }; 940570F6185E7F8300EB94BA /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 940570F7185E7F8300EB94BA /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 940570FB185E7F8300EB94BA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 940570FC185E7F8300EB94BA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.0; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 940570FE185E7F8300EB94BA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "EZAudioRecordExample/EZAudioRecordExample-Prefix.pch"; INFOPLIST_FILE = "EZAudioRecordExample/EZAudioRecordExample-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; 940570FF185E7F8300EB94BA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "EZAudioRecordExample/EZAudioRecordExample-Prefix.pch"; INFOPLIST_FILE = "EZAudioRecordExample/EZAudioRecordExample-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; 94057101185E7F8300EB94BA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/EZAudioRecordExample.app/EZAudioRecordExample"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "EZAudioRecordExample/EZAudioRecordExample-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "EZAudioRecordExampleTests/EZAudioRecordExampleTests-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; }; name = Debug; }; 94057102185E7F8300EB94BA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/EZAudioRecordExample.app/EZAudioRecordExample"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "EZAudioRecordExample/EZAudioRecordExample-Prefix.pch"; INFOPLIST_FILE = "EZAudioRecordExampleTests/EZAudioRecordExampleTests-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 940570C3185E7F8300EB94BA /* Build configuration list for PBXProject "EZAudioRecordExample" */ = { isa = XCConfigurationList; buildConfigurations = ( 940570FB185E7F8300EB94BA /* Debug */, 940570FC185E7F8300EB94BA /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 940570FD185E7F8300EB94BA /* Build configuration list for PBXNativeTarget "EZAudioRecordExample" */ = { isa = XCConfigurationList; buildConfigurations = ( 940570FE185E7F8300EB94BA /* Debug */, 940570FF185E7F8300EB94BA /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 94057100185E7F8300EB94BA /* Build configuration list for PBXNativeTarget "EZAudioRecordExampleTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 94057101185E7F8300EB94BA /* Debug */, 94057102185E7F8300EB94BA /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 940570C0185E7F8300EB94BA /* Project object */; } ================================================ FILE: EZAudioRecordExample/EZAudioRecordExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: EZAudioRecordExample/EZAudioRecordExampleTests/EZAudioRecordExampleTests-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.sha.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: EZAudioRecordExample/EZAudioRecordExampleTests/EZAudioRecordExampleTests.m ================================================ // // EZAudioRecordExampleTests.m // EZAudioRecordExampleTests // // Created by Syed Haris Ali on 12/15/13. // Copyright (c) 2013 Syed Haris Ali. All rights reserved. // #import @interface EZAudioRecordExampleTests : XCTestCase @end @implementation EZAudioRecordExampleTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); } @end ================================================ FILE: EZAudioRecordExample/EZAudioRecordExampleTests/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Zhixuan Lai 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. ================================================ FILE: README.md ================================================ ZLHistogramAudioPlot ==================== A hardware-accelerated audio visualization view using EZAudio, inspired by [AudioCopy](https://itunes.apple.com/us/app/audiocopy/id719137307?mt=8). ZLHistogramAudioPlot was originally developed for [Murmur](http://zhxnlai.github.io/#/murmur). Preview --- ###Buffer ![preview buffer](Previews/ZLHistogramAudioPlotBuffer.gif) ###Rolling ![preview rolling](Previews/ZLHistogramAudioPlotRolling.gif) CocoaPods --- You can install `ZLHistogramAudioPlot` through CocoaPods adding the following to your Podfile: pod 'ZLHistogramAudioPlot' Usage --- Checkout the [demo app](https://github.com/zhxnlai/ZLHistogramAudioPlot/tree/master/EZAudioRecordExample) for an example. Customizable attributes: ~~~objective-c /// The upper bound of the frequency range the audio plot will display. Default: /// 10000Hz @property (nonatomic) float maxFrequency; /// The lower bound of the frequency range the audio plot will display. Default: /// 1200Hz @property (nonatomic) float minFrequency; /// The number of bins in the audio plot. Default: 30 @property (nonatomic) NSUInteger numOfBins; /// The padding of each bin in percent width. Default: 0.1 @property (nonatomic) CGFloat padding; /// The gain applied to the height of each bin. Default: 10 @property (nonatomic) CGFloat gain; /// A float that specifies the vertical gravitational acceleration applied to /// each bin. Default: 10 pixel/sec^2 @property (nonatomic) float gravity; /// The color of all bins in the audio plot. @property (strong, nonatomic) UIColor *color; /// An array of color objects defining the color of each bin in the audio plot. /// If not set, the color attribute will be used instead. Currently only /// supported by plot type EZPlotTypeBuffer. @property (strong, nonatomic) NSArray *colors; ~~~ Dependencies --- - `ZLHistogramAudioPlot` is a subclass of `EZAudioPlot`. It requires [EZAudio](https://github.com/syedhali/EZAudio). - It also requires [Accelerate](https://developer.apple.com/library/ios/documentation/Accelerate/Reference/AccelerateFWRef/_index.html) framework for hardware acceleration. Compatibilty --- `ZLHistogramAudioPlot` uses the following interface provided by [EZAudio](https://github.com/syedhali/EZAudio) to get audio data: ~~~objective-c - (void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize; ~~~ It can be easily modified to work with [Audio Unit](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/Introduction/Introduction.html) and [Core Audio](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html). Requirements --- - iOS 6 or higher. - Automatic Reference Counting (ARC). License --- ZLHistogramAudioPlot is available under the MIT license. See the LICENSE file for more info. ================================================ FILE: ZLHistogramAudioPlot/ZLHistogramAudioPlot.h ================================================ // // MRBarChartAudioPlot.h // MurmurReborn // // Created by Zhixuan Lai on 8/2/14. // Copyright (c) 2014 Zhixuan Lai. All rights reserved. // #import "EZAudioPlot.h" @interface ZLHistogramAudioPlot : EZAudioPlot /// The upper bound of the frequency range the audio plot will display. Default: /// 10000Hz @property (nonatomic) float maxFrequency; /// The lower bound of the frequency range the audio plot will display. Default: /// 1200Hz @property (nonatomic) float minFrequency; /// The number of bins in the audio plot. Default: 30 @property (nonatomic) NSUInteger numOfBins; /// The padding of each bin in percent width. Default: 0.1 @property (nonatomic) CGFloat padding; /// The gain applied to the height of each bin. Default: 10 @property (nonatomic) CGFloat gain; /// A float that specifies the vertical gravitational acceleration applied to /// each bin. Default: 10 pixel/sec^2 @property (nonatomic) float gravity; /// The color of all bins in the audio plot. @property (strong, nonatomic) UIColor *color; /// An array of color objects defining the color of each bin in the audio plot. /// If not set, the color attribute will be used instead. Currently only /// supported by plot type EZPlotTypeBuffer. @property (strong, nonatomic) NSArray *colors; @end ================================================ FILE: ZLHistogramAudioPlot/ZLHistogramAudioPlot.m ================================================ // // MRBarChartAudioPlot.m // MurmurReborn // // Created by Zhixuan Lai on 8/2/14. // Copyright (c) 2014 Zhixuan Lai. All rights reserved. // #import "ZLHistogramAudioPlot.h" #import #import "EZAudio.h" const UInt32 kMaxFrames = 2048; const Float32 kAdjust0DB = 1.5849e-13; const NSInteger kFrameInterval = 1; // Alter this to draw more or less often @interface ZLHistogramAudioPlot () { // ftt setup FFTSetup fftSetup; COMPLEX_SPLIT A; int log2n, n, nOver2; float sampleRate, *dataBuffer; size_t bufferCapacity, index; // buffers float *heightsByFrequency, *speeds, *times, *tSqrts, *vts, *deltaHeights; } @property (strong, nonatomic) NSMutableArray *heightsByTime; @property (strong, nonatomic) CADisplayLink *displaylink; @end @implementation ZLHistogramAudioPlot @synthesize backgroundColor = _backgroundColor; @synthesize color = _color; @synthesize plotType = _plotType; @synthesize numOfBins; @synthesize gain; #pragma mark - Init - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setup:frame]; } return self; } - (void)awakeFromNib { [super awakeFromNib]; [self layoutIfNeeded]; [self setup:self.frame]; } - (void)setup:(CGRect)frame { // default attributes self.maxFrequency = 10000; self.minFrequency = 1200; self.numOfBins = 30; self.padding = 1 / 10.0; self.gain = 10; self.gravity = 10; self.color = [UIColor lightGrayColor]; self.colors = @[ [UIColor colorWithRed:242 / 255.0 green:128 / 255.0 blue:78 / 255.0 alpha:1], [UIColor colorWithRed:40 / 255.0 green:56 / 255.0 blue:72 / 255.0 alpha:1], [UIColor colorWithRed:244 / 255.0 green:234 / 255.0 blue:119 / 255.0 alpha:1], [UIColor colorWithRed:255 / 255.0 green:197 / 255.0 blue:69 / 255.0 alpha:1], [UIColor colorWithRed:193 / 255.0 green:75 / 255.0 blue:43 / 255.0 alpha:1], [UIColor colorWithRed:40 / 255.0 green:181 / 255.0 blue:164 / 255.0 alpha:1], [UIColor colorWithRed:208 / 255.0 green:221 / 255.0 blue:38 / 255.0 alpha:1], ]; // ftt setup dataBuffer = (float *)malloc(kMaxFrames * sizeof(float)); log2n = log2f(kMaxFrames); n = 1 << log2n; assert(n == kMaxFrames); nOver2 = kMaxFrames / 2; bufferCapacity = kMaxFrames; index = 0; A.realp = (float *)malloc(nOver2 * sizeof(float)); A.imagp = (float *)malloc(nOver2 * sizeof(float)); fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2); // inherited properties _plotType = EZPlotTypeRolling; // configure audio session AVAudioSession *session = [AVAudioSession sharedInstance]; sampleRate = session.sampleRate; // start timer self.displaylink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateHeights)]; self.displaylink.frameInterval = kFrameInterval; [self.displaylink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; } - (void)dealloc { [self.displaylink invalidate]; self.displaylink = nil; if (plotData) { free(plotData); } [self freeBuffersIfNeeded]; } #pragma mark - Properties - (void)setNumOfBins:(NSUInteger)someNumOfBins { numOfBins = MAX(1, someNumOfBins); // reset buffers [self freeBuffersIfNeeded]; // create buffers heightsByFrequency = (float *)calloc(sizeof(float), numOfBins); speeds = (float *)calloc(sizeof(float), numOfBins); times = (float *)calloc(sizeof(float), numOfBins); tSqrts = (float *)calloc(sizeof(float), numOfBins); vts = (float *)calloc(sizeof(float), numOfBins); deltaHeights = (float *)calloc(sizeof(float), numOfBins); self.heightsByTime = [NSMutableArray arrayWithCapacity:numOfBins]; for (int i = 0; i < numOfBins; i++) { self.heightsByTime[i] = [NSNumber numberWithFloat:0]; } } #pragma mark - Timer Callback - (void)updateHeights { // delay from last frame float delay = self.displaylink.duration * self.displaylink.frameInterval; // increment time vDSP_vsadd(times, 1, &delay, times, 1, numOfBins); // clamp time static const float timeMin = 1.5, timeMax = 10; vDSP_vclip(times, 1, &timeMin, &timeMax, times, 1, numOfBins); // increment speed float g = self.gravity * delay; vDSP_vsma(times, 1, &g, speeds, 1, speeds, 1, numOfBins); // increment height vDSP_vsq(times, 1, tSqrts, 1, numOfBins); vDSP_vmul(speeds, 1, times, 1, vts, 1, numOfBins); float aOver2 = g / 2; vDSP_vsma(tSqrts, 1, &aOver2, vts, 1, deltaHeights, 1, numOfBins); vDSP_vneg(deltaHeights, 1, deltaHeights, 1, numOfBins); vDSP_vadd(heightsByFrequency, 1, deltaHeights, 1, heightsByFrequency, 1, numOfBins); [self _refreshDisplay]; } #pragma mark - Update Buffers - (void)setSampleData:(float *)data length:(int)length { // fill the buffer with our sampled data. If we fill our buffer, run the // fft. int inNumberFrames = length; int read = (int)(bufferCapacity - index); if (read > inNumberFrames) { memcpy((float *)dataBuffer + index, data, inNumberFrames * sizeof(float)); index += inNumberFrames; } else { // if we enter this conditional, our buffer will be filled and we should // perform the FFT. memcpy((float *)dataBuffer + index, data, read * sizeof(float)); // reset the index. index = 0; // fft vDSP_ctoz((COMPLEX *)dataBuffer, 2, &A, 1, nOver2); vDSP_fft_zrip(fftSetup, &A, 1, log2n, FFT_FORWARD); vDSP_ztoc(&A, 1, (COMPLEX *)dataBuffer, 2, nOver2); // convert to dB Float32 one = 1, zero = 0; vDSP_vsq(dataBuffer, 1, dataBuffer, 1, inNumberFrames); vDSP_vsadd(dataBuffer, 1, &kAdjust0DB, dataBuffer, 1, inNumberFrames); vDSP_vdbcon(dataBuffer, 1, &one, dataBuffer, 1, inNumberFrames, 0); vDSP_vthr(dataBuffer, 1, &zero, dataBuffer, 1, inNumberFrames); // aux float mul = (sampleRate / bufferCapacity) / 2; int minFrequencyIndex = self.minFrequency / mul; int maxFrequencyIndex = self.maxFrequency / mul; int numDataPointsPerColumn = (maxFrequencyIndex - minFrequencyIndex) / numOfBins; float maxHeight = 0; for (NSUInteger i = 0; i < numOfBins; i++) { // calculate new column height float avg = 0; vDSP_meanv(dataBuffer + minFrequencyIndex + i * numDataPointsPerColumn, 1, &avg, numDataPointsPerColumn); CGFloat columnHeight = MIN(avg * self.gain, CGRectGetHeight(self.bounds)); maxHeight = MAX(maxHeight, columnHeight); // set column height, speed and time if needed if (columnHeight > heightsByFrequency[i]) { heightsByFrequency[i] = columnHeight; speeds[i] = 0; times[i] = 0; } } [self.heightsByTime addObject:[NSNumber numberWithFloat:maxHeight]]; if (self.heightsByTime.count > numOfBins) { [self.heightsByTime removeObjectAtIndex:0]; } } } - (void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize { [self setSampleData:buffer length:bufferSize]; } #pragma mark - Drawing - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextSaveGState(ctx); CGRect frame = self.bounds; // set the background color [(UIColor *)self.backgroundColor set]; UIRectFill(frame); CGFloat columnWidth = rect.size.width / (_plotType == EZPlotTypeBuffer ? numOfBins : numOfBins - 1); CGFloat actualWidth = MAX(1, columnWidth * (1 - 2 * self.padding)); CGFloat actualPadding = (columnWidth - actualWidth) / 2; // TODO: warning: padding is larger than width for (NSUInteger i = 0; i < numOfBins; i++) { CGFloat columnHeight = _plotType == EZPlotTypeBuffer ? heightsByFrequency[i] : [self.heightsByTime[i] floatValue]; if (columnHeight <= 0) continue; CGFloat columnX = i * columnWidth - (_plotType == EZPlotTypeBuffer ? 0 : columnWidth * [self rollingOffset]); UIBezierPath *rectanglePath = [UIBezierPath bezierPathWithRect:CGRectMake(columnX + actualPadding, CGRectGetHeight(frame) - columnHeight, actualWidth, columnHeight)]; UIColor *color = (_plotType == EZPlotTypeBuffer && self.colors) ? [self.colors objectAtIndex:i % self.colors.count] : self.color; [color setFill]; [rectanglePath fill]; } CGContextRestoreGState(ctx); } - (void)_refreshDisplay { #if TARGET_OS_IPHONE [self setNeedsDisplay]; #elif TARGET_OS_MAC [self setNeedsDisplay:YES]; #endif } #pragma mark - () void printFloatArray(float *array, int length, NSString *prefix) { NSMutableString *str = [NSMutableString string]; for (int i = 0; i < length; i++) { [str appendFormat:@"%f ", array[i]]; } NSLog(@"%@ %@", prefix, str); } /// Return rolling offset for rolling plot in percent - (CGFloat)rollingOffset { return (CGFloat)index / bufferCapacity; } - (void)freeBuffersIfNeeded { if (heightsByFrequency) { free(heightsByFrequency); } if (speeds) { free(speeds); } if (times) { free(times); } if (tSqrts) { free(tSqrts); } if (vts) { free(vts); } if (deltaHeights) { free(deltaHeights); } } @end ================================================ FILE: ZLHistogramAudioPlot.podspec ================================================ Pod::Spec.new do |s| s.name = "ZLHistogramAudioPlot" s.version = "0.0.1" s.summary = "A hardware-accelerated audio visualization view using EZAudio, inspired by AudioCopy." s.description = <<-DESC A hardware-accelerated audio visualization view using EZAudio, inspired by [AudioCopy](https://itunes.apple.com/us/app/audiocopy/id719137307?mt=8). DESC s.homepage = "https://github.com/zhxnlai/ZLHistogramAudioPlot" s.screenshots = "https://raw.githubusercontent.com/zhxnlai/ZLHistogramAudioPlot/master/Previews/ZLHistogramAudioPlotBuffer.gif" s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Zhixuan Lai" => "zhxnlai@gmail.com" } s.platform = :ios, "6.0" s.source = { :git => "https://github.com/zhxnlai/ZLHistogramAudioPlot.git", :tag => "0.0.1" } s.source_files = "ZLHistogramAudioPlot/*.{h,m}" s.frameworks = "UIKit", "Accelerate" s.requires_arc = true s.dependency "EZAudio" end