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

#import "TunerApp.h"
#import "DefaultsTable.h"
#import "PopUpListPatch.h"

#import "Frequency.h"
#import "ZeroFrequency.h"
#import "FFTFrequency.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 MSECPERSEC (1000)

@implementation TunerApp : Application

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

	[methods addObject:[[ZeroFrequency alloc] init]];
	[methods addObject:[[FFTFrequency alloc] init]];
	
	[self loadDefaults:sender];
	
	[sound perform:@selector(record:) with:sender afterDelay:0 cancelPrevious:YES];

	return self;
}

- appWillTerminate:sender { return [self saveDefaults:sender]; }

- willRecord:sender
{
	[soundMeter run:sender];

	[sound perform:@selector(stop:) with:sender afterDelay:([timeSlider floatValue] * MSECPERSEC) cancelPrevious:YES];

	return self;
}

- didRecord:sender
{
	int status = SND_ERR_NONE;
	id list = methodPopUpList;

	amplitude = [[soundMeter stop:sender] peakValue];
	
	frequency = FREQUENCY_UNSTABLE;

	method = [methods objectAt:[list indexOfItem:[list selectedItem]]];
	
	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) frequency = [method computeFrequency:sample];
#ifdef DEBUG
				else (void) fprintf(stderr, "convertToFormat: %s\n", SNDSoundError(status));
#endif
				}
			else [[[self clearNoteName] clearCentError] clearNoteGlyph];
			}
#ifdef DEBUG
		else (void) fprintf(stderr, "deleteSamples: %s\n", SNDSoundError(status));
#endif
		}
#ifdef DEBUG
	else (void) fprintf(stderr, "copySound: %s\n", SNDSoundError(status));;
#endif

	if(frequency == FREQUENCY_ERROR) [self switchMethod:"Switching methods, selected method not available!"];
	else if(frequency != FREQUENCY_UNSTABLE) {
		frequency *= [adjustmentSlider floatValue];
#ifdef DEBUG
		(void) fprintf(stderr, "amplitude = %f\nfrequency = %f\n\n", amplitude, frequency);
#endif
		if((key = keyNum(frequency)) % TONES == lastKey) [[[self showNoteName] showCentError] showNoteGlyph];
		else [[self clearNoteName] clearCentError];

		NXPing();

		lastKey = key % TONES;
		}

	[sound perform:@selector(record:) with:sender afterDelay:0 cancelPrevious:YES];

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

- showNoteName
{
	id control, list = [buttonMatrix cellList];
	unsigned int i, size, state, pitch = ((key + [transpositionField intValue]) % TONES);

	for(i = 0, size = [list count]; i < size; i++) {
		state = (pitch == [(control = [list objectAt:i]) tag]);
		if([control state] != state) [[[control setEnabled:YES] setState:state] setEnabled:NO];
		}

	return self;
}

- clearNoteName
{
	unsigned int i, size;
	id control, list = [buttonMatrix cellList];

	for(i = 0, size = [list count]; i < size; i++) {
		if([(control = [list objectAt:i]) state]) [[[control setEnabled:YES] setState:NO] setEnabled:NO];
		}

	return self;
}

- showCentError
{
	double zero, minimum, maximum, correct, calibration = [calibrationField floatValue] / freq(a4k);
	double error, tolerance = [toleranceSlider floatValue] + [method toleranceAtFrequency:frequency];
	
	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) * tolerance;

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

#import "GlyphTable.h"

- showNoteGlyph
{
	BOOL flag;
	id cell, image;
	struct entry *entry;
	unsigned int i, j, count = [staffMatrix cellCount];
	MKKeyNum index = key + [transpositionField intValue];
	
	if(index < START || index > END) return self;
	
	entry = &entries[i = (unsigned int) (END - index)];
	
	if(entry->clef != NULL && (image = [NXImage findImageNamed:entry->clef]) != nil && [staffButton image] != image) {
		[staffButton setImage:image];
		}
		
	for(j = 0, flag = NO; j < count; j++) {
		if(flag || (flag = (entry->notes)[j] == NULL)) image = [NXImage findImageNamed:STAFF];
		else if((image = [NXImage findImageNamed:(entry->notes)[j]]) == nil) continue;
		if([(cell = [staffMatrix cellAt:0 :j]) image] != image) [cell setImage:image];
		}
	
	return self;
}

- clearNoteGlyph
{
	id image = [NXImage findImageNamed:STAFF];

	[[staffMatrix cellList] makeObjectsPerform:@selector(setImage:) with:image];

	return self;
}

- switchMethod:(const char *) reason
{
	const char *title = "Zero"; // should be [[methods itemAt:0] ...]
	(void) NXRunAlertPanel([self appName], reason, NULL, NULL, NULL);

	[methodButton setTitle:title];
	[methodPopUpList selectItem:title];

	return nil;
}

- setDefault:sender
{
	[sender setTag:YES];

	return self;
}

- setCalibration:sender
{
	[calibrationField setIntValue:[[sender selectedCell] tag]];

	return [self setDefault:sender];
}

- setTransposition:sender
{
	[transpositionField setIntValue:[[sender selectedCell] tag]];

	return [self setDefault:sender];
}

- loadDefaults:sender;
{
	const char *string = getStringDefault("Method");

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

	if ([methodPopUpList indexOfItem:string] != -1) {
    	[methodButton setTitle:string];
    	[methodPopUpList selectItem:string];
		}
	[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];
}

- setMethodPopUpList:anObject
{
	[[(methodPopUpList = anObject) setTarget:self] setAction:@selector(setDefault:)];

	methodMatrix = [methodPopUpList itemList];
	
	return self;
}

- setVersion:anObject
{
	[(version = anObject) setStringValue:VERSION];

	return self;
}

@end
