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.