view examples/iAudioDB/AppController.m @ 714:e3087cf8ff14

* Enabled aiff, m4a, mp3 audio
author mas01mj
date Fri, 18 Jun 2010 15:25:30 +0000
parents df850498486e
children 0d1a7e4ed6cf 040f14b5a5fc
line wrap: on
line source
//
//  AppController.m
//  CAMUS
//
//  Created by Mike Jewell on 27/01/2010.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//
#import "AppController.h"
#import <AudioToolbox/AudioFile.h>


@implementation AppController

-(id)init
{
	[super init];
	
	// A max of 100 results.
	results = [[NSMutableArray alloc] initWithCapacity: 100];
	
	return self;
}

- (void)awakeFromNib {
	[tracksView setTarget:self];
	[tracksView setDoubleAction:@selector(tableDoubleClick:)];
	[self updateStatus];
}


- (IBAction)tableDoubleClick:(id)sender
{
	[self playResult:Nil];
//	NSLog(@"Table double clicked");
}
	

/**
 * Create a new database, given the selected filename.
 */
-(IBAction)newDatabase:(id)sender
{
	
	[NSApp beginSheet:createSheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
	session = [NSApp beginModalSessionForWindow:createSheet];
	[NSApp runModalSession:session];	
}

/**
 * Cancel the db creation (at configuration time).
 */
-(IBAction)cancelCreate:(id)sender
{
	[NSApp endModalSession:session];
	[createSheet orderOut:nil];
	[NSApp endSheet:createSheet];
}

-(IBAction)createDatabase:(id)sender
{
	[self cancelCreate:self];
	
	NSSavePanel* panel = [NSSavePanel savePanel];
	[panel setCanSelectHiddenExtension:YES];
	[panel setAllowedFileTypes:[NSArray arrayWithObjects:@"adb", nil]];
	NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@""];
	 
	[results removeAllObjects];
	[tracksView reloadData];
	 
	if(response == NSFileHandlingPanelOKButton)
	{
		// Work out which extractor to use
		NSString* extractor = @"adb_chroma";
		// TODO: This should be stored with the n3.
		int dim;
		switch([extractorOptions selectedTag])
		{
			case 0:
				extractor = @"adb_chroma";
				dim = 12;
				break;
	/*		case 1:
				extractor = @"adb_cq";
				dim = 48;
				break;
			case 2:
				extractor = @"qm_chroma";
				dim = 12;
				break;*/
			case 1:
				extractor = @"qm_mfcc";
				dim = 12;
				break;
		}
		
		// Calculate the max DB size
		NSLog(@"Max length: %f", [maxLengthField doubleValue]);
		NSLog(@"hop size: %f", [hopSizeField doubleValue]);
		int vectors = ceil([maxLengthField doubleValue] / (([hopSizeField doubleValue] / 1000.0f)));
		NSLog(@"Max Vectors: %d", vectors);
		int numtracks = [maxTracksField intValue];
		int datasize = ceil((numtracks * vectors * dim * 8.0f) / 1024.0f / 1024.0f); // In MB
		
		[self reset];
		 
		
		
		// Store useful paths.
		dbName = [[[panel URL] relativePath] retain];
		dbFilename = [[panel filename] retain];
		plistFilename = [[NSString stringWithFormat:@"%@.plist", [dbFilename stringByDeletingPathExtension]] retain];
		
		// Remove any existing files
		NSFileManager *fileManager = [[NSFileManager alloc] init];
		
		BOOL overwriteError = NO;
		
		if([fileManager fileExistsAtPath:[panel filename]])
		{
			if(![fileManager removeItemAtPath:[panel filename] error:NULL])
			{
				overwriteError = YES;
			}
		}
		
		if(!overwriteError && [fileManager fileExistsAtPath:plistFilename])
		{
			if(![fileManager removeItemAtPath:plistFilename error:NULL])
			{
				overwriteError = YES;
			}
		}
		[fileManager release];
		
		if(overwriteError)
		{
			NSAlert *alert = [[NSAlert alloc] init];
			[alert addButtonWithTitle:@"OK"];
			[alert setMessageText:@"Unable to create database."];
			[alert setInformativeText:@"A database with this name already exists, and could not be overwritten."];
			[alert setAlertStyle:NSWarningAlertStyle];
			[alert runModal];
			[alert release];
			return;
		}
		
		// Create new db, and set flags.
		db = audiodb_create([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], datasize, numtracks, dim);
		audiodb_l2norm(db);
			
		// Create the plist file (contains mapping from filename to key).
		dbState = [[NSMutableDictionary alloc] init];
		trackMap = [[NSMutableDictionary alloc] init];
		[dbState setValue:trackMap forKey:@"tracks"];
		[dbState setValue:extractor forKey:@"extractor"];
		[dbState setValue:[hopSizeField stringValue] forKey:@"hopsize"];
		[dbState writeToFile:plistFilename atomically:YES];
			 
		[queryKey setStringValue:@"None Selected"];
		[self updateStatus];
	}
}

-(void)reset
{
	// Tidy any existing references up.
	if(db)
	{
		NSLog(@"Close db");
		audiodb_close(db);
	}
	
	if(dbFilename)
	{
		NSLog(@"Tidy up filenames");
		[dbFilename release];
		[dbName release];
		[plistFilename release];
		[trackMap release];
		[dbState release];
	}
	
	if(selectedKey)
	{
		[selectedKey release];
		selectedKey = Nil;
	}
	
	// Reset query flags
	[queryPath setStringValue: @"No file selected"];
	[queryLengthSeconds setDoubleValue:0];
	[queryLengthVectors setDoubleValue:0];
	[multipleCheckBox setState:NSOnState];
	[queryStartSeconds setDoubleValue:0];
	[queryStartVectors setDoubleValue:0];
	
	[queryLengthSeconds setEnabled:NO];
	[queryLengthVectors setEnabled:NO];
	[queryStartSeconds setEnabled:NO];
	[queryStartVectors setEnabled:NO];
	[resetButton setEnabled:NO];
	[multipleCheckBox setEnabled:NO];
}

/**
 * Open an existing adb (which must have a plist)
 */
-(IBAction)openDatabase:(id)sender
{	
	NSArray *fileTypes = [NSArray arrayWithObject:@"adb"];
	NSOpenPanel* panel = [NSOpenPanel openPanel];
	NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes];
	if(response == NSFileHandlingPanelOKButton)
	{
		[self reset];
		
		// Store useful paths.
		NSLog(@"Open");
		db = audiodb_open([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], O_RDONLY);
		dbName = [[[panel URL] relativePath] retain];
		dbFilename = [[panel filename] retain];
		
		// TODO: Verify this exists!
		plistFilename = [[NSString stringWithFormat:@"%@.plist", [dbFilename stringByDeletingPathExtension]] retain];
		
		// Clear out any old results.
		[results removeAllObjects];
		[tracksView reloadData];
		
		[queryKey setStringValue:@"None Selected"];
		
		adb_liszt_results_t* liszt_results = audiodb_liszt(db);
		
		for(int k=0; k<liszt_results->nresults; k++)
		{
			NSMutableString *trackVal = [[NSMutableString alloc] init];
			[trackVal appendFormat:@"%s", liszt_results->entries[k].key];
		}
		
		audiodb_liszt_free_results(db, liszt_results);
		dbState = [[[NSMutableDictionary alloc] initWithContentsOfFile:plistFilename] retain];
		trackMap = [[dbState objectForKey:@"tracks"] retain];
		
		[self updateStatus];
		
		NSLog(@"Size: %d", [trackMap count]);
	}
}

-(IBAction)pathAction:(id)sender
{
	NSLog(@"Path action");
}

/**
 * Update button states and status field based on current state.
 */
-(void)updateStatus
{
	NSLog(@"Update status");
	if(db)
	{
		NSLog(@"Got a db");
		adb_status_t *status = (adb_status_t *)malloc(sizeof(adb_status_t));
		int flags;
		flags = audiodb_status(db, status);
		[statusField setStringValue: [NSString stringWithFormat:@"%@ Dim: %d Files: %d Slice: %@ms Ext: %@", 
									  dbName, 
									  status->dim, 
									  status->numFiles, 
									  [dbState objectForKey:@"hopsize"],
									  [dbState objectForKey:@"extractor"]]];
		[performQueryButton setEnabled:YES];
		[importAudioButton setEnabled:YES];
	}
	else
	{
		NSLog(@"No db");
		[performQueryButton setEnabled:NO];
		[importAudioButton setEnabled:NO];
		[playBothButton setEnabled:NO];
		[playResultButton setEnabled:NO];
		[stopButton setEnabled:NO];
	}
}

-(UInt64)getSampleRate:(NSString *)filename
{
	AudioFileID audioFile;
	AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:filename], 0x01, 0, &audioFile);
	
	UInt32 propertySize;
	UInt32 propertyIsWritable;
	AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyDataFormat, &propertySize, &propertyIsWritable);
	
	AudioStreamBasicDescription dataFormat;
	AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &propertySize, &dataFormat);
	Float64 sampleRate = dataFormat.mSampleRate;
	AudioFileClose(audioFile);
	
	return sampleRate;
}

-(UInt64)getHopSizeInSamples:(NSString *)filename
{
	NSString* hopStr = [dbState objectForKey:@"hopsize"];
	return round([self getSampleRate:filename] * ([hopStr doubleValue] / 1000));
}

-(int)nearestPow2:(int)x
{
    if (x < 0)
        return 0;
    --x;
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;
    return x+1;
}

-(void)importFile:(NSString *)filename withExtractorConfig:(NSString *)extractorPath
{
	// Create the extractor configuration
	int hopSizeSamples = [self getHopSizeInSamples:filename];
	int windowSizeSamples = [self nearestPow2:(hopSizeSamples*8)];
	
	NSString* extractorContent = [NSString stringWithContentsOfFile:extractorPath];
	NSString* newContent = [[extractorContent stringByReplacingOccurrencesOfString:@"HOP_SIZE" withString:[NSString stringWithFormat:@"%d", hopSizeSamples]] 
							stringByReplacingOccurrencesOfString:@"WINDOW_SIZE" withString:[NSString stringWithFormat:@"%d", windowSizeSamples]];
	NSString* n3FileName = [NSTemporaryDirectory() stringByAppendingPathComponent:@"extractor_config.n3"];
	NSLog(newContent);
	NSError* error;
	[newContent writeToFile:n3FileName atomically:YES encoding:NSASCIIStringEncoding error:&error];
	
	// Create the temp file for the extracted features
	NSString* tempFileTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"features.XXXXXX"];
	const char* tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
	char* tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
	strcpy(tempFileNameCString, tempFileTemplateCString);
	mktemp(tempFileNameCString);
	
	NSString* featuresFileName = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
	free(tempFileNameCString);
	
	// Extract features with sonic-annotator
	NSTask* task = [[NSTask alloc] init];
	NSLog(@"Resource path: %@", [ [NSBundle mainBundle] resourcePath]);
	NSString* pluginPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Vamp"];
	NSString* extractPath = [ [ NSBundle mainBundle ] pathForAuxiliaryExecutable: @"sonic-annotator" ];
	
	NSLog(@"Plugin path: %@", pluginPath);
	
	NSDictionary *defaultEnvironment = [[NSProcessInfo processInfo] environment];
    NSMutableDictionary *environment = [[NSMutableDictionary alloc] initWithDictionary:defaultEnvironment];
	[environment setValue:pluginPath forKey:@"VAMP_PATH"];
	NSLog(@"Env: %@", environment);
	[task setLaunchPath:extractPath];
	[task setEnvironment:environment];
	
	
	NSArray* args;
	args = [NSArray arrayWithObjects:@"-t", n3FileName, @"-w", @"rdf", @"-r", @"--rdf-network", @"--rdf-one-file", featuresFileName, @"--rdf-force", filename, nil];
	[task setArguments:args];
	[task launch];
	[task waitUntilExit];
	[task release];
	
	// Populate the audioDB instance
	NSTask* importTask = [[NSTask alloc] init];
	NSString* importPath = [ [ NSBundle mainBundle ] pathForAuxiliaryExecutable: @"populate" ];
	[importTask setLaunchPath:importPath];
	args = [NSArray arrayWithObjects:featuresFileName, dbFilename, nil];
	[importTask setArguments:args];
	[importTask launch];
	[importTask waitUntilExit];
	[importTask release];
	
	NSString* val = [filename retain];
	NSString* key = [[filename lastPathComponent] retain]; 
	
	// Update the plist store.
	[trackMap setValue:val forKey:key];
	[dbState writeToFile:plistFilename atomically: YES];
	
	
}

/**
 * Choose the file(s) to be imported.
 */
-(IBAction)importAudio:(id)sender
{
	[tracksView reloadData];
	
	NSArray *fileTypes = [NSArray arrayWithObjects:@"wav", @"mp3", @"aiff", @"m4a", nil];
	NSOpenPanel* panel = [NSOpenPanel openPanel];
	[panel setAllowsMultipleSelection:TRUE];
	NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes];
	if(response == NSFileHandlingPanelOKButton)
	{
		[indicator startAnimation:self];
		
		[NSApp beginSheet:importSheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
		session = [NSApp beginModalSessionForWindow: importSheet];
		[NSApp runModalSession:session];
		
		NSArray *filesToOpen = [panel filenames];
		
		NSString* extractor = [dbState objectForKey:@"extractor"];
		
		NSLog([NSString stringWithFormat:@"rdf/%@.n3", extractor]);
		NSString* extractorPath = [ [ NSBundle mainBundle ] pathForResource:extractor ofType:@"n3" inDirectory:@"rdf"];
		NSLog(@"Extractor path: %@", extractorPath);
		
//		NSString* extractorPath = [NSString stringWithFormat:@"/Applications/iAudioDB.app/rdf/%@.n3", extractor];
		
		for(int i=0; i<[filesToOpen count]; i++)
		{		
			audiodb_close(db);
			
			// Get the sample rate for the audio file
			
			[self importFile:[filesToOpen objectAtIndex:i] withExtractorConfig:extractorPath];
			db = audiodb_open([dbFilename cStringUsingEncoding:NSUTF8StringEncoding], O_RDONLY);
			[self updateStatus];
		}
		
		[NSApp endModalSession:session];
		[importSheet orderOut:nil];
		[NSApp endSheet:importSheet];
		[indicator stopAnimation:self];
	}
}

/**
 * Required table methods begin here.
 */
-(int)numberOfRowsInTableView:(NSTableView *)v
{
	return [results count];
}

/**
 * Return appropriate values - or the distance indicator if it's the meter column.
 */
-(id)tableView:(NSTableView *)v objectValueForTableColumn:(NSTableColumn *)tc row:(NSInteger)row
{
	id result = [results objectAtIndex:row];
	id value = [result objectForKey:[tc identifier]];
	
	if([[tc identifier] isEqualToString:@"meter"])
	{
		NSLevelIndicatorCell *distance = [[NSLevelIndicatorCell alloc] initWithLevelIndicatorStyle:NSRelevancyLevelIndicatorStyle];
		[distance setFloatValue:10.0f-[(NSNumber*)value floatValue]*100.0f];
		return distance;
	}
	else
	{
		return value;
	}
}

/**
 * Handle column sorting.
 */
- (void)tableView:(NSTableView *)v sortDescriptorsDidChange:(NSArray *)oldDescriptors
{
    [results sortUsingDescriptors:[v sortDescriptors]];
    [v reloadData];
}

/**
 * Only enable the import menu option if a database is loaded.
 */
- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
	SEL theAction = [anItem action];
	if (theAction == @selector(importAudio:))
	{
		if(!db)
		{
			return NO;
		}
	}
	return YES;
}

/**
 * Ensure play buttons are only enabled if a track is selected.
 */
-(IBAction)selectedChanged:(id)sender
{
	if([tracksView numberOfSelectedRows] == 0)
	{
		[playBothButton setEnabled:NO];
		[playResultButton setEnabled:NO];
	}
	else
	{
		[playBothButton setEnabled:YES];
		[playResultButton setEnabled:YES];
	}
}

/**
 * Play just the result track.
 */
-(IBAction)playResult:(id)sender
{

	if([tracksView selectedRow] == -1)
	{
		return;
	}
	
	NSDictionary* selectedRow = [results objectAtIndex:[tracksView selectedRow]];
	NSString* value = [selectedRow objectForKey:@"key"];
	float ipos = [[selectedRow objectForKey:@"ipos"] floatValue];
	NSString* filename = [trackMap objectForKey:value];
	NSLog(@"Key: %@ Value: %@", value, filename);
	
	if(queryTrack)
	{
		if([queryTrack isPlaying])
		{
			[queryTrack setDelegate:Nil];
			[queryTrack stop];
		}
		[queryTrack release];
		queryTrack = Nil;
	}
	
	if(resultTrack)
	{
		if([resultTrack isPlaying])
		{
			[resultTrack setDelegate:Nil];
			[resultTrack stop];
		}
		[resultTrack release];
		resultTrack = Nil;
	}
	
	resultTrack = [[[NSSound alloc] initWithContentsOfFile:filename byReference:YES] retain];
	[resultTrack setCurrentTime:ipos];
	[resultTrack setDelegate:self];
	[resultTrack play];
	
	[stopButton setEnabled:YES];
}

/**
 * Play the result and query simultaneously.
 */
-(IBAction)playBoth:(id)sender
{
	
	NSDictionary* selectedRow = [results objectAtIndex:[tracksView selectedRow]];
	NSString* value = [selectedRow objectForKey:@"key"];
	float ipos = [[selectedRow objectForKey:@"ipos"] floatValue];
	NSString* filename = [trackMap objectForKey:value];
	NSLog(@"Key: %@ Value: %@", value, filename);
		
	if(queryTrack)
	{
		
		if([queryTrack isPlaying])
		{
			[queryTrack setDelegate:Nil];
			[queryTrack stop];
		}
		[queryTrack release];
		queryTrack = Nil;
	}
	if(resultTrack)
	{
		if([resultTrack isPlaying])
		{
			[resultTrack setDelegate:Nil];
			[resultTrack stop];
		}
		[resultTrack release];
		resultTrack = Nil;
	}
	
	// Get query track and shift to start point
	queryTrack = [[[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES] retain];
	[queryTrack setDelegate:self];
	
	[queryTrack play];
	
	resultTrack = [[[NSSound alloc] initWithContentsOfFile:filename byReference:YES] retain];
	[resultTrack setCurrentTime:ipos];
	[resultTrack setDelegate:self];
	[resultTrack play];
	
	[stopButton setEnabled:YES];
}

/**
 * Disable the stop button after playback of both tracks.
 */
- (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)playbackSuccessful
{
	
	if((queryTrack && [queryTrack isPlaying]) || (resultTrack && [resultTrack isPlaying]))
	{
		return;
	}
	else
	{
		[stopButton setEnabled:NO];
	}
}

/**
 * Stop playback.
 */
-(IBAction)stopPlay:(id)sender
{
	if(queryTrack)
	{
		[queryTrack stop];
	}
	if(resultTrack)
	{
		[resultTrack stop];
	}
}

/**
 * Select an audio file, determine the key, and fire off a query.
 */
-(IBAction)chooseQuery:(id)sender
{
	[queryButton setEnabled:(selectedKey ? YES : NO)];
	[NSApp beginSheet:querySheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
	session = [NSApp beginModalSessionForWindow:querySheet];
	[NSApp runModalSession:session];	
}


-(IBAction)selectQueryFile:(id)sender
{
	NSArray* fileTypes = [NSArray arrayWithObjects: @"wav", @"mp3", @"aiff",@"m4a", nil];
	NSOpenPanel* panel = [NSOpenPanel openPanel];
	NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes];
	if(response == NSFileHandlingPanelOKButton)
	{
		NSArray* opts = [trackMap allKeysForObject:[panel filename]];
		if([opts count] != 1)
		{
			// TODO : Needs fixing!
			
			NSAlert *alert = [[[NSAlert alloc] init] autorelease];
			[alert addButtonWithTitle:@"OK"];
			[alert setMessageText:@"Track not found"];
			[alert setInformativeText:@"Make sure you have specified a valid track identifier."];
			[alert setAlertStyle:NSWarningAlertStyle];
			[alert beginSheetModalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
		}
		else
		{
			selectedKey = [opts objectAtIndex:0];
			[queryKey setStringValue:selectedKey];
			[queryPath setStringValue:selectedKey];
			selectedFilename = [[panel filename] retain];
			[queryButton setEnabled:YES];
			
			[self resetLengths:self];
		}
	}
}

-(IBAction)resetLengths:(id)sender
{
	queryTrack = [[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES];

	int sampleRate = [self getSampleRate:selectedFilename];
	int hopSize = [self getHopSizeInSamples:selectedFilename];
	int winSize = [self nearestPow2:(hopSize*8)];
	
	double samples = ([queryTrack duration]*sampleRate);
	
	[queryLengthSeconds setDoubleValue:[queryTrack duration]];
	[queryLengthVectors setDoubleValue:ceil((samples-winSize)/hopSize)];
	
	// For now, go with 0
	[queryStartSeconds setDoubleValue:0];
	[queryStartVectors setDoubleValue:0];

	[queryLengthSeconds setEnabled:YES];
	[queryLengthVectors setEnabled:YES];
	[queryStartSeconds setEnabled:YES];
	[queryStartVectors setEnabled:YES];
	[resetButton setEnabled:YES];
	[multipleCheckBox setEnabled:YES];
	[queryButton setEnabled:YES];
	
}

- (void)controlTextDidChange:(NSNotification *)nd
{
	NSTextField *ed = [nd object];
	
	int sampleRate = [self getSampleRate:selectedFilename];
	int hopSize = [self getHopSizeInSamples:selectedFilename];
	int winSize = [self nearestPow2:(hopSize*8)];
	
	if(!queryTrack)
	{
		queryTrack = [[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES];
	}
	
	double totalDuration = [queryTrack duration];
	double samples = totalDuration * sampleRate;
	double totalVectors = ceil((samples-winSize)/hopSize);

	
	double lengthSecs = [queryLengthSeconds doubleValue];
	double startSecs = [queryStartSeconds doubleValue];
	double lengthVectors = [queryLengthVectors doubleValue];
	double startVectors = [queryStartVectors doubleValue];
	
	// Query Length
	if (ed == queryLengthSeconds)
	{
		if(lengthSecs >= 0)
		{
			lengthVectors = ceil((((lengthSecs*sampleRate)-winSize)/hopSize)+1);
			if(lengthVectors < 0) {lengthVectors = 0; }
			[queryLengthVectors setDoubleValue:lengthVectors];
		}
	}
	
	if (ed == queryLengthVectors)
	{
		if(lengthVectors >= 0)
		{
			lengthSecs = ((hopSize*(lengthVectors-1))+winSize)/sampleRate;
			if(lengthSecs < 0) { lengthSecs = 0; }
			[queryLengthSeconds setDoubleValue:lengthSecs];
		}
	}
	
	// Query start
	if (ed == queryStartSeconds)
	{
		if(startSecs >= 0)
		{
			startVectors = ceil((startSecs*sampleRate)/hopSize);
			if(startVectors < 0) { startVectors = 0; }
			[queryStartVectors setDoubleValue:startVectors];
		}
	}
	if (ed == queryStartVectors)
	{
		if(startVectors >= 0)
		{
			startSecs = (hopSize*startVectors)/sampleRate;
			if(startSecs < 0) { startSecs = 0; }
			[queryStartSeconds setDoubleValue:startSecs];
		}
	}
	
	if((lengthSecs + startSecs) > totalDuration || (lengthVectors + startVectors) > totalVectors || lengthVectors == 0)
	{
		[queryButton setEnabled:NO];
	}
	else if(![queryButton isEnabled])
	{
		[queryButton setEnabled:YES];
	}
}

-(IBAction)cancelQuery:(id)sender
{
	[NSApp endModalSession:session];
	[querySheet orderOut:nil];
	[NSApp endSheet:querySheet];
}

/**
 * Actually perform the query. TODO: Monolithic.
 */
-(IBAction)performQuery:(id)sender
{
	[NSApp endModalSession:session];
	[querySheet orderOut:nil];
	[NSApp endSheet:querySheet];
	
	NSLog(@"Perform query! %@, %@", selectedKey, selectedFilename);
	
	adb_query_spec_t *spec = (adb_query_spec_t *)malloc(sizeof(adb_query_spec_t));
	spec->qid.datum = (adb_datum_t *)malloc(sizeof(adb_datum_t));
	
	spec->qid.sequence_length = [queryLengthVectors doubleValue];
	spec->qid.sequence_start = [queryStartVectors doubleValue];
	spec->qid.flags = 0;	
//	spec->qid.flags = spec->qid.flags | ADB_QID_FLAG_EXHAUSTIVE;
	
	spec->params.accumulation = ADB_ACCUMULATION_PER_TRACK;
	
	if([multipleCheckBox state] == NSOnState)
	{
		spec->params.npoints = 100;
	}
	else
	{
		spec->params.npoints = 1;
	}
		
	spec->params.distance = ADB_DISTANCE_EUCLIDEAN_NORMED;
	
	spec->params.ntracks = 100;
	//spec->refine.radius = 5.0;
//	spec->refine.absolute_threshold = -6;
//	spec->refine.relative_threshold = 10;
//	spec->refine.duration_ratio = 0;
	
	spec->refine.flags = 0;
//	spec->refine.flags |= ADB_REFINE_ABSOLUTE_THRESHOLD;
//	spec->refine.flags |= ADB_REFINE_RELATIVE_THRESHOLD;
//	spec->refine.flags |= ADB_REFINE_HOP_SIZE;
	//spec->refine.flags |= ADB_REFINE_RADIUS;

	adb_query_results_t *result = (adb_query_results_t *)malloc(sizeof(adb_query_results_t));
	spec->qid.datum->data = NULL;
	spec->qid.datum->power = NULL;
	spec->qid.datum->times = NULL;
	
	[results removeAllObjects];
	
	int ok = audiodb_retrieve_datum(db, [selectedKey cStringUsingEncoding:NSUTF8StringEncoding], spec->qid.datum);
	if(ok == 0)
	{
		
		float hopSize = [[dbState objectForKey:@"hopsize"] floatValue];
		NSLog(@"Got a datum");
		result = audiodb_query_spec(db, spec);
		if(result == NULL)
		{
			
			NSLog(@"No results");
		}
		else
		{
			
			NSLog(@"Populate table: %d", result->nresults);
			for(int i=0; i<result->nresults; i++)
			{
				
				NSString* filename = [trackMap objectForKey:[NSString stringWithFormat:@"%s", result->results[i].ikey]];
				int sampleRate = [self getSampleRate:filename];
				int hopSize = [self getHopSizeInSamples:filename];
				float divisor = (sampleRate/hopSize);
				
				NSMutableDictionary* dict = [[NSMutableDictionary alloc] initWithCapacity:4];
				[dict setValue:[NSString stringWithFormat:@"%s", result->results[i].ikey] forKey:@"key"];
				[dict setValue:[NSNumber numberWithFloat:result->results[i].dist] forKey:@"distance"];
				[dict setValue:[NSNumber numberWithFloat:result->results[i].dist] forKey:@"meter"];
				[dict setValue:[NSNumber numberWithFloat:result->results[i].ipos/divisor] forKey:@"ipos"];
				NSLog(@"%s ipos: %d, dist: %f", result->results[i].ikey,result->results[i].ipos, result->results[i].dist);
				[results addObject: dict];
			}
		}
		
		NSSortDescriptor *distSort = [[NSSortDescriptor alloc]initWithKey:@"meter"  ascending:YES];
		NSArray *distDescs = [NSArray arrayWithObject:distSort];
		
		[results sortUsingDescriptors:distDescs];
		[tracksView setSortDescriptors:distDescs];
		[tracksView reloadData];
		
	}
	else
	{		
		NSAlert *alert = [[[NSAlert alloc] init] autorelease];
		[alert addButtonWithTitle:@"OK"];
		[alert setMessageText:@"Track not found"];
		[alert setInformativeText:@"Make sure you have specified a valid track identifier."];
		[alert setAlertStyle:NSWarningAlertStyle];
		[alert beginSheetModalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
	}
//	audiodb_query_free_results(db, spec, result);
}

@end