Mercurial > hg > audiodb
view examples/iAudioDB/AppController.m @ 691:203181f7d804
Added checks for max length/vectors that disable the query button if invalid values are provided.
author | mas01mj |
---|---|
date | Fri, 12 Mar 2010 14:46:22 +0000 |
parents | f3268055df0a |
children | 02756c5ca15a |
line wrap: on
line source
// // AppController.m // iAudioDB // // Created by Mike Jewell on 27/01/2010. // Copyright 2010 __MyCompanyName__. All rights reserved. // #import "AppController.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]; 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 3: extractor = @"qm_mfcc"; dim = 12; break; } // Calculate the max DB size int vectors = ceil(([maxLengthField doubleValue] * 60) / ([hopSizeField doubleValue] / 44100)); int numtracks = [maxTracksField intValue]; int datasize = ceil((numtracks * vectors * dim * 8) / 1024 / 1024); // In MB [self reset]; // Create new db, and set flags. db = audiodb_create([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], datasize, numtracks, dim); audiodb_l2norm(db); // Store useful paths. dbName = [[[panel URL] relativePath] retain]; dbFilename = [[panel filename] retain]; plistFilename = [[NSString stringWithFormat:@"%@.plist", [dbFilename stringByDeletingPathExtension]] retain]; // 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 setValue:[windowSizeField stringValue] forKey:@"windowsize"]; [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) { NSLog(@"Released selected key: %@", selectedKey); [selectedKey release]; selectedKey = Nil; NSLog(@"Is now %@", selectedKey); } if(selectedKey) { NSLog(@"Still evals"); } // Reset query flags [queryPath setStringValue: @"No file selected"]; [queryLengthSeconds setDoubleValue:0]; [queryLengthVectors setDoubleValue:0]; [multipleCheckBox setState:NSOnState]; } /** * 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 Hop: %@ Win: %@ Ext: %@", dbName, status->dim, status->numFiles, [dbState objectForKey:@"hopsize"], [dbState objectForKey:@"windowsize"], [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]; } } /** * Choose the file(s) to be imported. * TODO: Currently handles the import process too - split this off. */ -(IBAction)importAudio:(id)sender { [tracksView reloadData]; NSArray *fileTypes = [NSArray arrayWithObject:@"wav"]; 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"]; NSString* extractorPath = [NSString stringWithFormat:@"/Applications/iAudioDB.app/rdf/%@.n3", extractor]; // TODO Shift this process into a separate function. // Create the customized extractor config NSString* extractorContent = [NSString stringWithContentsOfFile:extractorPath]; NSString* hopStr = [dbState objectForKey:@"hopsize"]; NSString* winStr = [dbState objectForKey:@"windowsize"]; NSString* newContent = [[extractorContent stringByReplacingOccurrencesOfString:@"HOP_SIZE" withString:hopStr] stringByReplacingOccurrencesOfString:@"WINDOW_SIZE" withString:winStr]; NSString* n3FileName = [NSTemporaryDirectory() stringByAppendingPathComponent:@"extractor_config.n3"]; NSError* error; [newContent writeToFile:n3FileName atomically:YES encoding:NSASCIIStringEncoding error:&error]; for(int i=0; i<[filesToOpen count]; i++) { audiodb_close(db); 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); NSTask* task = [[NSTask alloc] init]; [task setLaunchPath:@"/usr/local/bin/sonic-annotator"]; NSArray* args; args = [NSArray arrayWithObjects:@"-t", n3FileName, @"-w", @"rdf", @"-r", @"--rdf-network", @"--rdf-one-file", featuresFileName, @"--rdf-force", [filesToOpen objectAtIndex:i], nil]; [task setArguments:args]; [task launch]; [task waitUntilExit]; [task release]; NSTask* importTask = [[NSTask alloc] init]; [importTask setLaunchPath:@"/usr/local/bin/populate"]; args = [NSArray arrayWithObjects:featuresFileName, dbFilename, nil]; [importTask setArguments:args]; [importTask launch]; [importTask waitUntilExit]; [importTask release]; NSString* val = [[filesToOpen objectAtIndex:i] retain]; NSString* key = [[[filesToOpen objectAtIndex:i] lastPathComponent] retain]; // Update the plist store. [trackMap setValue:val forKey:key]; [dbState writeToFile:plistFilename atomically: YES]; 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]]; NSLog(@"Result: %s", [tc identifier]); if([[tc identifier] isEqualToString:@"meter"]) { NSLevelIndicatorCell *distance = [[NSLevelIndicatorCell alloc] initWithLevelIndicatorStyle:NSRelevancyLevelIndicatorStyle]; [distance setFloatValue:10-[(NSNumber*)value floatValue]*100]; 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]; } if(resultTrack) { if([resultTrack isPlaying]) { [resultTrack setDelegate:Nil]; [resultTrack stop]; } [resultTrack release]; } 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]; } if(resultTrack) { if([resultTrack isPlaying]) { [resultTrack setDelegate:Nil]; [resultTrack stop]; } [resultTrack release]; } // 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 arrayWithObject:@"wav"]; 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]; NSLog(@"%f", [queryTrack duration]); double samples = ([queryTrack duration]*44100); double hopSize = [[dbState objectForKey:@"hopsize"] doubleValue]; double winSize = [[dbState objectForKey:@"windowsize"] doubleValue]; [queryLengthSeconds setDoubleValue:ceil([queryTrack duration])]; [queryLengthVectors setDoubleValue:ceil((samples-winSize)/hopSize)]; } - (void)controlTextDidChange:(NSNotification *)nd { NSTextField *ed = [nd object]; double hopSize = [[dbState objectForKey:@"hopsize"] doubleValue]; double winSize = [[dbState objectForKey:@"windowsize"] doubleValue]; if(!queryTrack) { queryTrack = [[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES]; } double totalDuration = [queryTrack duration]; double samples = totalDuration * 44100; double totalVectors = ((samples-winSize)/hopSize); if (ed == queryLengthSeconds) { double secs = [queryLengthSeconds doubleValue]; if(secs > totalDuration) { [queryButton setEnabled:NO]; } else if(![queryButton isEnabled]) { [queryButton setEnabled:YES]; } if(secs > 0) { // (samples - windowSize) / hopSize [queryLengthVectors setDoubleValue:ceil(((secs*44100)-winSize)/hopSize)]; } } if (ed == queryLengthVectors) { double vectors = [queryLengthVectors doubleValue]; if(vectors > totalVectors) { [queryButton setEnabled:NO]; } else if(![queryButton isEnabled]) { [queryButton setEnabled:YES]; } if(vectors > 0) { [queryLengthSeconds setDoubleValue:ceil(((hopSize*vectors)+winSize)/44100)]; } } }; -(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 = 0; 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 = 10; } 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) { NSLog(@"Got a datum"); result = audiodb_query_spec(db, spec); if(result == NULL) { NSLog(@"No results"); } else { NSLog(@"Populate table: %d", result->nresults); float divisor = (44100/2048); for(int i=0; i<result->nresults; i++) { 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"]; [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