Classes/EAGLView.mm

/*
 
     File: EAGLView.mm
 Abstract: n/a
  Version: 2.0
 
 Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
 Inc. ("Apple") in consideration of your agreement to the following
 terms, and your use, installation, modification or redistribution of
 this Apple software constitutes acceptance of these terms.  If you do
 not agree with these terms, please do not use, install, modify or
 redistribute this Apple software.
 
 In consideration of your agreement to abide by the following terms, and
 subject to these terms, Apple grants you a personal, non-exclusive
 license, under Apple's copyrights in this original Apple software (the
 "Apple Software"), to use, reproduce, modify and redistribute the Apple
 Software, with or without modifications, in source and/or binary forms;
 provided that if you redistribute the Apple Software in its entirety and
 without modifications, you must retain this notice and the following
 text and disclaimers in all such redistributions of the Apple Software.
 Neither the name, trademarks, service marks or logos of Apple Inc. may
 be used to endorse or promote products derived from the Apple Software
 without specific prior written permission from Apple.  Except as
 expressly stated in this notice, no other rights or licenses, express or
 implied, are granted by Apple herein, including but not limited to any
 patent rights that may be infringed by your derivative works or by other
 works in which the Apple Software may be incorporated.
 
 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
 
 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
 
 Copyright (C) 2014 Apple Inc. All Rights Reserved.
 
 
 */
 
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
 
#import "EAGLView.h"
#import "BufferManager.h"
 
 
#define USE_DEPTH_BUFFER 1
#define SPECTRUM_BAR_WIDTH 4
 
 
#ifndef CLAMP
#define CLAMP(min,x,max) (x < min ? min : (x > max ? max : x))
#endif
 
 
// value, a, r, g, b
GLfloat colorLevels[] = {
    0., 1., 0., 0., 0.,
    .333, 1., .7, 0., 0.,
    .667, 1., 0., 0., 1.,
    1., 1., 0., 1., 1.,
};
 
#define kMinDrawSamples 64
#define kMaxDrawSamples 4096
 
 
typedef struct SpectrumLinkedTexture {
    GLuint                          texName;
    struct SpectrumLinkedTexture    *nextTex;
} SpectrumLinkedTexture;
 
 
typedef enum aurioTouchDisplayMode {
    aurioTouchDisplayModeOscilloscopeWaveform,
    aurioTouchDisplayModeOscilloscopeFFT,
    aurioTouchDisplayModeSpectrum
} aurioTouchDisplayMode;
 
 
 
@interface EAGLView () {
    
    /* The pixel dimensions of the backbuffer */
    GLint backingWidth;
    GLint backingHeight;
    
    EAGLContext *context;
    
    /* OpenGL names for the renderbuffer and framebuffers used to render to this view */
    GLuint viewRenderbuffer, viewFramebuffer;
    
    /* OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) */
    GLuint depthRenderbuffer;
    
    NSTimer                     *animationTimer;
    NSTimeInterval              animationInterval;
    NSTimeInterval              animationStarted;
    
    BOOL                        applicationResignedActive;
    
    UIImageView*                sampleSizeOverlay;
    UILabel*                    sampleSizeText;
    
    BOOL                        initted_oscilloscope, initted_spectrum;
    UInt32*                     texBitBuffer;
    CGRect                      spectrumRect;
    
    GLuint                      bgTexture;
    GLuint                      muteOffTexture, muteOnTexture;
    GLuint                      fftOffTexture, fftOnTexture;
    GLuint                      sonoTexture;
    
    aurioTouchDisplayMode       displayMode;
    
    SpectrumLinkedTexture*      firstTex;
    
    UIEvent*                    pinchEvent;
    CGFloat                     lastPinchDist;
    Float32*                    l_fftData;
    GLfloat*                    oscilLine;
    
    AudioController*            audioController;
    
}
 
- (BOOL)createFramebuffer;
- (void)destroyFramebuffer;
- (void)setupView;
- (void)drawView;
- (void)setAnimationInterval:(NSTimeInterval)interval;
 
@end
 
@implementation EAGLView
 
@synthesize applicationResignedActive;
 
// You must implement this
+ (Class) layerClass
{
    return [CAEAGLLayer class];
}
 
//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder
{
    if((self = [super initWithCoder:coder])) {
    
        self.frame = [[UIScreen mainScreen] bounds];
        
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer;
        
        eaglLayer.opaque = YES;
        
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                       [NSNumber numberWithBool:FALSE],
                                       kEAGLDrawablePropertyRetainedBacking,
                                       kEAGLColorFormatRGBA8,
                                       kEAGLDrawablePropertyColorFormat,
                                       nil];
        
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
        
        if(!context || ![EAGLContext setCurrentContext:context] || ![self createFramebuffer]) {
            [self release];
            return nil;
        }
        
        // Enable multi touch so we can handle pinch and zoom in the oscilloscope
        self.multipleTouchEnabled = YES;
        
        audioController = [[AudioController alloc] init];
        l_fftData = (Float32*) calloc([audioController getBufferManagerInstance]->GetFFTOutputBufferLength(), sizeof(Float32));
        
        oscilLine = (GLfloat*)malloc(kDefaultDrawSamples * 2 * sizeof(GLfloat));
 
        animationInterval = 1.0 / 60.0;
              
        [self setupView];
        [self drawView];
        
        displayMode = aurioTouchDisplayModeOscilloscopeWaveform;
        
        // Set up our overlay view that pops up when we are pinching/zooming the oscilloscope
        UIImage *img_ui = nil;
        {
            // Draw the rounded rect for the bg path using this convenience function
            CGPathRef bgPath = CreateRoundedRectPath(CGRectMake(0, 0, 110, 234), 15.);
            
            CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
            // Create the bitmap context into which we will draw
            CGContextRef cxt = CGBitmapContextCreate(NULL, 110, 234, 8, 4*110, cs, kCGImageAlphaPremultipliedFirst);
            CGContextSetFillColorSpace(cxt, cs);
            CGFloat fillClr[] = {0., 0., 0., 0.7};
            CGContextSetFillColor(cxt, fillClr);
            // Add the rounded rect to the context...
            CGContextAddPath(cxt, bgPath);
            // ... and fill it.
            CGContextFillPath(cxt);
            
            // Make a CGImage out of the context
            CGImageRef img_cg = CGBitmapContextCreateImage(cxt);
            // Make a UIImage out of the CGImage
            img_ui = [UIImage imageWithCGImage:img_cg];
            
            // Clean up
            CGImageRelease(img_cg);
            CGColorSpaceRelease(cs);
            CGContextRelease(cxt);
            CGPathRelease(bgPath);
        }
        
        // Create the image view to hold the background rounded rect which we just drew
        sampleSizeOverlay = [[UIImageView alloc] initWithImage:img_ui];
        sampleSizeOverlay.frame = CGRectMake(190, 124, 110, 234);
        
        // Create the text view which shows the size of our oscilloscope window as we pinch/zoom
        sampleSizeText = [[UILabel alloc] initWithFrame:CGRectMake(-62, 0, 234, 234)];
        sampleSizeText.textAlignment = NSTextAlignmentCenter;
        sampleSizeText.textColor = [UIColor whiteColor];
        sampleSizeText.text = @"0000 ms";
        sampleSizeText.font = [UIFont boldSystemFontOfSize:36.];
        // Rotate the text view since we want the text to draw top to bottom (when the device is oriented vertically)
        sampleSizeText.transform = CGAffineTransformMakeRotation(M_PI_2);
        sampleSizeText.backgroundColor = [UIColor clearColor];
        
        // Add the text view as a subview of the overlay BG
        [sampleSizeOverlay addSubview:sampleSizeText];
        // Text view was retained by the above line, so we can release it now
        [sampleSizeText release];
        
        // We don't add sampleSizeOverlay to our main view yet. We just hang on to it for now, and add it when we
        // need to display it, i.e. when a user starts a pinch/zoom.
        
        // Set up the view to refresh at 20 hz
        [self setAnimationInterval:1./20.];
        [self startAnimation];
    }
    
    return self;
}
 
- (void)layoutSubviews
{
    [EAGLContext setCurrentContext:context];
    [self destroyFramebuffer];
    [self createFramebuffer];
    [self drawView];
}
 
- (BOOL)createFramebuffer
{
    glGenFramebuffersOES(1, &viewFramebuffer);
    glGenRenderbuffersOES(1, &viewRenderbuffer);
    
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
    
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
    
    if(USE_DEPTH_BUFFER) {
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
    }
    
    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }
    
    return YES;
}
 
 
- (void)destroyFramebuffer
{
    glDeleteFramebuffersOES(1, &viewFramebuffer);
    viewFramebuffer = 0;
    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
    viewRenderbuffer = 0;
    
    if(depthRenderbuffer) {
        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
        depthRenderbuffer = 0;
    }
}
 
 
- (void)startAnimation
{
    animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
    animationStarted = [NSDate timeIntervalSinceReferenceDate];
    [audioController startIOUnit];
}
 
 
- (void)stopAnimation
{
    [animationTimer invalidate];
    animationTimer = nil;
    [audioController stopIOUnit];
}
 
 
- (void)setAnimationInterval:(NSTimeInterval)interval
{
    animationInterval = interval;
    
    if(animationTimer) {
        [self stopAnimation];
        [self startAnimation];
    }
}
 
 
- (void)setupView
{
    // Sets up matrices and transforms for OpenGL ES
    glViewport(0, 0, backingWidth, backingHeight);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrthof(0, backingWidth, 0, backingHeight, -1.0f, 1.0f);
    glMatrixMode(GL_MODELVIEW);
    
    // Clears the view with black
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    
    glEnableClientState(GL_VERTEX_ARRAY);
    
}
 
 
// Updates the OpenGL view when the timer fires
- (void)drawView
{
    // the NSTimer seems to fire one final time even though it's been invalidated
    // so just make sure and not draw if we're resigning active
    if (self.applicationResignedActive) return;
    
    // Make sure that you are drawing to the current context
    [EAGLContext setCurrentContext:context];
    
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    
    [self drawView:self forTime:([NSDate timeIntervalSinceReferenceDate] - animationStarted)];
    
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
 
 
- (void)setupViewForOscilloscope
{
    CGImageRef img;
    
    // Load our GL textures
    
    img = [UIImage imageNamed:@"oscilloscope.png"].CGImage;
    
    [self createGLTexture:&bgTexture fromCGImage:img];
    
    img = [UIImage imageNamed:@"fft_off.png"].CGImage;
    [self createGLTexture:&fftOffTexture fromCGImage:img];
    
    img = [UIImage imageNamed:@"fft_on.png"].CGImage;
    [self createGLTexture:&fftOnTexture fromCGImage:img];
    
    img = [UIImage imageNamed:@"mute_off.png"].CGImage;
    [self createGLTexture:&muteOffTexture fromCGImage:img];
    
    img = [UIImage imageNamed:@"mute_on.png"].CGImage;
    [self createGLTexture:&muteOnTexture fromCGImage:img];
    
    img = [UIImage imageNamed:@"sonogram.png"].CGImage;
    [self createGLTexture:&sonoTexture fromCGImage:img];
    
    initted_oscilloscope = YES;
}
 
 
- (void)clearTextures
{
    bzero(texBitBuffer, sizeof(UInt32) * 512);
    SpectrumLinkedTexture *curTex;
    
    for (curTex = firstTex; curTex; curTex = curTex->nextTex)
    {
        glBindTexture(GL_TEXTURE_2D, curTex->texName);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, texBitBuffer);
    }
}
 
- (void)setupViewForSpectrum
{
    glClearColor(0., 0., 0., 0.);
    
    spectrumRect = CGRectMake(10., 10., 460., 300.);
    
    // The bit buffer for the texture needs to be 512 pixels, because OpenGL textures are powers of
    // two in either dimensions. Our texture is drawing a strip of 300 vertical pixels on the screen,
    // so we need to step up to 512 (the nearest power of 2 greater than 300).
    texBitBuffer = (UInt32 *)(malloc(sizeof(UInt32) * 512));
    
    // Clears the view with black
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    NSUInteger texCount = ceil(CGRectGetWidth(spectrumRect) / (CGFloat)SPECTRUM_BAR_WIDTH);
    GLuint *texNames;
    
    texNames = (GLuint *)(malloc(sizeof(GLuint) * texCount));
    glGenTextures((int)texCount, texNames);
    
    unsigned int i;
    SpectrumLinkedTexture *curTex = NULL;
    firstTex = (SpectrumLinkedTexture *)(calloc(1, sizeof(SpectrumLinkedTexture)));
    firstTex->texName = texNames[0];
    curTex = firstTex;
    
    bzero(texBitBuffer, sizeof(UInt32) * 512);
    
    glBindTexture(GL_TEXTURE_2D, curTex->texName);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
    for (i=1; i<texCount; i++)
    {
        curTex->nextTex = (SpectrumLinkedTexture *)(calloc(1, sizeof(SpectrumLinkedTexture)));
        curTex = curTex->nextTex;
        curTex->texName = texNames[i];
        
        glBindTexture(GL_TEXTURE_2D, curTex->texName);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    }
    
    // Enable use of the texture
    glEnable(GL_TEXTURE_2D);
    // Set a blending function to use
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    // Enable blending
    glEnable(GL_BLEND);
    
    initted_spectrum = YES;
    
    free(texNames);
}
 
- (void)drawOscilloscope
{
    // Clear the view
    glClear(GL_COLOR_BUFFER_BIT);
    
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    
    glColor4f(1., 1., 1., 1.);
    
    glPushMatrix();
    
    // xy coord. offset for various devices
    float offsetY = (self.bounds.size.height - 480) / 2;
    float offsetX = (self.bounds.size.width - 320) / 2;
    
    glTranslatef(offsetX, 480 + offsetY, 0.);
    glRotatef(-90., 0., 0., 1.);
    
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    {
        // Draw our background oscilloscope screen
        const GLfloat vertices[] = {
            0., 0.,
            512., 0.,
            0.,  512.,
            512.,  512.,
        };
        const GLshort texCoords[] = {
            0, 0,
            1, 0,
            0, 1,
            1, 1,
        };
        
        
        glBindTexture(GL_TEXTURE_2D, bgTexture);
        
        glVertexPointer(2, GL_FLOAT, 0, vertices);
        glTexCoordPointer(2, GL_SHORT, 0, texCoords);
        
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }
    
    {
        // Draw our buttons
        const GLfloat vertices[] = {
            0., 0.,
            112, 0.,
            0.,  64,
            112,  64,
        };
        const GLshort texCoords[] = {
            0, 0,
            1, 0,
            0, 1,
            1, 1,
        };
        
        glPushMatrix();
        
        glVertexPointer(2, GL_FLOAT, 0, vertices);
        glTexCoordPointer(2, GL_SHORT, 0, texCoords);
        
        // button coords
        glTranslatef(15 + offsetX, 0, 0);
        glBindTexture(GL_TEXTURE_2D, sonoTexture);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        glTranslatef(90 + offsetX, 0, 0);
        glBindTexture(GL_TEXTURE_2D, audioController.muteAudio ? muteOnTexture : muteOffTexture);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        glTranslatef(105 + offsetX, 0, 0);
        glBindTexture(GL_TEXTURE_2D, (displayMode == aurioTouchDisplayModeOscilloscopeFFT) ? fftOnTexture : fftOffTexture);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        
        glPopMatrix();
        
    }
    
    BufferManager* bufferManager = [audioController getBufferManagerInstance];
    Float32** drawBuffers = bufferManager->GetDrawBuffers();
    if (displayMode == aurioTouchDisplayModeOscilloscopeFFT)
    {
        if (bufferManager->HasNewFFTData())
        {
            bufferManager->GetFFTOutput(l_fftData);
        
            int y, maxY;
            maxY = bufferManager->GetCurrentDrawBufferLength();
            int fftLength = bufferManager->GetFFTOutputBufferLength();
            for (y=0; y<maxY; y++)
            {
                CGFloat yFract = (CGFloat)y / (CGFloat)(maxY - 1);
                CGFloat fftIdx = yFract * ((CGFloat)fftLength - 1);
                
                double fftIdx_i, fftIdx_f;
                fftIdx_f = modf(fftIdx, &fftIdx_i);
                
                CGFloat fft_l_fl, fft_r_fl;
                CGFloat interpVal;
                
                int lowerIndex = (int) fftIdx_i;
                int upperIndex = (int) fftIdx_i + 1;
                upperIndex = (upperIndex == fftLength) ? fftLength - 1 : upperIndex;
                
                fft_l_fl = (CGFloat)(l_fftData[lowerIndex] + 80) / 64.;
                fft_r_fl = (CGFloat)(l_fftData[upperIndex] + 80) / 64.;
                interpVal = fft_l_fl * (1. - fftIdx_f) + fft_r_fl * fftIdx_f;
                
                drawBuffers[0][y] = CLAMP(0., interpVal, 1.);
            }
            [self cycleOscilloscopeLines];
        }
    }
    
    GLfloat *oscilLine_ptr;
    GLfloat max = kDefaultDrawSamples; //bufferManager->GetCurrentDrawBufferLength();
    Float32 *drawBuffer_ptr;
        
    glPushMatrix();
    
    // Translate to the left side and vertical center of the screen, and scale so that the screen coordinates
    // go from 0 to 1 along the X, and -1 to 1 along the Y
    glTranslatef(17., 182., 0.);
    glScalef(448., 116., 1.);
    
    // Set up some GL state for our oscilloscope lines
    glDisable(GL_TEXTURE_2D);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    glDisable(GL_LINE_SMOOTH);
    glLineWidth(2.);
    
    UInt32 drawBuffer_i;
    // Draw a line for each stored line in our buffer (the lines are stored and fade over time)
    for (drawBuffer_i=0; drawBuffer_i<kNumDrawBuffers; drawBuffer_i++)
    {
        if (!drawBuffers[drawBuffer_i]) continue;
        
        oscilLine_ptr = oscilLine;
        drawBuffer_ptr = drawBuffers[drawBuffer_i];
        
        GLfloat i;
        // Fill our vertex array with points
        for (i=0.; i<max; i=i+1.)
        {
            *oscilLine_ptr++ = i/max;
            *oscilLine_ptr++ = (Float32)(*drawBuffer_ptr++);
        }
        
        // If we're drawing the newest line, draw it in solid green. Otherwise, draw it in a faded green.
        if (drawBuffer_i == 0)
            glColor4f(0., 1., 0., 1.);
        else
            glColor4f(0., 1., 0., (.24 * (1. - ((GLfloat)drawBuffer_i / (GLfloat)kNumDrawBuffers))));
        
        // Set up vertex pointer,
        glVertexPointer(2, GL_FLOAT, 0, oscilLine);
        
        // and draw the line.
        glDrawArrays(GL_LINE_STRIP, 0, bufferManager->GetCurrentDrawBufferLength());
        
    }
    glPopMatrix();
    glPopMatrix();
}
 
- (void)cycleSpectrum
{
    SpectrumLinkedTexture *newFirst;
    newFirst = (SpectrumLinkedTexture *)calloc(1, sizeof(SpectrumLinkedTexture));
    newFirst->nextTex = firstTex;
    firstTex = newFirst;
    
    SpectrumLinkedTexture *thisTex = firstTex;
    do {
        if (!(thisTex->nextTex->nextTex))
        {
            firstTex->texName = thisTex->nextTex->texName;
            free(thisTex->nextTex);
            thisTex->nextTex = NULL;
        }
        thisTex = thisTex->nextTex;
    } while (thisTex);
}
 
double linearInterp(double valA, double valB, double fract)
{
    return valA + ((valB - valA) * fract);
}
 
 
- (void)renderFFTToTex
{
    [self cycleSpectrum];
    
    UInt32 *texBitBuffer_ptr = texBitBuffer;
    
    static int numLevels = sizeof(colorLevels) / sizeof(GLfloat) / 5;
    
    int y, maxY;
    maxY = CGRectGetHeight(spectrumRect);
    BufferManager* bufferManager = [audioController getBufferManagerInstance];
    int fftLength = bufferManager->GetFFTOutputBufferLength();
    for (y=0; y<maxY; y++)
    {
        CGFloat yFract = (CGFloat)y / (CGFloat)(maxY - 1);
        CGFloat fftIdx = yFract * ((CGFloat)fftLength-1);
        
        double fftIdx_i, fftIdx_f;
        fftIdx_f = modf(fftIdx, &fftIdx_i);
        
        CGFloat fft_l_fl, fft_r_fl;
        CGFloat interpVal;
        
        int lowerIndex = (int)(fftIdx_i);
        int upperIndex = (int)(fftIdx_i + 1);
        upperIndex = (upperIndex == fftLength) ? fftLength - 1 : upperIndex;
        
        fft_l_fl = (CGFloat)(l_fftData[lowerIndex] + 80) / 64.;
        fft_r_fl = (CGFloat)(l_fftData[upperIndex] + 80) / 64.;
        interpVal = fft_l_fl * (1. - fftIdx_f) + fft_r_fl * fftIdx_f;
        
        interpVal = sqrt(CLAMP(0., interpVal, 1.));
        
        UInt32 newPx = 0xFF000000;
        
        int level_i;
        const GLfloat *thisLevel = colorLevels;
        const GLfloat *nextLevel = colorLevels + 5;
        for (level_i=0; level_i<(numLevels-1); level_i++)
        {
            if ( (*thisLevel <= interpVal) && (*nextLevel >= interpVal) )
            {
                double fract = (interpVal - *thisLevel) / (*nextLevel - *thisLevel);
                newPx =
                ((UInt8)(255. * linearInterp(thisLevel[1], nextLevel[1], fract)) << 24)
                |
                ((UInt8)(255. * linearInterp(thisLevel[2], nextLevel[2], fract)) << 16)
                |
                ((UInt8)(255. * linearInterp(thisLevel[3], nextLevel[3], fract)) << 8)
                |
                (UInt8)(255. * linearInterp(thisLevel[4], nextLevel[4], fract))
                ;
                break;
            }
            
            thisLevel+=5;
            nextLevel+=5;
        }
        
        *texBitBuffer_ptr++ = newPx;
    }
    
    glBindTexture(GL_TEXTURE_2D, firstTex->texName);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, texBitBuffer);
}
 
- (void)drawSpectrum
{
    // Clear the view
    glClear(GL_COLOR_BUFFER_BIT);
       
    BufferManager* bufferManager = [audioController getBufferManagerInstance];
    if (bufferManager->HasNewFFTData())
    {
        bufferManager->GetFFTOutput(l_fftData);
        [self renderFFTToTex];
    }
    
    glClear(GL_COLOR_BUFFER_BIT);
    
    glEnable(GL_TEXTURE);
    glEnable(GL_TEXTURE_2D);
    
    glPushMatrix();
    glTranslatef(0., 480., 0.);
    glRotatef(-90., 0., 0., 1.);
    glTranslatef(spectrumRect.origin.x + spectrumRect.size.width, spectrumRect.origin.y, 0.);
    
    GLfloat quadCoords[] = {
        0., 0.,
        SPECTRUM_BAR_WIDTH, 0.,
        0., 512.,
        SPECTRUM_BAR_WIDTH, 512.,
    };
    
    GLshort texCoords[] = {
        0, 0,
        1, 0,
        0, 1,
        1, 1,
    };
    
    glVertexPointer(2, GL_FLOAT, 0, quadCoords);
    glEnableClientState(GL_VERTEX_ARRAY);
    glTexCoordPointer(2, GL_SHORT, 0, texCoords);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    glColor4f(1., 1., 1., 1.);
    
    SpectrumLinkedTexture *thisTex;
    glPushMatrix();
    for (thisTex = firstTex; thisTex; thisTex = thisTex->nextTex)
    {
        glTranslatef(-(SPECTRUM_BAR_WIDTH), 0., 0.);
        glBindTexture(GL_TEXTURE_2D, thisTex->texName);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }
    glPopMatrix();
    glPopMatrix();
    
    glFlush();
    
}
 
 
- (void)drawView:(id)sender forTime:(NSTimeInterval)time
{
    if (![audioController audioChainIsBeingReconstructed])  //hold off on drawing until the audio chain has been reconstructed
    {
        if ((displayMode == aurioTouchDisplayModeOscilloscopeWaveform) || (displayMode == aurioTouchDisplayModeOscilloscopeFFT))
        {
            if (!initted_oscilloscope) [self setupViewForOscilloscope];
            [self drawOscilloscope];
        } else if (displayMode == aurioTouchDisplayModeSpectrum) {
            if (!initted_spectrum) [self setupViewForSpectrum];
            [self drawSpectrum];
        }
    }
}
 
 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // If we're if waveform mode and not currently in a pinch event, and we've got two touches, start a pinch event
    if ((!pinchEvent) && ([[event allTouches] count] == 2) && (displayMode == aurioTouchDisplayModeOscilloscopeWaveform))
    {
        pinchEvent = event;
        NSArray *t = [[event allTouches] allObjects];
        lastPinchDist = fabs([[t objectAtIndex:0] locationInView:self].x - [[t objectAtIndex:1] locationInView:self].x);
        
        double hwSampleRate = [audioController sessionSampleRate];
        BufferManager* bufferManager = [audioController getBufferManagerInstance];
        sampleSizeText.text = [NSString stringWithFormat:@"%lu ms", bufferManager->GetCurrentDrawBufferLength() / (unsigned long)(hwSampleRate / 1000.)];
        [self addSubview:sampleSizeOverlay];
    }
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    // If we are in a pinch event...
    if ((event == pinchEvent) && ([[event allTouches] count] == 2))
    {
        CGFloat thisPinchDist, pinchDiff;
        NSArray *t = [[event allTouches] allObjects];
        thisPinchDist = fabs([[t objectAtIndex:0] locationInView:self].x - [[t objectAtIndex:1] locationInView:self].x);
        
        // Find out how far we traveled since the last event
        pinchDiff = thisPinchDist - lastPinchDist;
        // Adjust our draw buffer length accordingly,
        BufferManager* bufferManager = [audioController getBufferManagerInstance];
        UInt32 drawBufferLen = bufferManager->GetCurrentDrawBufferLength();
        drawBufferLen -= 12 * (int)pinchDiff;
        drawBufferLen = CLAMP(kMinDrawSamples, drawBufferLen, kMaxDrawSamples);
        bufferManager->SetCurrentDrawBufferLength(drawBufferLen);
        
        // and display the size of our oscilloscope window in our overlay view
        double hwSampleRate = [audioController sessionSampleRate];
        sampleSizeText.text = [NSString stringWithFormat:@"%lu ms", drawBufferLen / (unsigned long)(hwSampleRate / 1000.)];
        
        lastPinchDist = thisPinchDist;
    }
}
 
 
CGPathRef CreateRoundedRectPath(CGRect RECT, CGFloat cornerRadius)
{
    CGMutablePathRef        path;
    path = CGPathCreateMutable();
    
    double      maxRad = MAX(CGRectGetHeight(RECT) / 2., CGRectGetWidth(RECT) / 2.);
    
    if (cornerRadius > maxRad) cornerRadius = maxRad;
    
    CGPoint     bl, tl, tr, br;
    
    bl = tl = tr = br = RECT.origin;
    tl.y += RECT.size.height;
    tr.y += RECT.size.height;
    tr.x += RECT.size.width;
    br.x += RECT.size.width;
    
    CGPathMoveToPoint(path, NULL, bl.x + cornerRadius, bl.y);
    CGPathAddArcToPoint(path, NULL, bl.x, bl.y, bl.x, bl.y + cornerRadius, cornerRadius);
    CGPathAddLineToPoint(path, NULL, tl.x, tl.y - cornerRadius);
    CGPathAddArcToPoint(path, NULL, tl.x, tl.y, tl.x + cornerRadius, tl.y, cornerRadius);
    CGPathAddLineToPoint(path, NULL, tr.x - cornerRadius, tr.y);
    CGPathAddArcToPoint(path, NULL, tr.x, tr.y, tr.x, tr.y - cornerRadius, cornerRadius);
    CGPathAddLineToPoint(path, NULL, br.x, br.y + cornerRadius);
    CGPathAddArcToPoint(path, NULL, br.x, br.y, br.x - cornerRadius, br.y, cornerRadius);
    
    CGPathCloseSubpath(path);
    
    CGPathRef               ret;
    ret = CGPathCreateCopy(path);
    CGPathRelease(path);
    return ret;
}
 
 
- (void)cycleOscilloscopeLines
{
    BufferManager* bufferManager = [audioController getBufferManagerInstance];
    
    // Cycle the lines in our draw buffer so that they age and fade. The oldest line is discarded.
    Float32** drawBuffers = bufferManager->GetDrawBuffers();
    for (int drawBuffer_i=(kNumDrawBuffers - 2); drawBuffer_i>=0; drawBuffer_i--)
        memmove(drawBuffers[drawBuffer_i + 1], drawBuffers[drawBuffer_i], bufferManager->GetCurrentDrawBufferLength());
}
 
 
- (void)createGLTexture:(GLuint *)texName fromCGImage:(CGImageRef)img
{
    GLubyte *spriteData = NULL;
    CGContextRef spriteContext;
    size_t imgW, imgH, texW, texH;
    
    imgW = CGImageGetWidth(img);
    imgH = CGImageGetHeight(img);
    
    // Find smallest possible powers of 2 for our texture dimensions
    for (texW = 1; texW < imgW; texW *= 2) ;
    for (texH = 1; texH < imgH; texH *= 2) ;
    
    // Allocated memory needed for the bitmap context
    spriteData = (GLubyte *) calloc(texH, texW * 4);
    // Uses the bitmatp creation function provided by the Core Graphics framework.
    spriteContext = CGBitmapContextCreate(spriteData, texW, texH, 8, texW * 4, CGImageGetColorSpace(img), kCGImageAlphaPremultipliedLast);
    
    // Translate and scale the context to draw the image upside-down (conflict in flipped-ness between GL textures and CG contexts)
    CGContextTranslateCTM(spriteContext, 0., texH);
    CGContextScaleCTM(spriteContext, 1., -1.);
    
    // After you create the context, you can draw the sprite image to the context.
    CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, imgW, imgH), img);
    // You don't need the context at this point, so you need to release it to avoid memory leaks.
    CGContextRelease(spriteContext);
    
    // Use OpenGL ES to generate a name for the texture.
    glGenTextures(1, texName);
    // Bind the texture name.
    glBindTexture(GL_TEXTURE_2D, *texName);
    // Speidfy a 2D texture image, provideing the a pointer to the image data in memory
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLuint)texW, (GLuint)texH, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    // Set the texture parameters to use a minifying filter and a linear filer (weighted average)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
    // Enable use of the texture
    glEnable(GL_TEXTURE_2D);
    // Set a blending function to use
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);
    //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    // Enable blending
    glEnable(GL_BLEND);
    
    free(spriteData);
}
 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    BufferManager* bufferManager = [audioController getBufferManagerInstance];
    if (event == pinchEvent)
    {
        // If our pinch/zoom has ended, nil out the pinchEvent and remove the overlay view
        [sampleSizeOverlay removeFromSuperview];
        pinchEvent = nil;
        return;
    }
    
    // any tap in sonogram view will exit back to the waveform
    if (displayMode == aurioTouchDisplayModeSpectrum)
    {
        [audioController playButtonPressedSound];
        displayMode = aurioTouchDisplayModeOscilloscopeWaveform;
        bufferManager->SetDisplayMode(displayMode);
        return;
    }
    
    // xy coord. offset for various devices
    float offsetY = (self.bounds.size.height - 480) / 2;
    float offsetX = (self.bounds.size.width - 320) / 2;
    
    UITouch *touch = [touches anyObject];
    if (CGRectContainsPoint(CGRectMake(offsetX, 15., 52., 99.), [touch locationInView:self])) // The Sonogram button was touched
    {
        [audioController playButtonPressedSound];
        if ((displayMode == aurioTouchDisplayModeOscilloscopeWaveform) || (displayMode == aurioTouchDisplayModeOscilloscopeFFT))
        {
            if (!initted_spectrum) [self setupViewForSpectrum];
            [self clearTextures];
            displayMode = aurioTouchDisplayModeSpectrum;
            bufferManager->SetDisplayMode(displayMode);
        }
    }
    else if (CGRectContainsPoint(CGRectMake(offsetX, offsetY + 105., 52., 99.), [touch locationInView:self])) // The Mute button was touched
    {
        [audioController playButtonPressedSound];
        audioController.muteAudio = !(audioController.muteAudio);
        return;
    }
    else if (CGRectContainsPoint(CGRectMake(offsetX, offsetY + 210, 52., 99.), [touch locationInView:self])) // The FFT button was touched
    {
        [audioController playButtonPressedSound];
        displayMode = (displayMode == aurioTouchDisplayModeOscilloscopeWaveform) ?  aurioTouchDisplayModeOscilloscopeFFT :
        aurioTouchDisplayModeOscilloscopeWaveform;
        bufferManager->SetDisplayMode(displayMode);
        return;
    }
}
 
// Stop animating and release resources when they are no longer needed.
- (void)dealloc
{
    [self stopAnimation];
    
    if([EAGLContext currentContext] == context) {
        [EAGLContext setCurrentContext:nil];
    }
    
    [context release];
    context = nil;
    
    free(oscilLine);
    
    [super dealloc];
}
 
 
@end