diff --git a/TheAmazingAudioEngine.xcodeproj/project.pbxproj b/TheAmazingAudioEngine.xcodeproj/project.pbxproj index d3d2818..b81cebf 100644 --- a/TheAmazingAudioEngine.xcodeproj/project.pbxproj +++ b/TheAmazingAudioEngine.xcodeproj/project.pbxproj @@ -285,6 +285,12 @@ 4CE5F4D11CD3169C00322F03 /* AEAudioThreadEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE5F4CD1CD3169C00322F03 /* AEAudioThreadEndpoint.m */; }; 4CE5F4D21CD3169C00322F03 /* AEAudioThreadEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE5F4CD1CD3169C00322F03 /* AEAudioThreadEndpoint.m */; }; 4CE5F4D31CD3169C00322F03 /* AEAudioThreadEndpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CE5F4CD1CD3169C00322F03 /* AEAudioThreadEndpoint.m */; }; + B30958101DA2A59C00F07FB6 /* AEMeteringModule.h in Headers */ = {isa = PBXBuildFile; fileRef = B32E7E541DA2A15C00C91264 /* AEMeteringModule.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B30958111DA2A5A000F07FB6 /* AEMeteringModule.h in Headers */ = {isa = PBXBuildFile; fileRef = B32E7E541DA2A15C00C91264 /* AEMeteringModule.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B30958121DA2A5A400F07FB6 /* AEMeteringModule.h in Headers */ = {isa = PBXBuildFile; fileRef = B32E7E541DA2A15C00C91264 /* AEMeteringModule.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B32E7E591DA2A15C00C91264 /* AEMeteringModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B32E7E551DA2A15C00C91264 /* AEMeteringModule.m */; }; + B32E7E5A1DA2A15C00C91264 /* AEMeteringModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B32E7E551DA2A15C00C91264 /* AEMeteringModule.m */; }; + B32E7E5B1DA2A15C00C91264 /* AEMeteringModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B32E7E551DA2A15C00C91264 /* AEMeteringModule.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -435,6 +441,8 @@ 4CE5F4CA1CD3135800322F03 /* AECrossThreadMessagingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AECrossThreadMessagingTests.m; sourceTree = ""; }; 4CE5F4CC1CD3169C00322F03 /* AEAudioThreadEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEAudioThreadEndpoint.h; sourceTree = ""; }; 4CE5F4CD1CD3169C00322F03 /* AEAudioThreadEndpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEAudioThreadEndpoint.m; sourceTree = ""; }; + B32E7E541DA2A15C00C91264 /* AEMeteringModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AEMeteringModule.h; sourceTree = ""; }; + B32E7E551DA2A15C00C91264 /* AEMeteringModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AEMeteringModule.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -475,6 +483,8 @@ children = ( 4C94E2A31CAE6AFF006EB497 /* AEAudioFileRecorderModule.h */, 4C94E2A41CAE6AFF006EB497 /* AEAudioFileRecorderModule.m */, + B32E7E541DA2A15C00C91264 /* AEMeteringModule.h */, + B32E7E551DA2A15C00C91264 /* AEMeteringModule.m */, ); path = Taps; sourceTree = ""; @@ -695,6 +705,7 @@ 4CB2F2EE1D49ABC6008F745F /* AEManagedValue.h in Headers */, 4CE5F4C51CD30A1900322F03 /* AEMainThreadEndpoint.h in Headers */, 4C7756A11CD2E5E3004415A2 /* AECircularBuffer.h in Headers */, + B30958111DA2A5A000F07FB6 /* AEMeteringModule.h in Headers */, 4C9F0F561CB265F90032903E /* AEModule.h in Headers */, 4CBCF29F1CFBC3D200CA2EA0 /* AESplitterModule.h in Headers */, 4CB2F2E81D49ABC6008F745F /* AEBufferStack.h in Headers */, @@ -745,6 +756,7 @@ 4CB2F2EF1D49ABC6008F745F /* AEManagedValue.h in Headers */, 4CE5F4C61CD30A1900322F03 /* AEMainThreadEndpoint.h in Headers */, 4C7756A21CD2E5E3004415A2 /* AECircularBuffer.h in Headers */, + B30958121DA2A5A400F07FB6 /* AEMeteringModule.h in Headers */, 4C9F0F9F1CB269C30032903E /* AEModule.h in Headers */, 4CBCF2A01CFBC3D200CA2EA0 /* AESplitterModule.h in Headers */, 4CB2F2E91D49ABC6008F745F /* AEBufferStack.h in Headers */, @@ -809,6 +821,7 @@ 4CDCAD8E1CA5484D008AAEF1 /* AEReverbModule.h in Headers */, 4CDCAD531CA3D223008AAEF1 /* AEOscillatorModule.h in Headers */, 4CDCAD8A1CA5484D008AAEF1 /* AEParametricEqModule.h in Headers */, + B30958101DA2A59C00F07FB6 /* AEMeteringModule.h in Headers */, 4CB2F2ED1D49ABC6008F745F /* AEManagedValue.h in Headers */, 4CB2F2E11D49ABC6008F745F /* AEArray.h in Headers */, 4CDCAD801CA5484D008AAEF1 /* AEHighPassModule.h in Headers */, @@ -986,6 +999,7 @@ 4C9F0F3D1CB265F90032903E /* AEAudioUnitOutput.m in Sources */, 4C9F0F3E1CB265F90032903E /* AELowPassModule.m in Sources */, 4C9F0F3F1CB265F90032903E /* AEHighPassModule.m in Sources */, + B32E7E5A1DA2A15C00C91264 /* AEMeteringModule.m in Sources */, 4C9F0F401CB265F90032903E /* AEVarispeedModule.m in Sources */, 4C9F0F411CB265F90032903E /* AEBandpassModule.m in Sources */, 4C9F0F421CB265F90032903E /* AEAudioFileRecorderModule.m in Sources */, @@ -1038,6 +1052,7 @@ 4C9F0F871CB269C30032903E /* AEAudioUnitOutput.m in Sources */, 4C9F0F881CB269C30032903E /* AELowPassModule.m in Sources */, 4C9F0F891CB269C30032903E /* AEHighPassModule.m in Sources */, + B32E7E5B1DA2A15C00C91264 /* AEMeteringModule.m in Sources */, 4C9F0F8A1CB269C30032903E /* AEVarispeedModule.m in Sources */, 4C9F0F8B1CB269C30032903E /* AEBandpassModule.m in Sources */, 4C9F0F8C1CB269C30032903E /* AEAudioFileRecorderModule.m in Sources */, @@ -1094,6 +1109,7 @@ 4CDCAD911CA5484D008AAEF1 /* AEVarispeedModule.m in Sources */, 4CDCAD791CA5484D008AAEF1 /* AEBandpassModule.m in Sources */, 4C94E2A61CAE6AFF006EB497 /* AEAudioFileRecorderModule.m in Sources */, + B32E7E591DA2A15C00C91264 /* AEMeteringModule.m in Sources */, 4CDCADA11CA90FD3008AAEF1 /* AEAudioFilePlayerModule.m in Sources */, 4CDCAD481CA3C31C008AAEF1 /* AEUtilities.m in Sources */, 4CE5F4D11CD3169C00322F03 /* AEAudioThreadEndpoint.m in Sources */, diff --git a/TheAmazingAudioEngine/Modules/Taps/AEMeteringModule.h b/TheAmazingAudioEngine/Modules/Taps/AEMeteringModule.h new file mode 100644 index 0000000..9e4bcc7 --- /dev/null +++ b/TheAmazingAudioEngine/Modules/Taps/AEMeteringModule.h @@ -0,0 +1,50 @@ +// +// AEMeteringModule.h +// TheAmazingAudioEngine +// +// Created by Leo Thiessen on 2016-04-14. +// Copyright © 2016 A Tasty Pixel. All rights reserved. +// +// 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 "AEModule.h" + +typedef struct { + float average; + float peak; +} AEMeteringChannelLevels; + +typedef struct { + int maxChannel; + AEMeteringChannelLevels * _Nullable channels; +} AEMeteringLevels; + +@interface AEMeteringModule : AEModule + +/*! Initialize with 2 channels. */ +- (instancetype _Nullable)initWithRenderer:(AERenderer * _Nullable)renderer; + +/*! Initialize with 1-n number of channels. */ +- (instancetype _Nullable)initWithRenderer:(AERenderer * _Nullable)renderer maxChannel:(int)maxChannel; + +/*! Obtain average & peak levels, per channel, since the last access to this property. */ +@property (nonatomic, readonly) AEMeteringLevels * _Nonnull levels; + +@end diff --git a/TheAmazingAudioEngine/Modules/Taps/AEMeteringModule.m b/TheAmazingAudioEngine/Modules/Taps/AEMeteringModule.m new file mode 100644 index 0000000..e8eecd1 --- /dev/null +++ b/TheAmazingAudioEngine/Modules/Taps/AEMeteringModule.m @@ -0,0 +1,141 @@ +// +// AEMeteringModule.m +// TheAmazingAudioEngine +// +// Created by Leo Thiessen on 2016-04-14. +// Copyright © 2016 A Tasty Pixel. All rights reserved. +// +// 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 "AEMeteringModule.h" +@import Accelerate; + +/*! + * Audio level metering data + */ +typedef struct __audio_meters_t { + int maxChannel; + int capacity; + double * chanMeanAccumulator; + int chanMeanBlockCount; + float * chanPeak; + float * chanAverage; + BOOL reset; +} audio_meters_t; + +@interface AEMeteringModule () { + AEMeteringLevels levelsStruct; + audio_meters_t metersStruct; +} +@end + +@implementation AEMeteringModule + +- (instancetype _Nullable)initWithRenderer:(AERenderer * _Nullable)renderer { + if ( !(self = [super initWithRenderer:renderer]) ) { + return nil; + } + [self _setupWithMaxChannel:2]; // default of 2 channels, aka "stereo" + return self; +} + +- (instancetype _Nullable)initWithRenderer:(AERenderer * _Nullable)renderer maxChannel:(int)maxChannel { + if ( maxChannel < 1 || !(self = [super initWithRenderer:renderer]) ) { + return nil; + } + [self _setupWithMaxChannel:maxChannel]; + return self; +} + +- (void)_setupWithMaxChannel:(int)maxChannel { + metersStruct.maxChannel = maxChannel; + metersStruct.capacity = maxChannel; + metersStruct.chanMeanAccumulator = calloc(maxChannel, sizeof(double)); + metersStruct.chanMeanBlockCount = 0; + metersStruct.chanPeak = calloc(maxChannel, sizeof(float)); + metersStruct.chanAverage = calloc(maxChannel, sizeof(float)); + metersStruct.reset = NO; + + levelsStruct.maxChannel = maxChannel; + levelsStruct.channels = calloc(maxChannel, sizeof(AEMeteringChannelLevels)); + + self.processFunction = AEMeteringModuleProcess; +} + +- (void)dealloc { + if ( levelsStruct.channels ) { + free(levelsStruct.channels); + } + free(metersStruct.chanMeanAccumulator); + free(metersStruct.chanPeak); + free(metersStruct.chanAverage); +} + +- (void)rendererDidChangeNumberOfChannels { + int newChannelCount = self.renderer.numberOfOutputChannels; + if ( newChannelCount <= metersStruct.capacity ) { + metersStruct.maxChannel = newChannelCount; + levelsStruct.maxChannel = newChannelCount; + if ( levelsStruct.channels ) { + for ( int i = newChannelCount; i < metersStruct.capacity; ++i ) { // Zero out any unused capacity + levelsStruct.channels[i].average = 0; + levelsStruct.channels[i].peak = 0; + } + } + } +} + +- (AEMeteringLevels * _Nonnull)levels { + if ( levelsStruct.channels ) { + for ( int i = 0; i < metersStruct.maxChannel; ++i ) { + levelsStruct.channels[i].average = metersStruct.chanAverage[i]; + levelsStruct.channels[i].peak = metersStruct.chanPeak[i]; + } + metersStruct.reset = YES; + } + return &levelsStruct; +} + +static void AEMeteringModuleProcess(__unsafe_unretained AEMeteringModule * self, + const AERenderContext * _Nonnull context) { + const AudioBufferList * abl = AEBufferStackGet(context->stack, 0); + if ( !abl ) return; + + if ( self->metersStruct.reset ) { + self->metersStruct.reset = NO; + self->metersStruct.chanMeanBlockCount = 0; + vDSP_vclr(self->metersStruct.chanPeak, 1, self->metersStruct.capacity); + vDSP_vclr(self->metersStruct.chanAverage, 1, self->metersStruct.capacity); + vDSP_vclrD(self->metersStruct.chanMeanAccumulator, 1, self->metersStruct.capacity); + } + + float peak, avg; + for ( int i = 0; i < abl->mNumberBuffers && i < self->metersStruct.maxChannel; ++i ) { + peak = 0, avg = 0; + vDSP_maxmgv((float*)abl->mBuffers[i].mData, 1, &peak, context->frames); + if ( peak > self->metersStruct.chanPeak[i] ) { self->metersStruct.chanPeak[i] = peak; } + vDSP_meamgv((float*)abl->mBuffers[i].mData, 1, &avg, context->frames); + self->metersStruct.chanMeanAccumulator[i] += avg; + if ( i == 0 ) { self->metersStruct.chanMeanBlockCount++; } + self->metersStruct.chanAverage[i] = self->metersStruct.chanMeanAccumulator[i] / (double)self->metersStruct.chanMeanBlockCount; + } +} + +@end diff --git a/TheAmazingAudioEngine/TheAmazingAudioEngine.h b/TheAmazingAudioEngine/TheAmazingAudioEngine.h index 052f8e4..ed5260c 100644 --- a/TheAmazingAudioEngine/TheAmazingAudioEngine.h +++ b/TheAmazingAudioEngine/TheAmazingAudioEngine.h @@ -52,6 +52,7 @@ extern "C" { #import #import #import +#import #if TARGET_OS_IPHONE #import #endif @@ -350,4 +351,4 @@ extern "C" { #ifdef __cplusplus } -#endif \ No newline at end of file +#endif