mas01mj@669: // mas01mj@669: // AppController.m mas01mj@706: // CAMUS mas01mj@669: // mas01mj@669: // Created by Mike Jewell on 27/01/2010. mas01mj@669: // Copyright 2010 __MyCompanyName__. All rights reserved. mas01mj@669: // mas01mj@699: #import "AppController.h" mas01mj@701: #import mas01mj@669: mas01mj@669: mas01mj@669: @implementation AppController mas01mj@669: mas01mj@669: -(id)init mas01mj@669: { mas01mj@669: [super init]; mas01mj@669: mas01mj@669: // A max of 100 results. mas01mj@669: results = [[NSMutableArray alloc] initWithCapacity: 100]; mas01mj@669: mas01mj@669: return self; mas01mj@669: } mas01mj@669: mas01mj@699: - (void)awakeFromNib { mas01mj@699: [tracksView setTarget:self]; mas01mj@699: [tracksView setDoubleAction:@selector(tableDoubleClick:)]; mas01mj@699: [self updateStatus]; mas01mj@699: } mas01mj@699: mas01mj@699: mas01mj@699: - (IBAction)tableDoubleClick:(id)sender mas01mj@699: { mas01mj@699: [self playResult:Nil]; mas01mj@699: // NSLog(@"Table double clicked"); mas01mj@699: } mas01mj@699: mas01mj@669: mas01mj@669: /** mas01mj@669: * Create a new database, given the selected filename. mas01mj@669: */ mas01mj@669: -(IBAction)newDatabase:(id)sender mas01mj@669: { mas01mj@699: mas01mj@699: [NSApp beginSheet:createSheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil]; mas01mj@699: session = [NSApp beginModalSessionForWindow:createSheet]; mas01mj@699: [NSApp runModalSession:session]; mas01mj@699: } mas01mj@699: mas01mj@699: /** mas01mj@699: * Cancel the db creation (at configuration time). mas01mj@699: */ mas01mj@699: -(IBAction)cancelCreate:(id)sender mas01mj@699: { mas01mj@699: [NSApp endModalSession:session]; mas01mj@699: [createSheet orderOut:nil]; mas01mj@699: [NSApp endSheet:createSheet]; mas01mj@699: } mas01mj@699: mas01mj@699: -(IBAction)createDatabase:(id)sender mas01mj@699: { mas01mj@699: [self cancelCreate:self]; mas01mj@699: mas01mj@669: NSSavePanel* panel = [NSSavePanel savePanel]; mas01mj@709: [panel setCanSelectHiddenExtension:YES]; mas01mj@739: [panel setAllowedFileTypes:[NSArray arrayWithObject:@"adb"]]; mas01mj@669: NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@""]; mas01mj@699: mas01mj@669: [results removeAllObjects]; mas01mj@669: [tracksView reloadData]; mas01mj@699: mas01mj@669: if(response == NSFileHandlingPanelOKButton) mas01mj@669: { mas01mj@699: // Work out which extractor to use mas01mj@699: NSString* extractor = @"adb_chroma"; mas01mj@699: // TODO: This should be stored with the n3. mas01mj@699: int dim; mas01mj@699: switch([extractorOptions selectedTag]) mas01mj@685: { mas01mj@699: case 0: mas01mj@699: extractor = @"adb_chroma"; mas01mj@699: dim = 12; mas01mj@699: break; mas01mj@704: /* case 1: mas01mj@699: extractor = @"adb_cq"; mas01mj@699: dim = 48; mas01mj@699: break; mas01mj@699: case 2: mas01mj@699: extractor = @"qm_chroma"; mas01mj@699: dim = 12; mas01mj@704: break;*/ mas01mj@704: case 1: mas01mj@699: extractor = @"qm_mfcc"; mas01mj@699: dim = 12; mas01mj@699: break; mas01mj@685: } mas01mj@685: mas01mj@699: // Calculate the max DB size mas01mj@709: NSLog(@"Max length: %f", [maxLengthField doubleValue]); mas01mj@709: NSLog(@"hop size: %f", [hopSizeField doubleValue]); mas01mj@709: int vectors = ceil([maxLengthField doubleValue] / (([hopSizeField doubleValue] / 1000.0f))); mas01mj@709: NSLog(@"Max Vectors: %d", vectors); mas01mj@699: int numtracks = [maxTracksField intValue]; mas01mj@699: int datasize = ceil((numtracks * vectors * dim * 8.0f) / 1024.0f / 1024.0f); // In MB mas01mj@685: mas01mj@699: [self reset]; mas01mj@699: mas01mj@712: mas01mj@712: mas01mj@669: // Store useful paths. mas01mj@669: dbName = [[[panel URL] relativePath] retain]; mas01mj@669: dbFilename = [[panel filename] retain]; mas01mj@669: plistFilename = [[NSString stringWithFormat:@"%@.plist", [dbFilename stringByDeletingPathExtension]] retain]; mas01mj@712: mas01mj@712: // Remove any existing files mas01mj@712: NSFileManager *fileManager = [[NSFileManager alloc] init]; mas01mj@712: mas01mj@712: BOOL overwriteError = NO; mas01mj@712: mas01mj@712: if([fileManager fileExistsAtPath:[panel filename]]) mas01mj@712: { mas01mj@712: if(![fileManager removeItemAtPath:[panel filename] error:NULL]) mas01mj@712: { mas01mj@712: overwriteError = YES; mas01mj@712: } mas01mj@712: } mas01mj@712: mas01mj@712: if(!overwriteError && [fileManager fileExistsAtPath:plistFilename]) mas01mj@712: { mas01mj@712: if(![fileManager removeItemAtPath:plistFilename error:NULL]) mas01mj@712: { mas01mj@712: overwriteError = YES; mas01mj@712: } mas01mj@712: } mas01mj@712: [fileManager release]; mas01mj@712: mas01mj@712: if(overwriteError) mas01mj@712: { mas01mj@712: NSAlert *alert = [[NSAlert alloc] init]; mas01mj@712: [alert addButtonWithTitle:@"OK"]; mas01mj@712: [alert setMessageText:@"Unable to create database."]; mas01mj@712: [alert setInformativeText:@"A database with this name already exists, and could not be overwritten."]; mas01mj@712: [alert setAlertStyle:NSWarningAlertStyle]; mas01mj@712: [alert runModal]; mas01mj@712: [alert release]; mas01mj@712: return; mas01mj@712: } mas01mj@712: mas01mj@712: // Create new db, and set flags. mas01mj@712: db = audiodb_create([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], datasize, numtracks, dim); mas01mj@712: audiodb_l2norm(db); mas01mj@699: mas01mj@669: // Create the plist file (contains mapping from filename to key). mas01mj@699: dbState = [[NSMutableDictionary alloc] init]; mas01mj@669: trackMap = [[NSMutableDictionary alloc] init]; mas01mj@699: [dbState setValue:trackMap forKey:@"tracks"]; mas01mj@699: [dbState setValue:extractor forKey:@"extractor"]; mas01mj@699: [dbState setValue:[hopSizeField stringValue] forKey:@"hopsize"]; mas01mj@699: [dbState writeToFile:plistFilename atomically:YES]; mas01mj@699: mas01mj@669: [queryKey setStringValue:@"None Selected"]; mas01mj@669: [self updateStatus]; mas01mj@669: } mas01mj@669: } mas01mj@669: mas01mj@699: -(void)reset mas01mj@699: { mas01mj@699: // Tidy any existing references up. mas01mj@699: if(db) mas01mj@699: { mas01mj@699: NSLog(@"Close db"); mas01mj@699: audiodb_close(db); mas01mj@699: } mas01mj@699: mas01mj@699: if(dbFilename) mas01mj@699: { mas01mj@699: NSLog(@"Tidy up filenames"); mas01mj@699: [dbFilename release]; mas01mj@699: [dbName release]; mas01mj@699: [plistFilename release]; mas01mj@699: [trackMap release]; mas01mj@699: [dbState release]; mas01mj@699: } mas01mj@699: mas01mj@699: if(selectedKey) mas01mj@699: { mas01mj@699: [selectedKey release]; mas01mj@699: selectedKey = Nil; mas01mj@699: } mas01mj@699: mas01mj@699: // Reset query flags mas01mj@699: [queryPath setStringValue: @"No file selected"]; mas01mj@699: [queryLengthSeconds setDoubleValue:0]; mas01mj@699: [queryLengthVectors setDoubleValue:0]; mas01mj@699: [multipleCheckBox setState:NSOnState]; mas01mj@699: [queryStartSeconds setDoubleValue:0]; mas01mj@699: [queryStartVectors setDoubleValue:0]; mas01mj@699: mas01mj@699: [queryLengthSeconds setEnabled:NO]; mas01mj@699: [queryLengthVectors setEnabled:NO]; mas01mj@699: [queryStartSeconds setEnabled:NO]; mas01mj@699: [queryStartVectors setEnabled:NO]; mas01mj@699: [resetButton setEnabled:NO]; mas01mj@699: [multipleCheckBox setEnabled:NO]; mas01mj@739: mas01mj@739: [playBothButton setEnabled:NO]; mas01mj@739: [playResultButton setEnabled:NO]; mas01mj@699: } mas01mj@699: mas01mj@669: /** mas01mj@669: * Open an existing adb (which must have a plist) mas01mj@669: */ mas01mj@669: -(IBAction)openDatabase:(id)sender mas01mj@669: { mas01mj@669: NSArray *fileTypes = [NSArray arrayWithObject:@"adb"]; mas01mj@669: NSOpenPanel* panel = [NSOpenPanel openPanel]; mas01mj@669: NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes]; mas01mj@669: if(response == NSFileHandlingPanelOKButton) mas01mj@669: { mas01mj@699: [self reset]; mas01mj@669: mas01mj@669: // Store useful paths. mas01mj@699: NSLog(@"Open"); mas01mj@699: db = audiodb_open([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], O_RDONLY); mas01mj@669: dbName = [[[panel URL] relativePath] retain]; mas01mj@669: dbFilename = [[panel filename] retain]; mas01mj@669: mas01mj@669: // TODO: Verify this exists! mas01mj@669: plistFilename = [[NSString stringWithFormat:@"%@.plist", [dbFilename stringByDeletingPathExtension]] retain]; mas01mj@669: mas01mj@669: // Clear out any old results. mas01mj@669: [results removeAllObjects]; mas01mj@669: [tracksView reloadData]; mas01mj@669: mas01mj@669: [queryKey setStringValue:@"None Selected"]; mas01mj@669: mas01mj@669: adb_liszt_results_t* liszt_results = audiodb_liszt(db); mas01mj@669: mas01mj@669: for(int k=0; knresults; k++) mas01mj@669: { mas01mj@669: NSMutableString *trackVal = [[NSMutableString alloc] init]; mas01mj@669: [trackVal appendFormat:@"%s", liszt_results->entries[k].key]; mas01mj@669: } mas01mj@669: mas01mj@669: audiodb_liszt_free_results(db, liszt_results); mas01mj@699: dbState = [[[NSMutableDictionary alloc] initWithContentsOfFile:plistFilename] retain]; mas01mj@699: trackMap = [[dbState objectForKey:@"tracks"] retain]; mas01mj@699: mas01mj@699: [self updateStatus]; mas01mj@699: mas01mj@669: NSLog(@"Size: %d", [trackMap count]); mas01mj@669: } mas01mj@669: } mas01mj@669: mas01mj@699: -(IBAction)pathAction:(id)sender mas01mj@699: { mas01mj@699: NSLog(@"Path action"); mas01mj@699: } mas01mj@699: mas01mj@669: /** mas01mj@669: * Update button states and status field based on current state. mas01mj@669: */ mas01mj@669: -(void)updateStatus mas01mj@669: { mas01mj@699: NSLog(@"Update status"); mas01mj@669: if(db) mas01mj@669: { mas01mj@699: NSLog(@"Got a db"); mas01mj@699: adb_status_t *status = (adb_status_t *)malloc(sizeof(adb_status_t)); mas01mj@669: int flags; mas01mj@669: flags = audiodb_status(db, status); mas01mj@705: [statusField setStringValue: [NSString stringWithFormat:@"%@ Dim: %d Files: %d Slice: %@ms Ext: %@", mas01mj@699: dbName, mas01mj@699: status->dim, mas01mj@699: status->numFiles, mas01mj@699: [dbState objectForKey:@"hopsize"], mas01mj@699: [dbState objectForKey:@"extractor"]]]; mas01mj@699: [performQueryButton setEnabled:YES]; mas01mj@699: [importAudioButton setEnabled:YES]; mas01mj@669: } mas01mj@669: else mas01mj@669: { mas01mj@699: NSLog(@"No db"); mas01mj@699: [performQueryButton setEnabled:NO]; mas01mj@699: [importAudioButton setEnabled:NO]; mas01mj@699: [playBothButton setEnabled:NO]; mas01mj@699: [playResultButton setEnabled:NO]; mas01mj@699: [stopButton setEnabled:NO]; mas01mj@669: } mas01mj@669: } mas01mj@669: mas01mj@701: -(UInt64)getSampleRate:(NSString *)filename mas01mj@701: { mas01mj@701: AudioFileID audioFile; mas01mj@701: AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:filename], 0x01, 0, &audioFile); mas01mj@701: mas01mj@701: UInt32 propertySize; mas01mj@701: UInt32 propertyIsWritable; mas01mj@701: AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyDataFormat, &propertySize, &propertyIsWritable); mas01mj@701: mas01mj@701: AudioStreamBasicDescription dataFormat; mas01mj@701: AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &propertySize, &dataFormat); mas01mj@701: Float64 sampleRate = dataFormat.mSampleRate; mas01mj@701: AudioFileClose(audioFile); mas01mj@701: mas01mj@701: return sampleRate; mas01mj@701: } mas01mj@701: mas01mj@702: -(UInt64)getHopSizeInSamples:(NSString *)filename mas01mj@702: { mas01mj@702: NSString* hopStr = [dbState objectForKey:@"hopsize"]; mas01mj@702: return round([self getSampleRate:filename] * ([hopStr doubleValue] / 1000)); mas01mj@702: } mas01mj@702: mas01mj@702: -(int)nearestPow2:(int)x mas01mj@702: { mas01mj@702: if (x < 0) mas01mj@702: return 0; mas01mj@702: --x; mas01mj@702: x |= x >> 1; mas01mj@702: x |= x >> 2; mas01mj@702: x |= x >> 4; mas01mj@702: x |= x >> 8; mas01mj@702: x |= x >> 16; mas01mj@702: return x+1; mas01mj@702: } mas01mj@702: mas01mj@700: -(void)importFile:(NSString *)filename withExtractorConfig:(NSString *)extractorPath mas01mj@700: { mas01mj@700: // Create the extractor configuration mas01mj@702: int hopSizeSamples = [self getHopSizeInSamples:filename]; mas01mj@702: int windowSizeSamples = [self nearestPow2:(hopSizeSamples*8)]; mas01mj@700: mas01mj@700: NSString* extractorContent = [NSString stringWithContentsOfFile:extractorPath]; mas01mj@702: NSString* newContent = [[extractorContent stringByReplacingOccurrencesOfString:@"HOP_SIZE" withString:[NSString stringWithFormat:@"%d", hopSizeSamples]] mas01mj@702: stringByReplacingOccurrencesOfString:@"WINDOW_SIZE" withString:[NSString stringWithFormat:@"%d", windowSizeSamples]]; mas01mj@700: NSString* n3FileName = [NSTemporaryDirectory() stringByAppendingPathComponent:@"extractor_config.n3"]; mas01mj@702: NSLog(newContent); mas01mj@700: NSError* error; mas01mj@700: [newContent writeToFile:n3FileName atomically:YES encoding:NSASCIIStringEncoding error:&error]; mas01mj@700: mas01mj@700: // Create the temp file for the extracted features mas01mj@700: NSString* tempFileTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"features.XXXXXX"]; mas01mj@700: const char* tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation]; mas01mj@700: char* tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1); mas01mj@700: strcpy(tempFileNameCString, tempFileTemplateCString); mas01mj@700: mktemp(tempFileNameCString); mas01mj@700: mas01mj@700: NSString* featuresFileName = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)]; mas01mj@700: free(tempFileNameCString); mas01mj@700: mas01mj@700: // Extract features with sonic-annotator mas01mj@700: NSTask* task = [[NSTask alloc] init]; mas01mj@709: NSLog(@"Resource path: %@", [ [NSBundle mainBundle] resourcePath]); mas01mj@709: NSString* pluginPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Vamp"]; mas01mj@709: NSString* extractPath = [ [ NSBundle mainBundle ] pathForAuxiliaryExecutable: @"sonic-annotator" ]; mas01mj@705: mas01mj@709: NSLog(@"Plugin path: %@", pluginPath); mas01mj@709: mas01mj@709: NSDictionary *defaultEnvironment = [[NSProcessInfo processInfo] environment]; mas01mj@709: NSMutableDictionary *environment = [[NSMutableDictionary alloc] initWithDictionary:defaultEnvironment]; mas01mj@709: [environment setValue:pluginPath forKey:@"VAMP_PATH"]; mas01mj@709: NSLog(@"Env: %@", environment); mas01mj@705: [task setLaunchPath:extractPath]; mas01mj@709: [task setEnvironment:environment]; mas01mj@709: mas01mj@705: mas01mj@700: NSArray* args; mas01mj@700: args = [NSArray arrayWithObjects:@"-t", n3FileName, @"-w", @"rdf", @"-r", @"--rdf-network", @"--rdf-one-file", featuresFileName, @"--rdf-force", filename, nil]; mas01mj@700: [task setArguments:args]; mas01mj@700: [task launch]; mas01mj@700: [task waitUntilExit]; mas01mj@700: [task release]; mas01mj@700: mas01mj@700: // Populate the audioDB instance mas01mj@700: NSTask* importTask = [[NSTask alloc] init]; mas01mj@705: NSString* importPath = [ [ NSBundle mainBundle ] pathForAuxiliaryExecutable: @"populate" ]; mas01mj@705: [importTask setLaunchPath:importPath]; mas01mj@700: args = [NSArray arrayWithObjects:featuresFileName, dbFilename, nil]; mas01mj@700: [importTask setArguments:args]; mas01mj@700: [importTask launch]; mas01mj@700: [importTask waitUntilExit]; mas01mj@700: [importTask release]; mas01mj@700: mas01mj@700: NSString* val = [filename retain]; mas01mj@700: NSString* key = [[filename lastPathComponent] retain]; mas01mj@700: mas01mj@700: // Update the plist store. mas01mj@700: [trackMap setValue:val forKey:key]; mas01mj@700: [dbState writeToFile:plistFilename atomically: YES]; mas01mj@700: mas01mj@705: mas01mj@700: } mas01mj@700: mas01mj@669: /** mas01mj@669: * Choose the file(s) to be imported. mas01mj@669: */ mas01mj@699: -(IBAction)importAudio:(id)sender mas01mj@669: { mas01mj@669: [tracksView reloadData]; mas01mj@669: mas01mj@739: NSArray *fileTypes = [NSArray arrayWithObjects:@"wav", @"mp3", @"aif", @"aiff", @"m4a",nil]; mas01mj@669: NSOpenPanel* panel = [NSOpenPanel openPanel]; mas01mj@669: [panel setAllowsMultipleSelection:TRUE]; mas01mj@669: NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes]; mas01mj@669: if(response == NSFileHandlingPanelOKButton) mas01mj@669: { mas01mj@699: [indicator startAnimation:self]; mas01mj@692: mas01mj@699: [NSApp beginSheet:importSheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil]; mas01mj@699: session = [NSApp beginModalSessionForWindow: importSheet]; mas01mj@699: [NSApp runModalSession:session]; mas01mj@669: mas01mj@669: NSArray *filesToOpen = [panel filenames]; mas01mj@669: mas01mj@699: NSString* extractor = [dbState objectForKey:@"extractor"]; mas01mj@705: mas01mj@705: NSLog([NSString stringWithFormat:@"rdf/%@.n3", extractor]); mas01mj@705: NSString* extractorPath = [ [ NSBundle mainBundle ] pathForResource:extractor ofType:@"n3" inDirectory:@"rdf"]; mas01mj@705: NSLog(@"Extractor path: %@", extractorPath); mas01mj@705: mas01mj@705: // NSString* extractorPath = [NSString stringWithFormat:@"/Applications/iAudioDB.app/rdf/%@.n3", extractor]; mas01mj@669: mas01mj@669: for(int i=0; i<[filesToOpen count]; i++) mas01mj@699: { mas01mj@699: audiodb_close(db); mas01mj@700: mas01mj@700: // Get the sample rate for the audio file mas01mj@700: mas01mj@700: [self importFile:[filesToOpen objectAtIndex:i] withExtractorConfig:extractorPath]; mas01mj@699: db = audiodb_open([dbFilename cStringUsingEncoding:NSUTF8StringEncoding], O_RDONLY); mas01mj@669: [self updateStatus]; mas01mj@669: } mas01mj@669: mas01mj@669: [NSApp endModalSession:session]; mas01mj@669: [importSheet orderOut:nil]; mas01mj@669: [NSApp endSheet:importSheet]; mas01mj@669: [indicator stopAnimation:self]; mas01mj@669: } mas01mj@669: } mas01mj@669: mas01mj@669: /** mas01mj@669: * Required table methods begin here. mas01mj@669: */ mas01mj@669: -(int)numberOfRowsInTableView:(NSTableView *)v mas01mj@669: { mas01mj@669: return [results count]; mas01mj@669: } mas01mj@669: mas01mj@669: /** mas01mj@669: * Return appropriate values - or the distance indicator if it's the meter column. mas01mj@669: */ mas01mj@669: -(id)tableView:(NSTableView *)v objectValueForTableColumn:(NSTableColumn *)tc row:(NSInteger)row mas01mj@669: { mas01mj@669: id result = [results objectAtIndex:row]; mas01mj@669: id value = [result objectForKey:[tc identifier]]; mas01mj@669: mas01mj@669: if([[tc identifier] isEqualToString:@"meter"]) mas01mj@669: { mas01mj@669: NSLevelIndicatorCell *distance = [[NSLevelIndicatorCell alloc] initWithLevelIndicatorStyle:NSRelevancyLevelIndicatorStyle]; mas01mj@699: [distance setFloatValue:10.0f-[(NSNumber*)value floatValue]*100.0f]; mas01mj@669: return distance; mas01mj@669: } mas01mj@669: else mas01mj@669: { mas01mj@669: return value; mas01mj@669: } mas01mj@669: } mas01mj@669: mas01mj@669: /** mas01mj@669: * Handle column sorting. mas01mj@669: */ mas01mj@669: - (void)tableView:(NSTableView *)v sortDescriptorsDidChange:(NSArray *)oldDescriptors mas01mj@669: { mas01mj@669: [results sortUsingDescriptors:[v sortDescriptors]]; mas01mj@669: [v reloadData]; mas01mj@669: } mas01mj@669: mas01mj@669: /** mas01mj@669: * Only enable the import menu option if a database is loaded. mas01mj@669: */ mas01mj@669: - (BOOL)validateUserInterfaceItem:(id )anItem mas01mj@669: { mas01mj@669: SEL theAction = [anItem action]; mas01mj@669: if (theAction == @selector(importAudio:)) mas01mj@669: { mas01mj@669: if(!db) mas01mj@669: { mas01mj@669: return NO; mas01mj@669: } mas01mj@669: } mas01mj@739: else if(theAction == @selector(playBoth:) || theAction == @selector(playResult:)) mas01mj@739: { mas01mj@739: if([tracksView numberOfSelectedRows] == 0) mas01mj@739: { mas01mj@739: return NO; mas01mj@739: } mas01mj@739: else mas01mj@739: { mas01mj@739: return YES; mas01mj@739: } mas01mj@739: } mas01mj@739: else if(theAction == @selector(stopPlay:)) mas01mj@739: { mas01mj@739: return NO; mas01mj@739: } mas01mj@739: mas01mj@739: NSLog(@"Returning yes for %@", NSStringFromSelector(theAction)); mas01mj@739: mas01mj@669: return YES; mas01mj@669: } mas01mj@669: mas01mj@669: /** mas01mj@669: * Ensure play buttons are only enabled if a track is selected. mas01mj@669: */ mas01mj@669: -(IBAction)selectedChanged:(id)sender mas01mj@669: { mas01mj@739: NSLog(@"Selection changed"); mas01mj@669: if([tracksView numberOfSelectedRows] == 0) mas01mj@669: { mas01mj@699: [playBothButton setEnabled:NO]; mas01mj@699: [playResultButton setEnabled:NO]; mas01mj@669: } mas01mj@669: else mas01mj@669: { mas01mj@699: [playBothButton setEnabled:YES]; mas01mj@699: [playResultButton setEnabled:YES]; mas01mj@669: } mas01mj@669: } mas01mj@669: mas01mj@669: /** mas01mj@669: * Play just the result track. mas01mj@669: */ mas01mj@669: -(IBAction)playResult:(id)sender mas01mj@669: { mas01mj@669: mas01mj@699: if([tracksView selectedRow] == -1) mas01mj@699: { mas01mj@699: return; mas01mj@699: } mas01mj@699: mas01mj@669: NSDictionary* selectedRow = [results objectAtIndex:[tracksView selectedRow]]; mas01mj@669: NSString* value = [selectedRow objectForKey:@"key"]; mas01mj@669: float ipos = [[selectedRow objectForKey:@"ipos"] floatValue]; mas01mj@669: NSString* filename = [trackMap objectForKey:value]; mas01mj@669: NSLog(@"Key: %@ Value: %@", value, filename); mas01mj@669: mas01mj@669: if(queryTrack) mas01mj@669: { mas01mj@669: if([queryTrack isPlaying]) mas01mj@669: { mas01mj@669: [queryTrack setDelegate:Nil]; mas01mj@669: [queryTrack stop]; mas01mj@669: } mas01mj@669: [queryTrack release]; mas01mj@699: queryTrack = Nil; mas01mj@669: } mas01mj@669: mas01mj@669: if(resultTrack) mas01mj@669: { mas01mj@669: if([resultTrack isPlaying]) mas01mj@669: { mas01mj@669: [resultTrack setDelegate:Nil]; mas01mj@669: [resultTrack stop]; mas01mj@669: } mas01mj@669: [resultTrack release]; mas01mj@699: resultTrack = Nil; mas01mj@669: } mas01mj@669: mas01mj@669: resultTrack = [[[NSSound alloc] initWithContentsOfFile:filename byReference:YES] retain]; mas01mj@669: [resultTrack setCurrentTime:ipos]; mas01mj@669: [resultTrack setDelegate:self]; mas01mj@669: [resultTrack play]; mas01mj@669: mas01mj@669: [stopButton setEnabled:YES]; mas01mj@669: } mas01mj@669: mas01mj@669: /** mas01mj@669: * Play the result and query simultaneously. mas01mj@669: */ mas01mj@669: -(IBAction)playBoth:(id)sender mas01mj@669: { mas01mj@669: mas01mj@669: NSDictionary* selectedRow = [results objectAtIndex:[tracksView selectedRow]]; mas01mj@669: NSString* value = [selectedRow objectForKey:@"key"]; mas01mj@669: float ipos = [[selectedRow objectForKey:@"ipos"] floatValue]; mas01mj@669: NSString* filename = [trackMap objectForKey:value]; mas01mj@669: NSLog(@"Key: %@ Value: %@", value, filename); mas01mj@669: mas01mj@669: if(queryTrack) mas01mj@669: { mas01mj@669: mas01mj@669: if([queryTrack isPlaying]) mas01mj@669: { mas01mj@669: [queryTrack setDelegate:Nil]; mas01mj@669: [queryTrack stop]; mas01mj@669: } mas01mj@669: [queryTrack release]; mas01mj@699: queryTrack = Nil; mas01mj@669: } mas01mj@669: if(resultTrack) mas01mj@669: { mas01mj@669: if([resultTrack isPlaying]) mas01mj@669: { mas01mj@669: [resultTrack setDelegate:Nil]; mas01mj@669: [resultTrack stop]; mas01mj@669: } mas01mj@669: [resultTrack release]; mas01mj@699: resultTrack = Nil; mas01mj@669: } mas01mj@669: mas01mj@669: // Get query track and shift to start point mas01mj@669: queryTrack = [[[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES] retain]; mas01mj@669: [queryTrack setDelegate:self]; mas01mj@669: mas01mj@669: [queryTrack play]; mas01mj@669: mas01mj@669: resultTrack = [[[NSSound alloc] initWithContentsOfFile:filename byReference:YES] retain]; mas01mj@669: [resultTrack setCurrentTime:ipos]; mas01mj@669: [resultTrack setDelegate:self]; mas01mj@669: [resultTrack play]; mas01mj@669: mas01mj@669: [stopButton setEnabled:YES]; mas01mj@669: } mas01mj@669: mas01mj@669: /** mas01mj@669: * Disable the stop button after playback of both tracks. mas01mj@669: */ mas01mj@669: - (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)playbackSuccessful mas01mj@669: { mas01mj@669: mas01mj@669: if((queryTrack && [queryTrack isPlaying]) || (resultTrack && [resultTrack isPlaying])) mas01mj@669: { mas01mj@669: return; mas01mj@669: } mas01mj@669: else mas01mj@669: { mas01mj@669: [stopButton setEnabled:NO]; mas01mj@669: } mas01mj@669: } mas01mj@669: mas01mj@669: /** mas01mj@669: * Stop playback. mas01mj@669: */ mas01mj@669: -(IBAction)stopPlay:(id)sender mas01mj@669: { mas01mj@669: if(queryTrack) mas01mj@669: { mas01mj@669: [queryTrack stop]; mas01mj@669: } mas01mj@669: if(resultTrack) mas01mj@669: { mas01mj@669: [resultTrack stop]; mas01mj@669: } mas01mj@669: } mas01mj@669: mas01mj@669: /** mas01mj@669: * Select an audio file, determine the key, and fire off a query. mas01mj@669: */ mas01mj@669: -(IBAction)chooseQuery:(id)sender mas01mj@669: { mas01mj@699: [queryButton setEnabled:(selectedKey ? YES : NO)]; mas01mj@699: [NSApp beginSheet:querySheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil]; mas01mj@699: session = [NSApp beginModalSessionForWindow:querySheet]; mas01mj@699: [NSApp runModalSession:session]; mas01mj@699: } mas01mj@699: mas01mj@699: mas01mj@699: -(IBAction)selectQueryFile:(id)sender mas01mj@699: { mas01mj@739: NSArray* fileTypes = [NSArray arrayWithObjects: @"wav", @"mp3", @"aif", @"aiff", @"m4a", nil]; mas01mj@669: NSOpenPanel* panel = [NSOpenPanel openPanel]; mas01mj@669: NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes]; mas01mj@669: if(response == NSFileHandlingPanelOKButton) mas01mj@669: { mas01mj@669: NSArray* opts = [trackMap allKeysForObject:[panel filename]]; mas01mj@669: if([opts count] != 1) mas01mj@669: { mas01mj@699: // TODO : Needs fixing! mas01mj@699: mas01mj@669: NSAlert *alert = [[[NSAlert alloc] init] autorelease]; mas01mj@669: [alert addButtonWithTitle:@"OK"]; mas01mj@669: [alert setMessageText:@"Track not found"]; mas01mj@669: [alert setInformativeText:@"Make sure you have specified a valid track identifier."]; mas01mj@669: [alert setAlertStyle:NSWarningAlertStyle]; mas01mj@669: [alert beginSheetModalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil]; mas01mj@669: } mas01mj@669: else mas01mj@669: { mas01mj@669: selectedKey = [opts objectAtIndex:0]; mas01mj@669: [queryKey setStringValue:selectedKey]; mas01mj@699: [queryPath setStringValue:selectedKey]; mas01mj@669: selectedFilename = [[panel filename] retain]; mas01mj@699: [queryButton setEnabled:YES]; mas01mj@699: mas01mj@699: [self resetLengths:self]; mas01mj@669: } mas01mj@669: } mas01mj@669: } mas01mj@669: mas01mj@699: -(IBAction)resetLengths:(id)sender mas01mj@699: { mas01mj@699: queryTrack = [[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES]; mas01mj@699: mas01mj@702: int sampleRate = [self getSampleRate:selectedFilename]; mas01mj@702: int hopSize = [self getHopSizeInSamples:selectedFilename]; mas01mj@702: int winSize = [self nearestPow2:(hopSize*8)]; mas01mj@702: mas01mj@702: double samples = ([queryTrack duration]*sampleRate); mas01mj@699: mas01mj@699: [queryLengthSeconds setDoubleValue:[queryTrack duration]]; mas01mj@699: [queryLengthVectors setDoubleValue:ceil((samples-winSize)/hopSize)]; mas01mj@699: mas01mj@699: // For now, go with 0 mas01mj@699: [queryStartSeconds setDoubleValue:0]; mas01mj@699: [queryStartVectors setDoubleValue:0]; mas01mj@699: mas01mj@699: [queryLengthSeconds setEnabled:YES]; mas01mj@699: [queryLengthVectors setEnabled:YES]; mas01mj@699: [queryStartSeconds setEnabled:YES]; mas01mj@699: [queryStartVectors setEnabled:YES]; mas01mj@699: [resetButton setEnabled:YES]; mas01mj@699: [multipleCheckBox setEnabled:YES]; mas01mj@702: [queryButton setEnabled:YES]; mas01mj@699: mas01mj@699: } mas01mj@699: mas01mj@699: - (void)controlTextDidChange:(NSNotification *)nd mas01mj@699: { mas01mj@699: NSTextField *ed = [nd object]; mas01mj@699: mas01mj@702: int sampleRate = [self getSampleRate:selectedFilename]; mas01mj@702: int hopSize = [self getHopSizeInSamples:selectedFilename]; mas01mj@702: int winSize = [self nearestPow2:(hopSize*8)]; mas01mj@699: mas01mj@699: if(!queryTrack) mas01mj@699: { mas01mj@699: queryTrack = [[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES]; mas01mj@699: } mas01mj@699: mas01mj@699: double totalDuration = [queryTrack duration]; mas01mj@702: double samples = totalDuration * sampleRate; mas01mj@699: double totalVectors = ceil((samples-winSize)/hopSize); mas01mj@699: mas01mj@702: mas01mj@699: double lengthSecs = [queryLengthSeconds doubleValue]; mas01mj@699: double startSecs = [queryStartSeconds doubleValue]; mas01mj@699: double lengthVectors = [queryLengthVectors doubleValue]; mas01mj@699: double startVectors = [queryStartVectors doubleValue]; mas01mj@699: mas01mj@699: // Query Length mas01mj@699: if (ed == queryLengthSeconds) mas01mj@699: { mas01mj@699: if(lengthSecs >= 0) mas01mj@699: { mas01mj@703: lengthVectors = ceil((((lengthSecs*sampleRate)-winSize)/hopSize)+1); mas01mj@699: if(lengthVectors < 0) {lengthVectors = 0; } mas01mj@699: [queryLengthVectors setDoubleValue:lengthVectors]; mas01mj@699: } mas01mj@699: } mas01mj@699: mas01mj@699: if (ed == queryLengthVectors) mas01mj@699: { mas01mj@699: if(lengthVectors >= 0) mas01mj@699: { mas01mj@703: lengthSecs = ((hopSize*(lengthVectors-1))+winSize)/sampleRate; mas01mj@699: if(lengthSecs < 0) { lengthSecs = 0; } mas01mj@699: [queryLengthSeconds setDoubleValue:lengthSecs]; mas01mj@699: } mas01mj@699: } mas01mj@699: mas01mj@699: // Query start mas01mj@699: if (ed == queryStartSeconds) mas01mj@699: { mas01mj@699: if(startSecs >= 0) mas01mj@699: { mas01mj@703: startVectors = ceil((startSecs*sampleRate)/hopSize); mas01mj@699: if(startVectors < 0) { startVectors = 0; } mas01mj@699: [queryStartVectors setDoubleValue:startVectors]; mas01mj@699: } mas01mj@699: } mas01mj@699: if (ed == queryStartVectors) mas01mj@699: { mas01mj@699: if(startVectors >= 0) mas01mj@699: { mas01mj@703: startSecs = (hopSize*startVectors)/sampleRate; mas01mj@699: if(startSecs < 0) { startSecs = 0; } mas01mj@699: [queryStartSeconds setDoubleValue:startSecs]; mas01mj@699: } mas01mj@699: } mas01mj@699: mas01mj@699: if((lengthSecs + startSecs) > totalDuration || (lengthVectors + startVectors) > totalVectors || lengthVectors == 0) mas01mj@699: { mas01mj@699: [queryButton setEnabled:NO]; mas01mj@699: } mas01mj@699: else if(![queryButton isEnabled]) mas01mj@699: { mas01mj@699: [queryButton setEnabled:YES]; mas01mj@699: } mas01mj@699: } mas01mj@699: mas01mj@699: -(IBAction)cancelQuery:(id)sender mas01mj@699: { mas01mj@699: [NSApp endModalSession:session]; mas01mj@699: [querySheet orderOut:nil]; mas01mj@699: [NSApp endSheet:querySheet]; mas01mj@699: } mas01mj@699: mas01mj@669: /** mas01mj@669: * Actually perform the query. TODO: Monolithic. mas01mj@669: */ mas01mj@699: -(IBAction)performQuery:(id)sender mas01mj@669: { mas01mj@699: [NSApp endModalSession:session]; mas01mj@699: [querySheet orderOut:nil]; mas01mj@699: [NSApp endSheet:querySheet]; mas01mj@699: mas01mj@669: NSLog(@"Perform query! %@, %@", selectedKey, selectedFilename); mas01mj@669: mas01mj@669: adb_query_spec_t *spec = (adb_query_spec_t *)malloc(sizeof(adb_query_spec_t)); mas01mj@669: spec->qid.datum = (adb_datum_t *)malloc(sizeof(adb_datum_t)); mas01mj@669: mas01mj@699: spec->qid.sequence_length = [queryLengthVectors doubleValue]; mas01mj@699: spec->qid.sequence_start = [queryStartVectors doubleValue]; mas01mj@699: spec->qid.flags = 0; mas01mj@699: // spec->qid.flags = spec->qid.flags | ADB_QID_FLAG_EXHAUSTIVE; mas01mj@692: mas01mj@669: spec->params.accumulation = ADB_ACCUMULATION_PER_TRACK; mas01mj@699: mas01mj@699: if([multipleCheckBox state] == NSOnState) mas01mj@699: { mas01mj@699: spec->params.npoints = 100; mas01mj@699: } mas01mj@699: else mas01mj@699: { mas01mj@699: spec->params.npoints = 1; mas01mj@699: } mas01mj@699: mas01mj@669: spec->params.distance = ADB_DISTANCE_EUCLIDEAN_NORMED; mas01mj@669: mas01mj@669: spec->params.ntracks = 100; mas01mj@699: //spec->refine.radius = 5.0; mas01mj@669: // spec->refine.absolute_threshold = -6; mas01mj@669: // spec->refine.relative_threshold = 10; mas01mj@669: // spec->refine.duration_ratio = 0; mas01mj@669: mas01mj@669: spec->refine.flags = 0; mas01mj@669: // spec->refine.flags |= ADB_REFINE_ABSOLUTE_THRESHOLD; mas01mj@669: // spec->refine.flags |= ADB_REFINE_RELATIVE_THRESHOLD; mas01mj@699: // spec->refine.flags |= ADB_REFINE_HOP_SIZE; mas01mj@699: //spec->refine.flags |= ADB_REFINE_RADIUS; mas01mj@669: mas01mj@669: adb_query_results_t *result = (adb_query_results_t *)malloc(sizeof(adb_query_results_t)); mas01mj@669: spec->qid.datum->data = NULL; mas01mj@669: spec->qid.datum->power = NULL; mas01mj@669: spec->qid.datum->times = NULL; mas01mj@669: mas01mj@669: [results removeAllObjects]; mas01mj@669: mas01mj@669: int ok = audiodb_retrieve_datum(db, [selectedKey cStringUsingEncoding:NSUTF8StringEncoding], spec->qid.datum); mas01mj@669: if(ok == 0) mas01mj@669: { mas01mj@699: mas01mj@739: // float hopSize = [[dbState objectForKey:@"hopsize"] floatValue]; mas01mj@669: NSLog(@"Got a datum"); mas01mj@669: result = audiodb_query_spec(db, spec); mas01mj@669: if(result == NULL) mas01mj@669: { mas01mj@669: mas01mj@669: NSLog(@"No results"); mas01mj@669: } mas01mj@669: else mas01mj@669: { mas01mj@702: mas01mj@699: NSLog(@"Populate table: %d", result->nresults); mas01mj@669: for(int i=0; inresults; i++) mas01mj@669: { mas01mj@699: mas01mj@702: NSString* filename = [trackMap objectForKey:[NSString stringWithFormat:@"%s", result->results[i].ikey]]; mas01mj@702: int sampleRate = [self getSampleRate:filename]; mas01mj@702: int hopSize = [self getHopSizeInSamples:filename]; mas01mj@702: float divisor = (sampleRate/hopSize); mas01mj@702: mas01mj@669: NSMutableDictionary* dict = [[NSMutableDictionary alloc] initWithCapacity:4]; mas01mj@699: [dict setValue:[NSString stringWithFormat:@"%s", result->results[i].ikey] forKey:@"key"]; mas01mj@669: [dict setValue:[NSNumber numberWithFloat:result->results[i].dist] forKey:@"distance"]; mas01mj@669: [dict setValue:[NSNumber numberWithFloat:result->results[i].dist] forKey:@"meter"]; mas01mj@699: [dict setValue:[NSNumber numberWithFloat:result->results[i].ipos/divisor] forKey:@"ipos"]; mas01mj@669: [results addObject: dict]; mas01mj@669: } mas01mj@669: } mas01mj@669: mas01mj@669: NSSortDescriptor *distSort = [[NSSortDescriptor alloc]initWithKey:@"meter" ascending:YES]; mas01mj@669: NSArray *distDescs = [NSArray arrayWithObject:distSort]; mas01mj@669: mas01mj@669: [results sortUsingDescriptors:distDescs]; mas01mj@669: [tracksView setSortDescriptors:distDescs]; mas01mj@669: [tracksView reloadData]; mas01mj@669: mas01mj@669: } mas01mj@669: else mas01mj@669: { mas01mj@669: NSAlert *alert = [[[NSAlert alloc] init] autorelease]; mas01mj@669: [alert addButtonWithTitle:@"OK"]; mas01mj@669: [alert setMessageText:@"Track not found"]; mas01mj@669: [alert setInformativeText:@"Make sure you have specified a valid track identifier."]; mas01mj@669: [alert setAlertStyle:NSWarningAlertStyle]; mas01mj@669: [alert beginSheetModalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil]; mas01mj@669: } mas01mj@669: // audiodb_query_free_results(db, spec, result); mas01mj@669: } mas01mj@669: mas01mj@669: @end