/* TunerApp.m -- copyright 1992 by C.D.Lane */

#import "TunerApp.h"

#import <dsp/arrayproc.h>

#define APPLICATION "Tuner"
#define VERSION __DATE__

#define RTF_SEGMENT "__RTF"
#define HELP_SECTION "Tuner.rtf"

#define freq(k) MKKeyNumToFreq(k)
#define keyNum(f) MKFreqToKeyNum(f, NULL, 0.0)

#define writeDefault(n, v) NXWriteDefault(APPLICATION, n, v)
#define getStringDefault(n) NXGetDefaultValue(APPLICATION, n)
#define getIntDefault(n) atoi(NXGetDefaultValue(APPLICATION, n))
#define getFloatDefault(n) atof(NXGetDefaultValue(APPLICATION, n))

#define TONES (12)

#define RECORDDELAY (0.25)

#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)];
}

- appDidInit:sender
{
	[defaults registerDefaults:APPLICATION];

	[[[[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;
}

- 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;
}

- windowDidBecomeKey:sender
{
	int size;
	void *pointer;
	NXStream *stream;
	static BOOL flag = NO;

	if(flag) return self;

	if((pointer = getsectdata(RTF_SEGMENT, HELP_SECTION, &size)) == NULL) return nil;

	if((stream = NXOpenMemory((const char *) pointer, size, NX_READONLY)) == NULL) return nil;

	[[helpScrollView docView] readRichText:stream];

	NXCloseMemory(stream, NX_FREEBUFFER);

	flag = YES;

	return self;
}

- computeFrequency
{
	short *pointer = (short *) [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 *) [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(APPLICATION, 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

@implementation Sound(SimpleMethods)

- (int)	convertToFormat:(int) aFormat
{
	return [self convertToFormat:aFormat samplingRate:[self samplingRate] channelCount:[self channelCount]];
}

@end

@implementation NXStringTable(DefaultsTable)

- registerDefaults:(const char *) owner
{
	if(![self applyToDefaults:owner function:&NXRegisterDefaults]) return nil;

	return self;
}

- writeDefaults:(const char *) owner
{
	if(![self applyToDefaults:owner function:&NXWriteDefaults]) return nil;

	return self;
}

- updateDefaults
{
	NXUpdateDefaults();

	return self;
}

- (int) applyToDefaults:(const char *) owner function:(int (*)(const char *, const NXDefaultsVector)) routine
{
	int i, status;
	const void *key, *value;
	struct _NXDefault *vector;
	NXHashState state = [self initState];

	if((vector = (struct _NXDefault *) calloc((size_t) ([self count] + 1), sizeof(struct _NXDefault))) == NULL) perror("calloc");

	for(i = 0; [self nextState:&state key:&key value:&value]; i++) {
		 vector[i].name = (char *) key;
		 vector[i].value = (char *) value;
		 }

	vector[i].name = vector[i].value = NULL;

	status = (*routine)(owner, vector);

	cfree(vector);

	return status;
}

@end
