This is TunerApp.m in view mode; [Download] [Up]
/* TunerApp.m -- copyright 1992 by C.D.Lane */ #import "TunerApp.h" #import "DefaultsTable.h" #import <dsp/arrayproc.h> #define VERSION __DATE__ #define HELPFILE "Tuner" #define DEFAULTSFILE "Defaults" #define freq(k) MKKeyNumToFreq(k) #define keyNum(f) MKFreqToKeyNum(f, NULL, 0.0) #define TONES (12) #define RECORDDELAY (0.125) #define FFT_SIZE (1024) #define FFT_DATA DSPAPGetLowestAddressXY() #define FFT_COEF (FFT_DATA + FFT_SIZE) #define FFT_SKIP (1) #define DATA_SKIP (2) #define FFT_IMAG DSPMapPMemY(FFT_DATA) #define FFT_REAL DSPMapPMemX(FFT_DATA) #define SIN_TABLE DSPMapPMemY(FFT_COEF) #define COS_TABLE DSPMapPMemX(FFT_COEF) typedef enum {ZERO = 0, FFT} METHODS; typedef enum {LINEAR = -1, INDEXED = 0} ADDR_MODES; @implementation TunerApp : Application void stopRecord(DPSTimedEntry timedEntry, double now, id self) { [self stop:self]; } void startRecord(DPSTimedEntry timedEntry, double now, id self) { int status; if((status = [self record]) != SND_ERR_NONE) [NXApp printf:"record: %s\n", SNDSoundError(status)]; } + new { char pathnamebuf[MAXPATHLEN]; self = [super new]; bundle = [NXBundle bundleForClass:[self class]]; if ([bundle getPath:pathnamebuf forResource:DEFAULTSFILE ofType:"strings"]) [(defaults = [[DefaultsTable alloc] initFromFile:pathnamebuf]) registerDefaults:[self appName]]; return self; } - appDidInit:sender { [[[[soundView setAutoscale:YES] setDisplayMode:SK_DISPLAY_WAVE] setAutodisplay:YES] setSound:sound]; key = lastKey = c00k; #ifdef DEBUG (void) DSPSetErrorFP(stderr); (void) DSPEnableErrorLog(); #endif [self loadDefaults:sender]; timedEntry = DPSAddTimedEntry(RECORDDELAY, (DPSTimedEntryProc) &startRecord, sound, NX_BASETHRESHOLD); return self; } - appWillTerminate:sender { return [self saveDefaults:sender]; } - free { if(timedEntry != NULL) DPSRemoveTimedEntry(timedEntry); #ifdef DEBUG (void) DSPDSPDisableErrorLog(); #endif return [super free]; } - willRecord:sender { [soundMeter run:sender]; if(timedEntry != NULL) DPSRemoveTimedEntry(timedEntry); timedEntry = DPSAddTimedEntry([timeSlider floatValue], (DPSTimedEntryProc) &stopRecord, sound, NX_BASETHRESHOLD); return self; } - didRecord:sender { id result = nil; int status = SND_ERR_NONE; if(timedEntry != NULL) DPSRemoveTimedEntry(timedEntry); timedEntry = NULL; amplitude = [[soundMeter stop:sender] peakValue]; if((status = [sample copySound:sound]) == SND_ERR_NONE) { if((status = [sound deleteSamples]) == SND_ERR_NONE) { if(amplitude >= [squelchSlider floatValue]) { if((status = [sample convertToFormat:SND_FORMAT_LINEAR_16]) == SND_ERR_NONE) { switch([[methodMatrix selectedCell] tag]) { case FFT : result = [self computeFrequencyViaFFT]; break; case ZERO : default : result = [self computeFrequency]; break; } } else [self printf:"convertToFormat: %s\n", SNDSoundError(status)]; } else [[self clearNoteName] clearCentError]; } else [self printf:"deleteSamples: %s\n", SNDSoundError(status)]; } else [self printf:"copySound: %s\n", SNDSoundError(status)]; if(result != nil) { frequency *= [adjustmentSlider floatValue]; #ifdef DEBUG [self printf:"amplitude = %f\n", amplitude]; [self printf:"frequency = %f\n\n", frequency]; #endif if((key = keyNum(frequency)) % TONES == lastKey) [[self showNoteName] showCentError]; else [[self clearNoteName] clearCentError]; lastKey = key % TONES; } timedEntry = DPSAddTimedEntry(RECORDDELAY, (DPSTimedEntryProc) &startRecord, sound, NX_BASETHRESHOLD); return self; } - openHelpPanel:sender { NXStream *stream; static BOOL flag = NO; char pathnamebuf[MAXPATHLEN]; if(!flag) { if ([bundle getPath:pathnamebuf forResource:HELPFILE ofType:"rtf"]) { if((stream = NXMapFile(pathnamebuf, NX_READONLY)) != NULL) { [helpScrollView readRichText:stream]; NXCloseMemory(stream, NX_FREEBUFFER); flag = YES; } else return nil; } else return nil; } [[helpScrollView window] makeKeyAndOrderFront:sender]; return self; } - computeFrequency { short *pointer = (short *) [(Sound *) sample data]; unsigned int start, end = 0, i = 0, transitions = 0, size = [sample sampleCount]; while(i < size && pointer[end = i++] == 0); while(i < size && !((pointer[end] > 0 && pointer[i] < 0) || (pointer[end] < 0 && pointer[i] > 0))) ++i; end = i++; if(i >= size) return nil; for(start = i; i < size; i++) if((pointer[end] > 0 && pointer[i] < 0) || (pointer[end] < 0 && pointer[i] > 0)) { transitions++; end = i; } if(start > end) return nil; frequency = (transitions * [sample samplingRate]) / (2 * ((end - start) + 1)); return self; } - computeFrequencyViaFFT { unsigned int i; MKKeyNum note, maximum = c00k; float pitch, pitches[b7k], spectrum[FFT_SIZE]; short data[FFT_SIZE], hits[b7k], *pointer = (short *) [(Sound *) sample data]; float threshold = [squelchSlider floatValue], rate = [sample samplingRate] / (FFT_SIZE * DATA_SKIP); if([sample sampleCount] < (FFT_SIZE * DATA_SKIP)) return nil; for(i = 0; i < FFT_SIZE; i++) data[i] = pointer[i * DATA_SKIP]; for(note = c00k; note < b7k; note++) pitches[note] = hits[note] = 0; if(DSPAPInit() == 0) { (void) DSPAPWriteFloatArray(DSPAPSinTable(FFT_SIZE), SIN_TABLE, 1, FFT_SIZE/2); (void) DSPAPWriteFloatArray(DSPAPCosTable(FFT_SIZE), COS_TABLE, 1, FFT_SIZE/2); (void) DSPAPWriteShortArray(data, FFT_REAL, 1, FFT_SIZE); (void) DSPAPvclear(FFT_IMAG, 1, FFT_SIZE); (void) DSPAPfftr2a(FFT_SIZE, FFT_DATA, FFT_COEF); (void) DSPSetDMAReadMReg(INDEXED); { (void) DSPAPReadFloatArray(spectrum, FFT_IMAG, FFT_SIZE/2, FFT_SIZE); } (void) DSPSetDMAReadMReg(LINEAR); (void) DSPAPFree(); } else return [self switchMethod:"Switching methods, DSP not available!"]; for(i = 0; i < FFT_SIZE; i++) if(fabs(spectrum[i]) >= threshold) { note = keyNum(pitch = (i * rate)); pitches[note] += pitch; ++hits[note]; } for(note = c00k; note < b7k; note++) if(hits[note] > hits[maximum]) maximum = note; if(hits[maximum] == 0) return nil; frequency = pitches[maximum] / hits[maximum]; return self; } - showNoteName { unsigned int i, size; id control, list = [buttonMatrix cellList]; size = [list count]; for(i = 0; i < size; i++) { control = [list objectAt:i]; [control setEnabled:(((key + [transpositionField intValue]) % TONES) == [control tag])]; } return self; } - clearNoteName { unsigned int i, size; id list = [buttonMatrix cellList]; size = [list count]; for(i = 0; i < size; i++) [[list objectAt:i] setEnabled:NO]; return self; } - showCentError { double error, zero, minimum, maximum, correct, calibration = [calibrationField floatValue] / freq(a4k); correct = freq(key) * calibration; minimum = freq(key - 1) * calibration; maximum = freq(key + 1) * calibration; [[[centSlider setMinValue:minimum] setMaxValue:maximum] setFloatValue:frequency]; [centSlider setEnabled:YES]; zero = (((error = frequency - correct) > 0.0) ? maximum - correct : correct - minimum) * [toleranceSlider floatValue]; [flat setState:(error <= zero)]; [sharp setState:(error >= -zero)]; [attune setState:([flat state] && [sharp state])]; return self; } - clearCentError { [[[[centSlider setMinValue:freq(af4k)] setMaxValue:freq(as4k)] setFloatValue:freq(a4k)] setEnabled:NO]; [flat setState:NO]; [sharp setState:NO]; [attune setState:NO]; return self; } - switchMethod:(const char *) reason { (void) NXRunAlertPanel([self appName], reason, NULL, NULL, NULL); [methodMatrix selectCellWithTag:ZERO]; return nil; } - setDefault:sender { [sender setTag:YES]; return self; } - setCalibration:sender { [calibrationField setIntValue:[[sender selectedCell] tag]]; [sender setTag:YES]; return self; } - setTransposition:sender { [transpositionField setIntValue:[[sender selectedCell] tag]]; [sender setTag:YES]; return self; } - printf:(const char *) format, ... { va_list ap; va_start(ap, format); { (void) vfprintf(stderr, format, ap); } va_end(ap); return self; } - loadDefaults:sender; { const char *string; unsigned int i, size; id cell, list = [methodMatrix cellList]; [[squelchSlider setTag:NO] setFloatValue:getFloatDefault("Squelch")]; [[timeSlider setTag:NO] setFloatValue:getFloatDefault("SampleTime")]; [[toleranceSlider setTag:NO] setFloatValue:getFloatDefault("Tolerance")]; [[adjustmentSlider setTag:NO] setFloatValue:getFloatDefault("Adjustment")]; [calibrationField setIntValue:getIntDefault("Calibration")]; [[calibrationMatrix setTag:NO] selectCellWithTag:[calibrationField intValue]]; [transpositionField setIntValue:getIntDefault("Transposition")]; [[transpositionMatrix setTag:NO] selectCellWithTag:[transpositionField intValue]]; for(i = 0, size = [list count], string = getStringDefault("Method"); i < size; i++) if(strcmp([(cell = [list objectAt:i]) title], string) == 0) [methodMatrix selectCell:cell]; [methodMatrix setTag:NO]; return self; } - saveDefaults:sender { if([squelchSlider tag]) (void) writeDefault("Squelch", [squelchSlider stringValue]); if([timeSlider tag]) (void) writeDefault("SampleTime", [timeSlider stringValue]); if([toleranceSlider tag]) (void) writeDefault("Tolerance", [toleranceSlider stringValue]); if([adjustmentSlider tag]) (void) writeDefault("Adjustment", [adjustmentSlider stringValue]); if([calibrationMatrix tag]) (void) writeDefault("Calibration", [calibrationField stringValue]); if([transpositionMatrix tag]) (void) writeDefault("Transposition", [transpositionField stringValue]); if([methodMatrix tag]) (void) writeDefault("Method", [[methodMatrix selectedCell] title]); return self; } - resetDefaults:sender { [defaults updateDefaults]; return [self loadDefaults:sender]; } - setVersion:anObject { [(version = anObject) setStringValue:VERSION]; return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.