// // AudioAnalyzer.m // LEGAudio // // Created by Yamashita Makoto on 04/12/04. // Copyright 2004 Yamashita Makoto. All rights reserved. // #import "AudioAnalyzer.h" @implementation AudioAnalyzer - (id)init { if (self = [super init]) { noiseThreshold = 20.0; wvFrm = (float*)malloc(WaveFormLength * sizeof(float)); freq = (float*)malloc(WaveFormLengthOv2 * sizeof(float)); modZ = (float*)malloc(100 * sizeof(float)); setupReal = create_fftsetup(WaveFormLog, FFT_RADIX2); // prepare fft coefficients intr.realp = (float*)malloc(WaveFormLengthOv2 * sizeof(float)); intr.imagp = (float*)malloc(WaveFormLengthOv2 * sizeof(float)); if (NO == [self setupAudio]) { [self release]; self = nil; } } return self; } - (void)dealloc { if (inputUnit) AudioUnitUninitialize(inputUnit); // waveform (time domain and frequency domain) buffers free(wvFrm); free(freq); // FFT utility buffers destroy_fftsetup(setupReal); free(intr.realp); free(intr.imagp); } // core audio - (BOOL)setupAudio { Component devComp; ComponentDescription compDesc; OSStatus err; UInt32 enableIO, size, i, bufferSizeFrames, bufferSizeBytes; Float64 rate; AURenderCallbackStruct inputCB; NSString *errStr; //There are several different types of Audio Units. //Some audio units serve as Outputs, Mixers, or DSP //units. See AUComponent.h for listing //Every Component has a subType, which will give a clearer picture //of what this components function will be. //all Audio Units in AUComponent.h must use //"kAudioUnitManufacturer_Apple" as the Manufacturer compDesc.componentType = kAudioUnitType_Output; compDesc.componentSubType = kAudioUnitSubType_HALOutput; compDesc.componentManufacturer = kAudioUnitManufacturer_Apple; compDesc.componentFlags = 0; compDesc.componentFlagsMask = 0; //Finds a component that meets the desc spec's devComp = FindNextComponent(NULL, &compDesc); if (devComp == NULL) return NO; OpenAComponent(devComp, &inputUnit); //ENABLE IO (INPUT) //You must enable the Audio Unit (AUHAL) for input and disable output //BEFORE setting the AUHAL's current device. //Enable input on the AUHAL enableIO = 1; err = AudioUnitSetProperty(inputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, // input element &enableIO, sizeof(enableIO)); if (err) { errStr = @"enabling input of inputUnit"; goto ErrHandle; } //disable Output on the AUHAL enableIO = 0; err = AudioUnitSetProperty(inputUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, //output element &enableIO, sizeof(enableIO)); if (err) { errStr = @"disabling output of inputUnit"; goto ErrHandle; } // use default input device size = sizeof(AudioDeviceID); err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &size, &inDevID); if (err) { errStr = @"obtaining the default input device"; goto ErrHandle; } //Set the Current Device to the AUHAL. //this should be done only after IO has been enabled on the AUHAL. err = AudioUnitSetProperty(inputUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &inDevID, size); if (err) { errStr = @"associating the default input device to inputUnit as the current device"; goto ErrHandle; } inputCB.inputProc = InputProc; inputCB.inputProcRefCon = (void*)self; err = AudioUnitSetProperty(inputUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &inputCB, sizeof(inputCB)); if (err) { errStr = @"setting input callback"; goto ErrHandle; } err = AudioUnitInitialize(inputUnit); if (err) { errStr = @"initializing inputUnit"; goto ErrHandle; } size = sizeof(UInt32); err = AudioUnitGetProperty(inputUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &bufferSizeFrames, &size); bufferSizeBytes = bufferSizeFrames * sizeof(Float32); //Get the Stream Format (client side) size = sizeof(inputDesc); err = AudioUnitGetProperty(inputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &inputDesc, &size); size = sizeof(Float64); AudioDeviceGetProperty(inDevID, 0, 1, kAudioDevicePropertyNominalSampleRate, &size, &rate); inputDesc.mSampleRate = rate; //Set new stream format to AUHAL size = sizeof(inputDesc); err = AudioUnitSetProperty(inputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &inputDesc, size); inputBuffer = (AudioBufferList *)malloc(offsetof(AudioBufferList, mBuffers[inputDesc.mChannelsPerFrame])); inputBuffer->mNumberBuffers = inputDesc.mChannelsPerFrame; //pre-malloc buffers for AudioBufferLists for(i = 0; i < inputBuffer->mNumberBuffers ; i++) { inputBuffer->mBuffers[i].mNumberChannels = 1; inputBuffer->mBuffers[i].mDataByteSize = bufferSizeBytes; inputBuffer->mBuffers[i].mData = malloc(bufferSizeBytes); } state = Waiting; framesWritten = 0; return YES; ErrHandle: NSLog(@"Error encountered at %, error code %d", errStr, err); if (inputUnit) AudioUnitUninitialize(inputUnit); inputUnit = NULL; state = Error; return NO; } - (void)start { OSStatus err = -1; if (inputUnit && (state == Waiting)) { err = AudioOutputUnitStart(inputUnit); framesWritten = 0; state = WFLogging; } } - (void)stop { AudioOutputUnitStop(inputUnit); state = Waiting; } - (void)analyzeWaveForm { SInt32 stride = 1; NSArray *comps, *sortedNotes; NSNumber *maxComponent; float maxValue; NoteName sevenNotes[7] = {E, F, G, A, B, C, D}; int i; state = FFTRunning; // interleave the wave form ctoz((COMPLEX*)wvFrm, 2, &intr, 1, WaveFormLengthOv2); // fft! (setupReal points to the coefficients table) fft_zrip ( setupReal, &intr, stride, WaveFormLog, FFT_FORWARD ); // The output signal is now in a split real form for (i = 0; i < WaveFormLengthOv2; i++) { freq[i] = hypot(intr.realp[i], intr.imagp[i]); } state = FFTDone; // find maximal comps = [self getNoteComponents]; sortedNotes = [comps sortedArrayUsingSelector:@selector(compare:)]; maxComponent = [sortedNotes lastObject]; maxValue = [maxComponent floatValue]; if (maxValue > 1.5 * [[sortedNotes objectAtIndex:[sortedNotes count] - 2] floatValue] && maxValue > noiseThreshold) { // return the maximal note when it is louder enough than the others dominantNote = sevenNotes[[comps indexOfObject:maxComponent]]; } else { dominantNote = Noise; } } - (NSArray *)getNoteComponents { float freqUnit = 44100.0 / (WaveFormLength * Skip); int i, j, k0, k1, k; float interval, thecmp; NSMutableArray *noteComps = [NSMutableArray arrayWithCapacity:7]; int cent[7] = {36, 44, 61, 78, 94, 3, 19}; // E, F, ..., D freq log_2 * 100 mod 100 (1st & 2nd decimal) //init modZ array for (i = 0; i < 100; i++) modZ[i] = 0; // construct modZ array for (i = 70.0 / freqUnit; (i < 2000.0 / freqUnit) && (i < WaveFormLengthOv2); i++) { interval = log2((i + 1) * freqUnit) - log2(i * freqUnit); // weight for this index k0 = (int)(log2(i * freqUnit) * 100); k1 = (int)(log2((i + 1) * freqUnit) * 100); for (k = k0; k < k1; k++) modZ[k % 100] += pow(freq[i], 2) * interval; } // get E, F, .. component ranging 2*NoteSupport elements for (i = 0; i < 7; i++) { thecmp = 0; for (j = -NoteSupport; j <= NoteSupport; j++) thecmp += modZ[(cent[i] + j) % 100]; [noteComps addObject:[NSNumber numberWithFloat:thecmp]]; } return noteComps; } - (void)setNoiseThreshold:(float)newThreshold { noiseThreshold = newThreshold; } - (void)adjustNoise { noiseThreshold = 4 * [[[[self getNoteComponents] sortedArrayUsingSelector:@selector(compare:)] lastObject] floatValue]; } - (NoteName)dominantNote {return dominantNote;} - (void)reportResult { NSMutableString *result = [NSMutableString stringWithCapacity:0]; int i; if (inputUnit) { [result appendString:[self description]]; for (i = 0; i < WaveFormLength; i++) { [result appendFormat:@"%f\n", wvFrm[i]]; } [result writeToFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/LEGAudio.txt"] atomically:YES]; // frequency result = [NSMutableString stringWithCapacity:0]; for (i = 0; i < WaveFormLengthOv2; i++) { [result appendFormat:@"%f\n", freq[i]]; } [result writeToFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/Frequency.txt"] atomically:YES]; // mod Z array result = [NSMutableString stringWithCapacity:0]; for (i = 0; i < 100; i++) { [result appendFormat:@"%f\n", modZ[i]]; } [result writeToFile:[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/ModZ.txt"] atomically:YES]; } } - (NSString *)description { NSMutableString *desc = [NSMutableString stringWithCapacity:0]; UInt32 propertySize = sizeof(inputDesc); NSString *formatDesc; [desc appendString:[super description]]; AudioUnitGetProperty(inputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &inputDesc, &propertySize); if (inputDesc.mFormatID == kAudioFormatLinearPCM) { formatDesc = @"linear PCM"; } else if (inputDesc.mFormatID == kAudioFormatAC3) { formatDesc = @"AC3"; } else if (inputDesc.mFormatID == kAudioFormat60958AC3) { formatDesc = @"60958AC3"; } else { formatDesc = @"unknown"; } [desc appendFormat:@"\n** Input Device Info **\nDevice ID: %d\nSampleRate: %lf\nFormat: %@\nFormatFlags: %d\nBytesPerPacket: %d\nFramesPerPacket: %d\nBytesPerFrame: %d\nChannelsPerFrame: %d\nBitsPerChannel: %d\nReserved: %d\n", inDevID, inputDesc.mSampleRate, formatDesc, inputDesc.mFormatFlags, inputDesc.mBytesPerPacket, inputDesc.mFramesPerPacket, inputDesc.mBytesPerFrame, inputDesc.mChannelsPerFrame, inputDesc.mBitsPerChannel, inputDesc.mReserved]; [desc appendFormat:@"** Record Style **\nLog every %d samples\nBuffer size : 2^%d\n", Skip, WaveFormLog]; return desc; } // input device callback - (OSStatus)inputWithActionFlags:(AudioUnitRenderActionFlags*)ioActionFlags timeStamp:(const AudioTimeStamp *)inTimeStamp busNumber:(UInt32)inBusNumber numberFrames:(UInt32)inNumberFrames data:(AudioBufferList*)ioData { OSStatus err; UInt32 i; Float32 *theFrame; err= AudioUnitRender(inputUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, // the number of frames requested inputBuffer); theFrame = inputBuffer->mBuffers[0].mData; for (i = 0; i < inNumberFrames && framesWritten < WaveFormLength; i++) { if (0 == (i % Skip)) { wvFrm[framesWritten++] = (float)theFrame[i]; } } if (framesWritten >= WaveFormLength) { // stop logging [self analyzeWaveForm]; framesWritten = 0; state = WFLogging; } return err; } @end // input device unit data proc, just forwards the arguments OSStatus InputProc( void * inRefCon, AudioUnitRenderActionFlags * ioActionFlags, const AudioTimeStamp * inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData ) { AudioAnalyzer *theAnalyzer = (AudioAnalyzer *)inRefCon; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; OSStatus res; res = [theAnalyzer inputWithActionFlags:ioActionFlags timeStamp:inTimeStamp busNumber:inBusNumber numberFrames:inNumberFrames data:ioData]; [pool release]; return res; }