Files
EZAudio/Classes/Interface Components/EZAudioPlotGL.m
2015-07-02 14:57:42 -07:00

533 lines
17 KiB
Objective-C

//
// 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 () <EZAudioDisplayLinkDelegate>
@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