Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Relevant replacement documents include:
Classes/aurioTouchAppDelegate.mm
/* |
File: aurioTouchAppDelegate.mm |
Abstract: n/a |
Version: 1.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) 2011 Apple Inc. All Rights Reserved. |
*/ |
#import "aurioTouchAppDelegate.h" |
#import "AudioUnit/AudioUnit.h" |
#import "CAXException.h" |
@implementation aurioTouchAppDelegate |
// 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., |
}; |
@synthesize window; |
@synthesize view; |
@synthesize rioUnit; |
@synthesize unitIsRunning; |
@synthesize unitHasBeenCreated; |
@synthesize displayMode; |
@synthesize fftBufferManager; |
@synthesize mute; |
@synthesize inputProc; |
#pragma mark- |
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() |
{ |
// Cycle the lines in our draw buffer so that they age and fade. The oldest line is discarded. |
int drawBuffer_i; |
for (drawBuffer_i=(kNumDrawBuffers - 2); drawBuffer_i>=0; drawBuffer_i--) |
memmove(drawBuffers[drawBuffer_i + 1], drawBuffers[drawBuffer_i], drawBufferLen); |
} |
#pragma mark -Audio Session Interruption Listener |
void rioInterruptionListener(void *inClientData, UInt32 inInterruption) |
{ |
try { |
printf("Session interrupted! --- %s ---", inInterruption == kAudioSessionBeginInterruption ? "Begin Interruption" : "End Interruption"); |
aurioTouchAppDelegate *THIS = (aurioTouchAppDelegate*)inClientData; |
if (inInterruption == kAudioSessionEndInterruption) { |
// make sure we are again the active session |
XThrowIfError(AudioSessionSetActive(true), "couldn't set audio session active"); |
XThrowIfError(AudioOutputUnitStart(THIS->rioUnit), "couldn't start unit"); |
} |
if (inInterruption == kAudioSessionBeginInterruption) { |
XThrowIfError(AudioOutputUnitStop(THIS->rioUnit), "couldn't stop unit"); |
} |
} catch (CAXException e) { |
char buf[256]; |
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf)); |
} |
} |
#pragma mark -Audio Session Property Listener |
void propListener( void * inClientData, |
AudioSessionPropertyID inID, |
UInt32 inDataSize, |
const void * inData) |
{ |
aurioTouchAppDelegate *THIS = (aurioTouchAppDelegate*)inClientData; |
if (inID == kAudioSessionProperty_AudioRouteChange) |
{ |
try { |
UInt32 isAudioInputAvailable; |
UInt32 size = sizeof(isAudioInputAvailable); |
XThrowIfError(AudioSessionGetProperty(kAudioSessionProperty_AudioInputAvailable, &size, &isAudioInputAvailable), "couldn't get AudioSession AudioInputAvailable property value"); |
if(THIS->unitIsRunning && !isAudioInputAvailable) |
{ |
XThrowIfError(AudioOutputUnitStop(THIS->rioUnit), "couldn't stop unit"); |
THIS->unitIsRunning = false; |
} |
else if(!THIS->unitIsRunning && isAudioInputAvailable) |
{ |
XThrowIfError(AudioSessionSetActive(true), "couldn't set audio session active\n"); |
if (!THIS->unitHasBeenCreated) // the rio unit is being created for the first time |
{ |
XThrowIfError(SetupRemoteIO(THIS->rioUnit, THIS->inputProc, THIS->thruFormat), "couldn't setup remote i/o unit"); |
THIS->unitHasBeenCreated = true; |
THIS->dcFilter = new DCRejectionFilter[THIS->thruFormat.NumberChannels()]; |
UInt32 maxFPS; |
size = sizeof(maxFPS); |
XThrowIfError(AudioUnitGetProperty(THIS->rioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFPS, &size), "couldn't get the remote I/O unit's max frames per slice"); |
THIS->fftBufferManager = new FFTBufferManager(maxFPS); |
THIS->l_fftData = new int32_t[maxFPS/2]; |
THIS->oscilLine = (GLfloat*)malloc(drawBufferLen * 2 * sizeof(GLfloat)); |
} |
XThrowIfError(AudioOutputUnitStart(THIS->rioUnit), "couldn't start unit"); |
THIS->unitIsRunning = true; |
} |
// we need to rescale the sonogram view's color thresholds for different input |
CFStringRef newRoute; |
size = sizeof(CFStringRef); |
XThrowIfError(AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &size, &newRoute), "couldn't get new audio route"); |
if (newRoute) |
{ |
CFShow(newRoute); |
if (CFStringCompare(newRoute, CFSTR("Headset"), NULL) == kCFCompareEqualTo) // headset plugged in |
{ |
colorLevels[0] = .3; |
colorLevels[5] = .5; |
} |
else if (CFStringCompare(newRoute, CFSTR("Receiver"), NULL) == kCFCompareEqualTo) // headset plugged in |
{ |
colorLevels[0] = 0; |
colorLevels[5] = .333; |
colorLevels[10] = .667; |
colorLevels[15] = 1.0; |
} |
else |
{ |
colorLevels[0] = 0; |
colorLevels[5] = .333; |
colorLevels[10] = .667; |
colorLevels[15] = 1.0; |
} |
} |
} catch (CAXException e) { |
char buf[256]; |
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf)); |
} |
} |
} |
#pragma mark -RIO Render Callback |
static OSStatus PerformThru( |
void *inRefCon, |
AudioUnitRenderActionFlags *ioActionFlags, |
const AudioTimeStamp *inTimeStamp, |
UInt32 inBusNumber, |
UInt32 inNumberFrames, |
AudioBufferList *ioData) |
{ |
aurioTouchAppDelegate *THIS = (aurioTouchAppDelegate *)inRefCon; |
OSStatus err = AudioUnitRender(THIS->rioUnit, ioActionFlags, inTimeStamp, 1, inNumberFrames, ioData); |
if (err) { printf("PerformThru: error %d\n", (int)err); return err; } |
// Remove DC component |
for(UInt32 i = 0; i < ioData->mNumberBuffers; ++i) |
THIS->dcFilter[i].InplaceFilter((Float32*)(ioData->mBuffers[i].mData), inNumberFrames); |
if (THIS->displayMode == aurioTouchDisplayModeOscilloscopeWaveform) |
{ |
// The draw buffer is used to hold a copy of the most recent PCM data to be drawn on the oscilloscope |
if (drawBufferLen != drawBufferLen_alloced) |
{ |
int drawBuffer_i; |
// Allocate our draw buffer if needed |
if (drawBufferLen_alloced == 0) |
for (drawBuffer_i=0; drawBuffer_i<kNumDrawBuffers; drawBuffer_i++) |
drawBuffers[drawBuffer_i] = NULL; |
// Fill the first element in the draw buffer with PCM data |
for (drawBuffer_i=0; drawBuffer_i<kNumDrawBuffers; drawBuffer_i++) |
{ |
drawBuffers[drawBuffer_i] = (SInt8 *)realloc(drawBuffers[drawBuffer_i], drawBufferLen); |
bzero(drawBuffers[drawBuffer_i], drawBufferLen); |
} |
drawBufferLen_alloced = drawBufferLen; |
} |
int i; |
//Convert the floating point audio data to integer (Q7.24) |
err = AudioConverterConvertComplexBuffer(THIS->audioConverter, inNumberFrames, ioData, THIS->drawABL); |
if (err) { printf("AudioConverterConvertComplexBuffer: error %d\n", (int)err); return err; } |
SInt8 *data_ptr = (SInt8 *)(THIS->drawABL->mBuffers[0].mData); |
for (i=0; i<inNumberFrames; i++) |
{ |
if ((i+drawBufferIdx) >= drawBufferLen) |
{ |
cycleOscilloscopeLines(); |
drawBufferIdx = -i; |
} |
drawBuffers[0][i + drawBufferIdx] = data_ptr[2]; |
data_ptr += 4; |
} |
drawBufferIdx += inNumberFrames; |
} |
else if ((THIS->displayMode == aurioTouchDisplayModeSpectrum) || (THIS->displayMode == aurioTouchDisplayModeOscilloscopeFFT)) |
{ |
if (THIS->fftBufferManager == NULL) return noErr; |
if (THIS->fftBufferManager->NeedsNewAudioData()) |
THIS->fftBufferManager->GrabAudioData(ioData); |
} |
if (THIS->mute == YES) { SilenceData(ioData); } |
return err; |
} |
#pragma mark- |
- (void)applicationDidFinishLaunching:(UIApplication *)application |
{ |
// Turn off the idle timer, since this app doesn't rely on constant touch input |
application.idleTimerDisabled = YES; |
// mute should be on at launch |
self.mute = YES; |
displayMode = aurioTouchDisplayModeOscilloscopeWaveform; |
// Initialize our remote i/o unit |
inputProc.inputProc = PerformThru; |
inputProc.inputProcRefCon = self; |
CFURLRef url = NULL; |
try { |
url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFStringRef([[NSBundle mainBundle] pathForResource:@"button_press" ofType:@"caf"]), kCFURLPOSIXPathStyle, false); |
XThrowIfError(AudioServicesCreateSystemSoundID(url, &buttonPressSound), "couldn't create button tap alert sound"); |
CFRelease(url); |
// Initialize and configure the audio session |
XThrowIfError(AudioSessionInitialize(NULL, NULL, rioInterruptionListener, self), "couldn't initialize audio session"); |
UInt32 audioCategory = kAudioSessionCategory_PlayAndRecord; |
XThrowIfError(AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(audioCategory), &audioCategory), "couldn't set audio category"); |
XThrowIfError(AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange, propListener, self), "couldn't set property listener"); |
Float32 preferredBufferSize = .005; |
XThrowIfError(AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(preferredBufferSize), &preferredBufferSize), "couldn't set i/o buffer duration"); |
UInt32 size = sizeof(hwSampleRate); |
XThrowIfError(AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate, &size, &hwSampleRate), "couldn't get hw sample rate"); |
XThrowIfError(AudioSessionSetActive(true), "couldn't set audio session active\n"); |
XThrowIfError(SetupRemoteIO(rioUnit, inputProc, thruFormat), "couldn't setup remote i/o unit"); |
unitHasBeenCreated = true; |
drawFormat.SetAUCanonical(2, false); |
drawFormat.mSampleRate = 44100; |
XThrowIfError(AudioConverterNew(&thruFormat, &drawFormat, &audioConverter), "couldn't setup AudioConverter"); |
dcFilter = new DCRejectionFilter[thruFormat.NumberChannels()]; |
UInt32 maxFPS; |
size = sizeof(maxFPS); |
XThrowIfError(AudioUnitGetProperty(rioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFPS, &size), "couldn't get the remote I/O unit's max frames per slice"); |
fftBufferManager = new FFTBufferManager(maxFPS); |
l_fftData = new int32_t[maxFPS/2]; |
drawABL = (AudioBufferList*) malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer)); |
drawABL->mNumberBuffers = 2; |
for (UInt32 i=0; i<drawABL->mNumberBuffers; ++i) |
{ |
drawABL->mBuffers[i].mData = (SInt32*) calloc(maxFPS, sizeof(SInt32)); |
drawABL->mBuffers[i].mDataByteSize = maxFPS * sizeof(SInt32); |
drawABL->mBuffers[i].mNumberChannels = 1; |
} |
oscilLine = (GLfloat*)malloc(drawBufferLen * 2 * sizeof(GLfloat)); |
XThrowIfError(AudioOutputUnitStart(rioUnit), "couldn't start remote i/o unit"); |
size = sizeof(thruFormat); |
XThrowIfError(AudioUnitGetProperty(rioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &thruFormat, &size), "couldn't get the remote I/O unit's output client format"); |
unitIsRunning = 1; |
} |
catch (CAXException &e) { |
char buf[256]; |
fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf)); |
unitIsRunning = 0; |
if (dcFilter) delete[] dcFilter; |
if (drawABL) |
{ |
for (UInt32 i=0; i<drawABL->mNumberBuffers; ++i) |
free(drawABL->mBuffers[i].mData); |
free(drawABL); |
drawABL = NULL; |
} |
if (url) CFRelease(url); |
} |
catch (...) { |
fprintf(stderr, "An unknown error occurred\n"); |
unitIsRunning = 0; |
if (dcFilter) delete[] dcFilter; |
if (drawABL) |
{ |
for (UInt32 i=0; i<drawABL->mNumberBuffers; ++i) |
free(drawABL->mBuffers[i].mData); |
free(drawABL); |
drawABL = NULL; |
} |
if (url) CFRelease(url); |
} |
// Set ourself as the delegate for the EAGLView so that we get drawing and touch events |
view.delegate = self; |
// Enable multi touch so we can handle pinch and zoom in the oscilloscope |
view.multipleTouchEnabled = YES; |
// 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 = UITextAlignmentCenter; |
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 |
[view setAnimationInterval:1./20.]; |
[view startAnimation]; |
} |
- (void)applicationDidBecomeActive:(UIApplication *)application { |
//start animation now that we're in the foreground |
view.applicationResignedActive = NO; |
[view startAnimation]; |
AudioSessionSetActive(true); |
} |
- (void)applicationWillResignActive:(UIApplication *)application { |
//stop animation before going into background |
view.applicationResignedActive = YES; |
[view stopAnimation]; |
} |
- (void)applicationDidEnterBackground:(UIApplication *)application { |
} |
- (void)applicationWillEnterForeground:(UIApplication *)application { |
} |
- (void)dealloc |
{ |
delete[] dcFilter; |
delete fftBufferManager; |
if (drawABL) |
{ |
for (UInt32 i=0; i<drawABL->mNumberBuffers; ++i) |
free(drawABL->mBuffers[i].mData); |
free(drawABL); |
drawABL = NULL; |
} |
[view release]; |
[window release]; |
free(oscilLine); |
[super dealloc]; |
} |
- (void)setFFTData:(int32_t *)FFTDATA length:(NSUInteger)LENGTH |
{ |
if (LENGTH != fftLength) |
{ |
fftLength = LENGTH; |
fftData = (SInt32 *)(realloc(fftData, LENGTH * sizeof(SInt32))); |
} |
memmove(fftData, FFTDATA, fftLength * sizeof(Float32)); |
hasNewFFTData = YES; |
} |
- (void)createGLTexture:(GLuint *)texName fromCGImage:(CGImageRef)img |
{ |
GLubyte *spriteData = NULL; |
CGContextRef spriteContext; |
GLuint 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, texW, 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)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(texCount, texNames); |
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(); |
glTranslatef(0., 480., 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); |
glTranslatef(5, 0, 0); |
glBindTexture(GL_TEXTURE_2D, sonoTexture); |
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
glTranslatef(99, 0, 0); |
glBindTexture(GL_TEXTURE_2D, mute ? muteOnTexture : muteOffTexture); |
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
glTranslatef(99, 0, 0); |
glBindTexture(GL_TEXTURE_2D, (displayMode == aurioTouchDisplayModeOscilloscopeFFT) ? fftOnTexture : fftOffTexture); |
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
glPopMatrix(); |
} |
if (displayMode == aurioTouchDisplayModeOscilloscopeFFT) |
{ |
if (fftBufferManager->HasNewAudioData()) |
{ |
if (fftBufferManager->ComputeFFT(l_fftData)) |
[self setFFTData:l_fftData length:fftBufferManager->GetNumberFrames() / 2]; |
else |
hasNewFFTData = NO; |
} |
if (hasNewFFTData) |
{ |
int y, maxY; |
maxY = drawBufferLen; |
for (y=0; y<maxY; y++) |
{ |
CGFloat yFract = (CGFloat)y / (CGFloat)(maxY - 1); |
CGFloat fftIdx = yFract * ((CGFloat)fftLength); |
double fftIdx_i, fftIdx_f; |
fftIdx_f = modf(fftIdx, &fftIdx_i); |
SInt8 fft_l, fft_r; |
CGFloat fft_l_fl, fft_r_fl; |
CGFloat interpVal; |
fft_l = (fftData[(int)fftIdx_i] & 0xFF000000) >> 24; |
fft_r = (fftData[(int)fftIdx_i + 1] & 0xFF000000) >> 24; |
fft_l_fl = (CGFloat)(fft_l + 80) / 64.; |
fft_r_fl = (CGFloat)(fft_r + 80) / 64.; |
interpVal = fft_l_fl * (1. - fftIdx_f) + fft_r_fl * fftIdx_f; |
interpVal = CLAMP(0., interpVal, 1.); |
drawBuffers[0][y] = (interpVal * 120); |
} |
cycleOscilloscopeLines(); |
} |
} |
GLfloat *oscilLine_ptr; |
GLfloat max = drawBufferLen; |
SInt8 *drawBuffer_ptr; |
// Alloc an array for our oscilloscope line vertices |
if (resetOscilLine) { |
oscilLine = (GLfloat*)realloc(oscilLine, drawBufferLen * 2 * sizeof(GLfloat)); |
resetOscilLine = NO; |
} |
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.); |
int 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++) / 128.; |
} |
// 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, drawBufferLen); |
} |
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); |
} |
- (void)renderFFTToTex |
{ |
[self cycleSpectrum]; |
UInt32 *texBitBuffer_ptr = texBitBuffer; |
static int numLevels = sizeof(colorLevels) / sizeof(GLfloat) / 5; |
int y, maxY; |
maxY = CGRectGetHeight(spectrumRect); |
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); |
SInt8 fft_l, fft_r; |
CGFloat fft_l_fl, fft_r_fl; |
CGFloat interpVal; |
fft_l = (fftData[(int)fftIdx_i] & 0xFF000000) >> 24; |
fft_r = (fftData[(int)fftIdx_i + 1] & 0xFF000000) >> 24; |
fft_l_fl = (CGFloat)(fft_l + 80) / 64.; |
fft_r_fl = (CGFloat)(fft_r + 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); |
hasNewFFTData = NO; |
} |
- (void)drawSpectrum |
{ |
// Clear the view |
glClear(GL_COLOR_BUFFER_BIT); |
if (fftBufferManager->HasNewAudioData()) |
{ |
if (fftBufferManager->ComputeFFT(l_fftData)) |
{ |
[self setFFTData:l_fftData length:fftBufferManager->GetNumberFrames() / 2]; |
} |
else |
hasNewFFTData = NO; |
} |
if (hasNewFFTData) [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 ((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) && (self.displayMode == aurioTouchDisplayModeOscilloscopeWaveform)) |
{ |
pinchEvent = event; |
NSArray *t = [[event allTouches] allObjects]; |
lastPinchDist = fabs([[t objectAtIndex:0] locationInView:view].x - [[t objectAtIndex:1] locationInView:view].x); |
sampleSizeText.text = [NSString stringWithFormat:@"%i ms", drawBufferLen / (int)(hwSampleRate / 1000.)]; |
[view 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:view].x - [[t objectAtIndex:1] locationInView:view].x); |
// Find out how far we traveled since the last event |
pinchDiff = thisPinchDist - lastPinchDist; |
// Adjust our draw buffer length accordingly, |
drawBufferLen -= 12 * (int)pinchDiff; |
drawBufferLen = CLAMP(kMinDrawSamples, drawBufferLen, kMaxDrawSamples); |
resetOscilLine = YES; |
// and display the size of our oscilloscope window in our overlay view |
sampleSizeText.text = [NSString stringWithFormat:@"%i ms", drawBufferLen / (int)(hwSampleRate / 1000.)]; |
lastPinchDist = thisPinchDist; |
} |
} |
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event |
{ |
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 (self.displayMode == aurioTouchDisplayModeSpectrum) |
{ |
AudioServicesPlaySystemSound(buttonPressSound); |
self.displayMode = aurioTouchDisplayModeOscilloscopeWaveform; |
return; |
} |
UITouch *touch = [touches anyObject]; |
if (unitIsRunning) |
{ |
if (CGRectContainsPoint(CGRectMake(0., 5., 52., 99.), [touch locationInView:view])) // The Sonogram button was touched |
{ |
AudioServicesPlaySystemSound(buttonPressSound); |
if ((self.displayMode == aurioTouchDisplayModeOscilloscopeWaveform) || (self.displayMode == aurioTouchDisplayModeOscilloscopeFFT)) |
{ |
if (!initted_spectrum) [self setupViewForSpectrum]; |
[self clearTextures]; |
self.displayMode = aurioTouchDisplayModeSpectrum; |
} |
} |
else if (CGRectContainsPoint(CGRectMake(0., 104., 52., 99.), [touch locationInView:view])) // The Mute button was touched |
{ |
AudioServicesPlaySystemSound(buttonPressSound); |
self.mute = !(self.mute); |
return; |
} |
else if (CGRectContainsPoint(CGRectMake(0., 203, 52., 99.), [touch locationInView:view])) // The FFT button was touched |
{ |
AudioServicesPlaySystemSound(buttonPressSound); |
self.displayMode = (self.displayMode == aurioTouchDisplayModeOscilloscopeWaveform) ? aurioTouchDisplayModeOscilloscopeFFT : |
aurioTouchDisplayModeOscilloscopeWaveform; |
return; |
} |
} |
} |
@end |
Copyright © 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-12-06