Mercurial > hg > audiodb
diff examples/iAudioDB/AppController.m @ 669:780ebab29268
Added initial increment of OSX audioDB interface
author | mas01mj |
---|---|
date | Wed, 03 Mar 2010 17:17:08 +0000 |
parents | |
children | 15e71890b584 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/iAudioDB/AppController.m Wed Mar 03 17:17:08 2010 +0000 @@ -0,0 +1,602 @@ +// +// 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; +} + + +/** + * Create a new database, given the selected filename. + */ +-(IBAction)newDatabase:(id)sender +{ + NSSavePanel* panel = [NSSavePanel savePanel]; + NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@""]; + + [results removeAllObjects]; + [tracksView reloadData]; + + if(response == NSFileHandlingPanelOKButton) + { + // TODO: Refactor this into a 'tidy' method. + // Tidy any existing references up. + if(db) + { + audiodb_close(db); + } + + if(dbFilename) + { + [dbFilename release]; + [dbName release]; + [plistFilename release]; + } + + // Create new db, and set flags. + db = audiodb_create([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], 0, 0, 0); + audiodb_l2norm(db); + audiodb_power(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). + trackMap = [[NSMutableDictionary alloc] init]; + [trackMap writeToFile:plistFilename atomically:YES]; + + [queryKey setStringValue:@"None Selected"]; + [self updateStatus]; + } +} + +/** + * 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) + { + // Tidy any existing references up. + if(db) + { + audiodb_close(db); + } + + if(dbFilename) + { + [dbFilename release]; + [dbName release]; + [plistFilename release]; + } + + // Store useful paths. + db = audiodb_open([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], O_RDWR); + 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"]; + [self updateStatus]; + + 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); + trackMap = [[[NSMutableDictionary alloc] initWithContentsOfFile:plistFilename] retain]; + NSLog(@"Size: %d", [trackMap count]); + } +} + +/** + * Update button states and status field based on current state. + */ +-(void)updateStatus +{ + if(db) + { + adb_status_ptr status = (adb_status_ptr)malloc(sizeof(struct adbstatus)); + int flags; + flags = audiodb_status(db, status); + [statusField setStringValue: [NSString stringWithFormat:@"Database: %@ Dimensions: %d Files: %d", dbName, status->dim, status->numFiles]]; + [chooseButton setEnabled:YES]; + } + else + { + [chooseButton setEnabled:NO]; + [playBothButton setEnabled:FALSE]; + [playResultButton setEnabled:FALSE]; + } +} + +/** + * Get user's import choices. + */ +-(IBAction)importAudio:(id)sender +{ + [NSApp beginSheet:importSheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil]; + session = [NSApp beginModalSessionForWindow: importSheet]; + [NSApp runModalSession:session]; +} + +/** + * Cancel the import (at configuration time). + */ +-(IBAction)cancelImport:(id)sender; +{ + [NSApp endModalSession:session]; + [importSheet orderOut:nil]; + [NSApp endSheet:importSheet]; +} + +/** + * Choose the file(s) to be imported. + * TODO: Currently handles the import process too - split this off. + */ +-(IBAction)selectFiles:(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) + { + NSRect newFrame; + + [extractingBox setHidden:FALSE]; + newFrame.origin.x = [importSheet frame].origin.x; + newFrame.origin.y = [importSheet frame].origin.y - [extractingBox frame].size.height; + newFrame.size.width = [importSheet frame].size.width; + newFrame.size.height = [importSheet frame].size.height + [extractingBox frame].size.height; + + [indicator startAnimation:self]; + [importSheet setFrame:newFrame display:YES animate:YES]; + + NSArray *filesToOpen = [panel filenames]; + + NSLog(@"Begin import"); + + // Work out which extractor to use + NSString* extractor = @"chromagram"; + switch([extractorOptions selectedTag]) + { + case 0: + extractor = @"mfcc"; + break; + case 1: + extractor = @"chromagram"; + break; + } + + for(int i=0; i<[filesToOpen count]; i++) + { + // First extract powers + + NSString *tempFileTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"powers.XXXXXX"]; + const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation]; + char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1); + strcpy(tempFileNameCString, tempFileTemplateCString); + mktemp(tempFileNameCString); + + NSString* powersFileName = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)]; + free(tempFileNameCString); + + NSTask *task = [[NSTask alloc] init]; + [task setLaunchPath:@"/usr/local/bin/fftExtract2"]; + NSArray *args = [NSArray arrayWithObjects:@"-P", @"-h", @"11025", @"-w", @"16384", @"-n", @"32768", @"-i", @"1000", [filesToOpen objectAtIndex:i], powersFileName, nil]; + [task setArguments:args]; + [task launch]; + [task waitUntilExit]; + [task release]; + + // Then features + + tempFileTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"features.XXXXXX"]; + tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation]; + tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1); + strcpy(tempFileNameCString, tempFileTemplateCString); + mktemp(tempFileNameCString); + + NSString* featuresFileName = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)]; + free(tempFileNameCString); + + task = [[NSTask alloc] init]; + + [task setLaunchPath:@"/usr/local/bin/fftExtract2"]; + + NSArray *args2; + + // Choose the args (TODO: This should use sonic annotator eventually) + if([extractor isEqualToString:@"chromagram"]) + { + args2 = [NSArray arrayWithObjects:@"-p",@"/Users/moj/planfile",@"-c", @"36", @"-h", @"11025", @"-w", @"16384", @"-n", @"32768", @"-i", @"1000", [filesToOpen objectAtIndex:i], featuresFileName, nil]; + } + else + { + args2 = [NSArray arrayWithObjects:@"-p",@"/Users/moj/planfile",@"-m", @"13", @"-h", @"11025", @"-w", @"16384", @"-n ", @"32768", @"-i", @"1000", [filesToOpen objectAtIndex:i], featuresFileName, nil]; + } + [task setArguments:args2]; + [task launch]; + [task waitUntilExit]; + [task release]; + + NSString* val = [[filesToOpen objectAtIndex:i] retain]; + NSString* key = [[[filesToOpen objectAtIndex:i] lastPathComponent] retain]; + + adb_insert_t insert; + insert.features = [featuresFileName cStringUsingEncoding:NSUTF8StringEncoding]; + insert.power = [powersFileName cStringUsingEncoding:NSUTF8StringEncoding]; + insert.times = NULL; + insert.key = [key cStringUsingEncoding:NSUTF8StringEncoding]; + + // Insert into db. + if(audiodb_insert(db, &insert)) + { + // TODO: Show an error message. + NSLog(@"Weep: %@ %@ %@", featuresFileName, powersFileName, key); + continue; + } + + // Update the plist store. + [trackMap setValue:val forKey:key]; + [trackMap writeToFile:plistFilename atomically: YES]; + + [self updateStatus]; + } + + newFrame.origin.x = [importSheet frame].origin.x; + newFrame.origin.y = [importSheet frame].origin.y + [extractingBox frame].size.height; + newFrame.size.width = [importSheet frame].size.width; + newFrame.size.height = [importSheet frame].size.height - [extractingBox frame].size.height; + + [importSheet setFrame:newFrame display:YES animate:YES]; + + [NSApp endModalSession:session]; + [importSheet orderOut:nil]; + [NSApp endSheet:importSheet]; + [indicator stopAnimation:self]; + [extractingBox setHidden:TRUE]; + } +} + +/** + * 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-[(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:FALSE]; + [playResultButton setEnabled:FALSE]; + } + else + { + [playBothButton setEnabled:TRUE]; + [playResultButton setEnabled:TRUE]; + } +} + +/** + * Play just the result track. + */ +-(IBAction)playResult:(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]; + } + + 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]; + float qpos = [[selectedRow objectForKey:@"qpos"] 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 setCurrentTime:qpos]; + [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 +{ + NSArray* fileTypes = [NSArray arrayWithObject:@"wav"]; + NSOpenPanel* panel = [NSOpenPanel openPanel]; + NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes]; + if(response == NSFileHandlingPanelOKButton) + { + NSLog(@"%@", [panel filename]); + // Grab key + NSArray* opts = [trackMap allKeysForObject:[panel filename]]; + if([opts count] != 1) + { + 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]; + selectedFilename = [[panel filename] retain]; + [self performQuery]; + } + } +} + +/** + * Actually perform the query. TODO: Monolithic. + */ +-(void)performQuery +{ + 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 = 20; + 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; + spec->params.distance = ADB_DISTANCE_EUCLIDEAN_NORMED; + + spec->params.npoints = 1; + spec->params.ntracks = 100; + //spec->refine.radius = 5.0; + spec->refine.hopsize = 1; +// 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 + { + for(int i=0; i<result->nresults; i++) + { + NSMutableDictionary* dict = [[NSMutableDictionary alloc] initWithCapacity:4]; + [dict setValue:[NSString stringWithFormat:@"%s", result->results[i].key] 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].qpos/4] forKey:@"qpos"]; + [dict setValue:[NSNumber numberWithFloat:result->results[i].ipos/4] forKey:@"ipos"]; + NSLog(@"%s qpos %d ipos %d", result->results[i].key, result->results[i].qpos/4, result->results[i].ipos/4); + [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