// // EZAudioPlotGL.m // EZAudio // // Created by Syed Haris Ali on 11/22/13. // Copyright (c) 2015 Syed Haris Ali. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "EZAudioPlotGL.h" #import "EZAudioDisplayLink.h" #import "EZAudioUtilities.h" #import "EZAudioPlot.h" //------------------------------------------------------------------------------ #pragma mark - Data Structures //------------------------------------------------------------------------------ typedef struct { BOOL interpolated; EZPlotHistoryInfo *historyInfo; EZAudioPlotGLPoint *points; UInt32 pointCount; GLuint vbo; GLuint vab; } EZAudioPlotGLInfo; //------------------------------------------------------------------------------ #pragma mark - EZAudioPlotGL (Interface Extension) //------------------------------------------------------------------------------ @interface EZAudioPlotGL () @property (nonatomic, strong) GLKBaseEffect *baseEffect; @property (nonatomic, strong) EZAudioDisplayLink *displayLink; @property (nonatomic, assign) EZAudioPlotGLInfo *info; @end //------------------------------------------------------------------------------ #pragma mark - EZAudioPlotGL (Implementation) //------------------------------------------------------------------------------ @implementation EZAudioPlotGL //------------------------------------------------------------------------------ #pragma mark - Dealloc //------------------------------------------------------------------------------ - (void)dealloc { [self.displayLink stop]; self.displayLink = nil; [EZAudioUtilities freeHistoryInfo:self.info->historyInfo]; #if !TARGET_OS_IPHONE glDeleteVertexArrays(1, &self.info->vab); #endif glDeleteBuffers(1, &self.info->vbo); free(self.info->points); free(self.info); self.baseEffect = nil; } //------------------------------------------------------------------------------ #pragma mark - Initialization //------------------------------------------------------------------------------ - (instancetype)init { self = [super init]; if (self) { [self setup]; } return self; } //------------------------------------------------------------------------------ - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self setup]; } return self; } //------------------------------------------------------------------------------ - (instancetype)initWithFrame:(EZRect)frame { self = [super initWithFrame:frame]; if (self) { [self setup]; } return self; } //------------------------------------------------------------------------------ #if TARGET_OS_IPHONE - (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context { self = [super initWithFrame:frame context:context]; if (self) { [self setup]; } return self; } #elif TARGET_OS_MAC - (instancetype)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat *)format { self = [super initWithFrame:frameRect pixelFormat:format]; if (self) { [self setup]; } return self; } #endif //------------------------------------------------------------------------------ #pragma mark - Setup //------------------------------------------------------------------------------ - (void)setup { // // Setup info data structure // self.info = (EZAudioPlotGLInfo *)malloc(sizeof(EZAudioPlotGLInfo)); memset(self.info, 0, sizeof(EZAudioPlotGLInfo)); // // Create points array // UInt32 pointCount = [self maximumRollingHistoryLength]; self.info->points = (EZAudioPlotGLPoint *)calloc(sizeof(EZAudioPlotGLPoint), pointCount); self.info->pointCount = pointCount; // // Create the history data structure to hold the rolling data // self.info->historyInfo = [EZAudioUtilities historyInfoWithDefaultLength:[self defaultRollingHistoryLength] maximumLength:[self maximumRollingHistoryLength]]; // // Setup OpenGL specific stuff // [self setupOpenGL]; // // Setup view properties // self.gain = 1.0f; #if TARGET_OS_IPHONE self.backgroundColor = [UIColor colorWithRed:0.569f green:0.82f blue:0.478f alpha:1.0f]; self.color = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f]; #elif TARGET_OS_MAC self.backgroundColor = [NSColor colorWithCalibratedRed:0.569f green:0.82f blue:0.478f alpha:1.0f]; self.color = [NSColor colorWithCalibratedRed:1.0f green:1.0f blue:1.0f alpha:1.0f]; #endif // // Create the display link // self.displayLink = [EZAudioDisplayLink displayLinkWithDelegate:self]; [self.displayLink start]; } //------------------------------------------------------------------------------ - (void)setupOpenGL { self.baseEffect = [[GLKBaseEffect alloc] init]; self.baseEffect.useConstantColor = YES; #if TARGET_OS_IPHONE if (!self.context) { self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; } [EAGLContext setCurrentContext:self.context]; self.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; self.drawableDepthFormat = GLKViewDrawableDepthFormat24; self.drawableStencilFormat = GLKViewDrawableStencilFormat8; self.drawableMultisample = GLKViewDrawableMultisample4X; self.opaque = NO; self.enableSetNeedsDisplay = NO; #elif TARGET_OS_MAC self.wantsBestResolutionOpenGLSurface = YES; self.wantsLayer = YES; self.layer.opaque = YES; self.layer.backgroundColor = [NSColor clearColor].CGColor; if (!self.pixelFormat) { NSOpenGLPixelFormatAttribute attrs[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFAMultisample, NSOpenGLPFASampleBuffers, 1, NSOpenGLPFASamples, 4, NSOpenGLPFADepthSize, 24, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, 0 }; self.pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; } #if DEBUG NSAssert(self.pixelFormat, @"Could not create OpenGL pixel format so context is not valid"); #endif self.openGLContext = [[NSOpenGLContext alloc] initWithFormat:self.pixelFormat shareContext:nil]; GLint swapInt = 1; GLint surfaceOpacity = 0; [self.openGLContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; [self.openGLContext setValues:&surfaceOpacity forParameter:NSOpenGLCPSurfaceOpacity]; [self.openGLContext lock]; glGenVertexArrays(1, &self.info->vab); glBindVertexArray(self.info->vab); #endif glGenBuffers(1, &self.info->vbo); glBindBuffer(GL_ARRAY_BUFFER, self.info->vbo); glBufferData(GL_ARRAY_BUFFER, self.info->pointCount * sizeof(EZAudioPlotGLPoint), self.info->points, GL_STREAM_DRAW); #if !TARGET_OS_IPHONE [self.openGLContext unlock]; #endif } //------------------------------------------------------------------------------ #pragma mark - Updating The Plot //------------------------------------------------------------------------------ - (void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize { // // Update history // [EZAudioUtilities appendBuffer:buffer withBufferSize:bufferSize toHistoryInfo:self.info->historyInfo]; // // Convert this data to point data // switch (self.plotType) { case EZPlotTypeBuffer: [self setSampleData:buffer length:bufferSize]; break; case EZPlotTypeRolling: [self setSampleData:self.info->historyInfo->buffer length:self.info->historyInfo->bufferSize]; break; default: break; } } //------------------------------------------------------------------------------ - (void)setSampleData:(float *)data length:(int)length { int pointCount = self.shouldFill ? length * 2 : length; EZAudioPlotGLPoint *points = self.info->points; for (int i = 0; i < length; i++) { if (self.shouldFill) { points[i * 2].x = points[i * 2 + 1].x = i; points[i * 2].y = data[i]; points[i * 2 + 1].y = 0.0f; } else { points[i].x = i; points[i].y = data[i]; } } points[0].y = points[pointCount - 1].y = 0.0f; self.info->pointCount = pointCount; self.info->interpolated = self.shouldFill; #if !TARGET_OS_IPHONE [self.openGLContext lock]; glBindVertexArray(self.info->vab); #endif glBindBuffer(GL_ARRAY_BUFFER, self.info->vbo); glBufferSubData(GL_ARRAY_BUFFER, 0, pointCount * sizeof(EZAudioPlotGLPoint), self.info->points); #if !TARGET_OS_IPHONE [self.openGLContext unlock]; #endif } //------------------------------------------------------------------------------ #pragma mark - Adjusting History Resolution //------------------------------------------------------------------------------ - (int)rollingHistoryLength { return self.info->historyInfo->bufferSize; } //------------------------------------------------------------------------------ - (int)setRollingHistoryLength:(int)historyLength { self.info->historyInfo->bufferSize = MIN(EZAudioPlotDefaultMaxHistoryBufferLength, historyLength); return self.info->historyInfo->bufferSize; } //------------------------------------------------------------------------------ #pragma mark - Clearing The Plot //------------------------------------------------------------------------------ - (void)clear { float emptyBuffer[1]; emptyBuffer[0] = 0.0f; [self setSampleData:emptyBuffer length:1]; [EZAudioUtilities clearHistoryInfo:self.info->historyInfo]; #if TARGET_OS_IPHONE [self display]; #elif TARGET_OS_MAC [self redraw]; #endif } //------------------------------------------------------------------------------ #pragma mark - Start/Stop Display Link //------------------------------------------------------------------------------ - (void)pauseDrawing { [self.displayLink stop]; } //------------------------------------------------------------------------------ - (void)resumeDrawing { [self.displayLink start]; } //------------------------------------------------------------------------------ #pragma mark - Setters //------------------------------------------------------------------------------ - (void)setBackgroundColor:(id)backgroundColor { _backgroundColor = backgroundColor; if (backgroundColor) { CGColorRef colorRef = [backgroundColor CGColor]; CGFloat red; CGFloat green; CGFloat blue; CGFloat alpha; [EZAudioUtilities getColorComponentsFromCGColor:colorRef red:&red green:&green blue:&blue alpha:&alpha]; // // Note! If you set the alpha to be 0 on mac for a transparent view // the EZAudioPlotGL will make the superview layer-backed to make // sure there is a surface to display itself on (or else you will get // some pretty weird drawing glitches // #if !TARGET_OS_IPHONE if (alpha == 0.0f) { [self.superview setWantsLayer:YES]; } #endif glClearColor(red, green, blue, alpha); } else { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); } } //------------------------------------------------------------------------------ - (void)setColor:(id)color { _color = color; if (color) { CGColorRef colorRef = [color CGColor]; CGFloat red; CGFloat green; CGFloat blue; CGFloat alpha; [EZAudioUtilities getColorComponentsFromCGColor:colorRef red:&red green:&green blue:&blue alpha:&alpha]; self.baseEffect.constantColor = GLKVector4Make(red, green, blue, alpha); } else { self.baseEffect.constantColor = GLKVector4Make(0.0f, 0.0f, 0.0f, 0.0f); } } //------------------------------------------------------------------------------ #pragma mark - Drawing //------------------------------------------------------------------------------ - (void)drawRect:(EZRect)rect { [self redraw]; } //------------------------------------------------------------------------------ - (void)redraw { #if !TARGET_OS_IPHONE [self.openGLContext makeCurrentContext]; [self.openGLContext lock]; #endif [self redrawWithPoints:self.info->points pointCount:self.info->pointCount baseEffect:self.baseEffect vertexBufferObject:self.info->vbo vertexArrayBuffer:self.info->vab interpolated:self.info->interpolated mirrored:self.shouldMirror gain:self.gain]; #if !TARGET_OS_IPHONE [self.openGLContext flushBuffer]; [self.openGLContext unlock]; #endif } //------------------------------------------------------------------------------ - (void)redrawWithPoints:(EZAudioPlotGLPoint *)points pointCount:(UInt32)pointCount baseEffect:(GLKBaseEffect *)baseEffect vertexBufferObject:(GLuint)vbo vertexArrayBuffer:(GLuint)vab interpolated:(BOOL)interpolated mirrored:(BOOL)mirrored gain:(float)gain { glClear(GL_COLOR_BUFFER_BIT); GLenum mode = interpolated ? GL_TRIANGLE_STRIP : GL_LINE_STRIP; float interpolatedFactor = interpolated ? 2.0f : 1.0f; float xscale = 2.0f / ((float)pointCount / interpolatedFactor); float yscale = 1.0f * gain; GLKMatrix4 transform = GLKMatrix4MakeTranslation(-1.0f, 0.0f, 0.0f); transform = GLKMatrix4Scale(transform, xscale, yscale, 1.0f); baseEffect.transform.modelviewMatrix = transform; #if !TARGET_OS_IPHONE glBindVertexArray(vab); #endif glBindBuffer(GL_ARRAY_BUFFER, vbo); [baseEffect prepareToDraw]; glEnableVertexAttribArray(GLKVertexAttribPosition); glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(EZAudioPlotGLPoint), NULL); glDrawArrays(mode, 0, pointCount); if (mirrored) { baseEffect.transform.modelviewMatrix = GLKMatrix4Rotate(transform, M_PI, 1.0f, 0.0f, 0.0f); [baseEffect prepareToDraw]; glDrawArrays(mode, 0, pointCount); } } //------------------------------------------------------------------------------ #pragma mark - Subclass //------------------------------------------------------------------------------ - (int)defaultRollingHistoryLength { return EZAudioPlotDefaultHistoryBufferLength; } //------------------------------------------------------------------------------ - (int)maximumRollingHistoryLength { return EZAudioPlotDefaultMaxHistoryBufferLength; } //------------------------------------------------------------------------------ #pragma mark - EZAudioDisplayLinkDelegate //------------------------------------------------------------------------------ - (void)displayLinkNeedsDisplay:(EZAudioDisplayLink *)displayLink { #if TARGET_OS_IPHONE if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateActive) { [self display]; } #elif TARGET_OS_MAC [self redraw]; #endif } //------------------------------------------------------------------------------ @end