ftp.nice.ch/pub/next/audio/apps/Tuner.N.bs.tar.gz#/Tuner/TunerApp.m

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.