annotate examples/iAudioDB/AppController.m @ 709:362bae792124

* Added custom libraries (with relative lib paths) * Added custom binaries (with relative lib paths) * Added .adb extension to create dialogue
author mas01mj
date Fri, 18 Jun 2010 11:29:12 +0000
parents 6d3ed50c9d2d
children df850498486e
rev   line source
mas01mj@669 1 //
mas01mj@669 2 // AppController.m
mas01mj@706 3 // CAMUS
mas01mj@669 4 //
mas01mj@669 5 // Created by Mike Jewell on 27/01/2010.
mas01mj@669 6 // Copyright 2010 __MyCompanyName__. All rights reserved.
mas01mj@669 7 //
mas01mj@699 8 #import "AppController.h"
mas01mj@701 9 #import <AudioToolbox/AudioFile.h>
mas01mj@669 10
mas01mj@669 11
mas01mj@669 12 @implementation AppController
mas01mj@669 13
mas01mj@669 14 -(id)init
mas01mj@669 15 {
mas01mj@669 16 [super init];
mas01mj@669 17
mas01mj@669 18 // A max of 100 results.
mas01mj@669 19 results = [[NSMutableArray alloc] initWithCapacity: 100];
mas01mj@669 20
mas01mj@669 21 return self;
mas01mj@669 22 }
mas01mj@669 23
mas01mj@699 24 - (void)awakeFromNib {
mas01mj@699 25 [tracksView setTarget:self];
mas01mj@699 26 [tracksView setDoubleAction:@selector(tableDoubleClick:)];
mas01mj@699 27 [self updateStatus];
mas01mj@699 28 }
mas01mj@699 29
mas01mj@699 30
mas01mj@699 31 - (IBAction)tableDoubleClick:(id)sender
mas01mj@699 32 {
mas01mj@699 33 [self playResult:Nil];
mas01mj@699 34 // NSLog(@"Table double clicked");
mas01mj@699 35 }
mas01mj@699 36
mas01mj@669 37
mas01mj@669 38 /**
mas01mj@669 39 * Create a new database, given the selected filename.
mas01mj@669 40 */
mas01mj@669 41 -(IBAction)newDatabase:(id)sender
mas01mj@669 42 {
mas01mj@699 43
mas01mj@699 44 [NSApp beginSheet:createSheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
mas01mj@699 45 session = [NSApp beginModalSessionForWindow:createSheet];
mas01mj@699 46 [NSApp runModalSession:session];
mas01mj@699 47 }
mas01mj@699 48
mas01mj@699 49 /**
mas01mj@699 50 * Cancel the db creation (at configuration time).
mas01mj@699 51 */
mas01mj@699 52 -(IBAction)cancelCreate:(id)sender
mas01mj@699 53 {
mas01mj@699 54 [NSApp endModalSession:session];
mas01mj@699 55 [createSheet orderOut:nil];
mas01mj@699 56 [NSApp endSheet:createSheet];
mas01mj@699 57 }
mas01mj@699 58
mas01mj@699 59 -(IBAction)createDatabase:(id)sender
mas01mj@699 60 {
mas01mj@699 61 [self cancelCreate:self];
mas01mj@699 62
mas01mj@669 63 NSSavePanel* panel = [NSSavePanel savePanel];
mas01mj@709 64 [panel setCanSelectHiddenExtension:YES];
mas01mj@709 65 [panel setAllowedFileTypes:[NSArray arrayWithObjects:@"adb", nil]];
mas01mj@669 66 NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@""];
mas01mj@699 67
mas01mj@669 68 [results removeAllObjects];
mas01mj@669 69 [tracksView reloadData];
mas01mj@699 70
mas01mj@669 71 if(response == NSFileHandlingPanelOKButton)
mas01mj@669 72 {
mas01mj@699 73 // Work out which extractor to use
mas01mj@699 74 NSString* extractor = @"adb_chroma";
mas01mj@699 75 // TODO: This should be stored with the n3.
mas01mj@699 76 int dim;
mas01mj@699 77 switch([extractorOptions selectedTag])
mas01mj@685 78 {
mas01mj@699 79 case 0:
mas01mj@699 80 extractor = @"adb_chroma";
mas01mj@699 81 dim = 12;
mas01mj@699 82 break;
mas01mj@704 83 /* case 1:
mas01mj@699 84 extractor = @"adb_cq";
mas01mj@699 85 dim = 48;
mas01mj@699 86 break;
mas01mj@699 87 case 2:
mas01mj@699 88 extractor = @"qm_chroma";
mas01mj@699 89 dim = 12;
mas01mj@704 90 break;*/
mas01mj@704 91 case 1:
mas01mj@699 92 extractor = @"qm_mfcc";
mas01mj@699 93 dim = 12;
mas01mj@699 94 break;
mas01mj@685 95 }
mas01mj@685 96
mas01mj@699 97 // Calculate the max DB size
mas01mj@709 98 NSLog(@"Max length: %f", [maxLengthField doubleValue]);
mas01mj@709 99 NSLog(@"hop size: %f", [hopSizeField doubleValue]);
mas01mj@709 100 int vectors = ceil([maxLengthField doubleValue] / (([hopSizeField doubleValue] / 1000.0f)));
mas01mj@709 101 NSLog(@"Max Vectors: %d", vectors);
mas01mj@699 102 int numtracks = [maxTracksField intValue];
mas01mj@699 103 int datasize = ceil((numtracks * vectors * dim * 8.0f) / 1024.0f / 1024.0f); // In MB
mas01mj@685 104
mas01mj@699 105 [self reset];
mas01mj@699 106
mas01mj@669 107 // Create new db, and set flags.
mas01mj@699 108 db = audiodb_create([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], datasize, numtracks, dim);
mas01mj@669 109 audiodb_l2norm(db);
mas01mj@699 110
mas01mj@669 111 // Store useful paths.
mas01mj@669 112 dbName = [[[panel URL] relativePath] retain];
mas01mj@669 113 dbFilename = [[panel filename] retain];
mas01mj@669 114 plistFilename = [[NSString stringWithFormat:@"%@.plist", [dbFilename stringByDeletingPathExtension]] retain];
mas01mj@699 115
mas01mj@669 116 // Create the plist file (contains mapping from filename to key).
mas01mj@699 117 dbState = [[NSMutableDictionary alloc] init];
mas01mj@669 118 trackMap = [[NSMutableDictionary alloc] init];
mas01mj@699 119 [dbState setValue:trackMap forKey:@"tracks"];
mas01mj@699 120 [dbState setValue:extractor forKey:@"extractor"];
mas01mj@699 121 [dbState setValue:[hopSizeField stringValue] forKey:@"hopsize"];
mas01mj@699 122 [dbState writeToFile:plistFilename atomically:YES];
mas01mj@699 123
mas01mj@669 124 [queryKey setStringValue:@"None Selected"];
mas01mj@669 125 [self updateStatus];
mas01mj@669 126 }
mas01mj@669 127 }
mas01mj@669 128
mas01mj@699 129 -(void)reset
mas01mj@699 130 {
mas01mj@699 131 // Tidy any existing references up.
mas01mj@699 132 if(db)
mas01mj@699 133 {
mas01mj@699 134 NSLog(@"Close db");
mas01mj@699 135 audiodb_close(db);
mas01mj@699 136 }
mas01mj@699 137
mas01mj@699 138 if(dbFilename)
mas01mj@699 139 {
mas01mj@699 140 NSLog(@"Tidy up filenames");
mas01mj@699 141 [dbFilename release];
mas01mj@699 142 [dbName release];
mas01mj@699 143 [plistFilename release];
mas01mj@699 144 [trackMap release];
mas01mj@699 145 [dbState release];
mas01mj@699 146 }
mas01mj@699 147
mas01mj@699 148 if(selectedKey)
mas01mj@699 149 {
mas01mj@699 150 [selectedKey release];
mas01mj@699 151 selectedKey = Nil;
mas01mj@699 152 }
mas01mj@699 153
mas01mj@699 154 // Reset query flags
mas01mj@699 155 [queryPath setStringValue: @"No file selected"];
mas01mj@699 156 [queryLengthSeconds setDoubleValue:0];
mas01mj@699 157 [queryLengthVectors setDoubleValue:0];
mas01mj@699 158 [multipleCheckBox setState:NSOnState];
mas01mj@699 159 [queryStartSeconds setDoubleValue:0];
mas01mj@699 160 [queryStartVectors setDoubleValue:0];
mas01mj@699 161
mas01mj@699 162 [queryLengthSeconds setEnabled:NO];
mas01mj@699 163 [queryLengthVectors setEnabled:NO];
mas01mj@699 164 [queryStartSeconds setEnabled:NO];
mas01mj@699 165 [queryStartVectors setEnabled:NO];
mas01mj@699 166 [resetButton setEnabled:NO];
mas01mj@699 167 [multipleCheckBox setEnabled:NO];
mas01mj@699 168 }
mas01mj@699 169
mas01mj@669 170 /**
mas01mj@669 171 * Open an existing adb (which must have a plist)
mas01mj@669 172 */
mas01mj@669 173 -(IBAction)openDatabase:(id)sender
mas01mj@669 174 {
mas01mj@669 175 NSArray *fileTypes = [NSArray arrayWithObject:@"adb"];
mas01mj@669 176 NSOpenPanel* panel = [NSOpenPanel openPanel];
mas01mj@669 177 NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes];
mas01mj@669 178 if(response == NSFileHandlingPanelOKButton)
mas01mj@669 179 {
mas01mj@699 180 [self reset];
mas01mj@669 181
mas01mj@669 182 // Store useful paths.
mas01mj@699 183 NSLog(@"Open");
mas01mj@699 184 db = audiodb_open([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], O_RDONLY);
mas01mj@669 185 dbName = [[[panel URL] relativePath] retain];
mas01mj@669 186 dbFilename = [[panel filename] retain];
mas01mj@669 187
mas01mj@669 188 // TODO: Verify this exists!
mas01mj@669 189 plistFilename = [[NSString stringWithFormat:@"%@.plist", [dbFilename stringByDeletingPathExtension]] retain];
mas01mj@669 190
mas01mj@669 191 // Clear out any old results.
mas01mj@669 192 [results removeAllObjects];
mas01mj@669 193 [tracksView reloadData];
mas01mj@669 194
mas01mj@669 195 [queryKey setStringValue:@"None Selected"];
mas01mj@669 196
mas01mj@669 197 adb_liszt_results_t* liszt_results = audiodb_liszt(db);
mas01mj@669 198
mas01mj@669 199 for(int k=0; k<liszt_results->nresults; k++)
mas01mj@669 200 {
mas01mj@669 201 NSMutableString *trackVal = [[NSMutableString alloc] init];
mas01mj@669 202 [trackVal appendFormat:@"%s", liszt_results->entries[k].key];
mas01mj@669 203 }
mas01mj@669 204
mas01mj@669 205 audiodb_liszt_free_results(db, liszt_results);
mas01mj@699 206 dbState = [[[NSMutableDictionary alloc] initWithContentsOfFile:plistFilename] retain];
mas01mj@699 207 trackMap = [[dbState objectForKey:@"tracks"] retain];
mas01mj@699 208
mas01mj@699 209 [self updateStatus];
mas01mj@699 210
mas01mj@669 211 NSLog(@"Size: %d", [trackMap count]);
mas01mj@669 212 }
mas01mj@669 213 }
mas01mj@669 214
mas01mj@699 215 -(IBAction)pathAction:(id)sender
mas01mj@699 216 {
mas01mj@699 217 NSLog(@"Path action");
mas01mj@699 218 }
mas01mj@699 219
mas01mj@669 220 /**
mas01mj@669 221 * Update button states and status field based on current state.
mas01mj@669 222 */
mas01mj@669 223 -(void)updateStatus
mas01mj@669 224 {
mas01mj@699 225 NSLog(@"Update status");
mas01mj@669 226 if(db)
mas01mj@669 227 {
mas01mj@699 228 NSLog(@"Got a db");
mas01mj@699 229 adb_status_t *status = (adb_status_t *)malloc(sizeof(adb_status_t));
mas01mj@669 230 int flags;
mas01mj@669 231 flags = audiodb_status(db, status);
mas01mj@705 232 [statusField setStringValue: [NSString stringWithFormat:@"%@ Dim: %d Files: %d Slice: %@ms Ext: %@",
mas01mj@699 233 dbName,
mas01mj@699 234 status->dim,
mas01mj@699 235 status->numFiles,
mas01mj@699 236 [dbState objectForKey:@"hopsize"],
mas01mj@699 237 [dbState objectForKey:@"extractor"]]];
mas01mj@699 238 [performQueryButton setEnabled:YES];
mas01mj@699 239 [importAudioButton setEnabled:YES];
mas01mj@669 240 }
mas01mj@669 241 else
mas01mj@669 242 {
mas01mj@699 243 NSLog(@"No db");
mas01mj@699 244 [performQueryButton setEnabled:NO];
mas01mj@699 245 [importAudioButton setEnabled:NO];
mas01mj@699 246 [playBothButton setEnabled:NO];
mas01mj@699 247 [playResultButton setEnabled:NO];
mas01mj@699 248 [stopButton setEnabled:NO];
mas01mj@669 249 }
mas01mj@669 250 }
mas01mj@669 251
mas01mj@701 252 -(UInt64)getSampleRate:(NSString *)filename
mas01mj@701 253 {
mas01mj@701 254 AudioFileID audioFile;
mas01mj@701 255 AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:filename], 0x01, 0, &audioFile);
mas01mj@701 256
mas01mj@701 257 UInt32 propertySize;
mas01mj@701 258 UInt32 propertyIsWritable;
mas01mj@701 259 AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyDataFormat, &propertySize, &propertyIsWritable);
mas01mj@701 260
mas01mj@701 261 AudioStreamBasicDescription dataFormat;
mas01mj@701 262 AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &propertySize, &dataFormat);
mas01mj@701 263 Float64 sampleRate = dataFormat.mSampleRate;
mas01mj@701 264 AudioFileClose(audioFile);
mas01mj@701 265
mas01mj@701 266 return sampleRate;
mas01mj@701 267 }
mas01mj@701 268
mas01mj@702 269 -(UInt64)getHopSizeInSamples:(NSString *)filename
mas01mj@702 270 {
mas01mj@702 271 NSString* hopStr = [dbState objectForKey:@"hopsize"];
mas01mj@702 272 return round([self getSampleRate:filename] * ([hopStr doubleValue] / 1000));
mas01mj@702 273 }
mas01mj@702 274
mas01mj@702 275 -(int)nearestPow2:(int)x
mas01mj@702 276 {
mas01mj@702 277 if (x < 0)
mas01mj@702 278 return 0;
mas01mj@702 279 --x;
mas01mj@702 280 x |= x >> 1;
mas01mj@702 281 x |= x >> 2;
mas01mj@702 282 x |= x >> 4;
mas01mj@702 283 x |= x >> 8;
mas01mj@702 284 x |= x >> 16;
mas01mj@702 285 return x+1;
mas01mj@702 286 }
mas01mj@702 287
mas01mj@700 288 -(void)importFile:(NSString *)filename withExtractorConfig:(NSString *)extractorPath
mas01mj@700 289 {
mas01mj@700 290 // Create the extractor configuration
mas01mj@702 291 int hopSizeSamples = [self getHopSizeInSamples:filename];
mas01mj@702 292 int windowSizeSamples = [self nearestPow2:(hopSizeSamples*8)];
mas01mj@700 293
mas01mj@700 294 NSString* extractorContent = [NSString stringWithContentsOfFile:extractorPath];
mas01mj@702 295 NSString* newContent = [[extractorContent stringByReplacingOccurrencesOfString:@"HOP_SIZE" withString:[NSString stringWithFormat:@"%d", hopSizeSamples]]
mas01mj@702 296 stringByReplacingOccurrencesOfString:@"WINDOW_SIZE" withString:[NSString stringWithFormat:@"%d", windowSizeSamples]];
mas01mj@700 297 NSString* n3FileName = [NSTemporaryDirectory() stringByAppendingPathComponent:@"extractor_config.n3"];
mas01mj@702 298 NSLog(newContent);
mas01mj@700 299 NSError* error;
mas01mj@700 300 [newContent writeToFile:n3FileName atomically:YES encoding:NSASCIIStringEncoding error:&error];
mas01mj@700 301
mas01mj@700 302 // Create the temp file for the extracted features
mas01mj@700 303 NSString* tempFileTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"features.XXXXXX"];
mas01mj@700 304 const char* tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
mas01mj@700 305 char* tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
mas01mj@700 306 strcpy(tempFileNameCString, tempFileTemplateCString);
mas01mj@700 307 mktemp(tempFileNameCString);
mas01mj@700 308
mas01mj@700 309 NSString* featuresFileName = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
mas01mj@700 310 free(tempFileNameCString);
mas01mj@700 311
mas01mj@700 312 // Extract features with sonic-annotator
mas01mj@700 313 NSTask* task = [[NSTask alloc] init];
mas01mj@709 314 NSLog(@"Resource path: %@", [ [NSBundle mainBundle] resourcePath]);
mas01mj@709 315 NSString* pluginPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Vamp"];
mas01mj@709 316 NSString* extractPath = [ [ NSBundle mainBundle ] pathForAuxiliaryExecutable: @"sonic-annotator" ];
mas01mj@705 317
mas01mj@709 318 NSLog(@"Plugin path: %@", pluginPath);
mas01mj@709 319
mas01mj@709 320 NSDictionary *defaultEnvironment = [[NSProcessInfo processInfo] environment];
mas01mj@709 321 NSMutableDictionary *environment = [[NSMutableDictionary alloc] initWithDictionary:defaultEnvironment];
mas01mj@709 322 [environment setValue:pluginPath forKey:@"VAMP_PATH"];
mas01mj@709 323 NSLog(@"Env: %@", environment);
mas01mj@705 324 [task setLaunchPath:extractPath];
mas01mj@709 325 [task setEnvironment:environment];
mas01mj@709 326
mas01mj@705 327
mas01mj@700 328 NSArray* args;
mas01mj@700 329 args = [NSArray arrayWithObjects:@"-t", n3FileName, @"-w", @"rdf", @"-r", @"--rdf-network", @"--rdf-one-file", featuresFileName, @"--rdf-force", filename, nil];
mas01mj@700 330 [task setArguments:args];
mas01mj@700 331 [task launch];
mas01mj@700 332 [task waitUntilExit];
mas01mj@700 333 [task release];
mas01mj@700 334
mas01mj@700 335 // Populate the audioDB instance
mas01mj@700 336 NSTask* importTask = [[NSTask alloc] init];
mas01mj@705 337 NSString* importPath = [ [ NSBundle mainBundle ] pathForAuxiliaryExecutable: @"populate" ];
mas01mj@705 338 [importTask setLaunchPath:importPath];
mas01mj@700 339 args = [NSArray arrayWithObjects:featuresFileName, dbFilename, nil];
mas01mj@700 340 [importTask setArguments:args];
mas01mj@700 341 [importTask launch];
mas01mj@700 342 [importTask waitUntilExit];
mas01mj@700 343 [importTask release];
mas01mj@700 344
mas01mj@700 345 NSString* val = [filename retain];
mas01mj@700 346 NSString* key = [[filename lastPathComponent] retain];
mas01mj@700 347
mas01mj@700 348 // Update the plist store.
mas01mj@700 349 [trackMap setValue:val forKey:key];
mas01mj@700 350 [dbState writeToFile:plistFilename atomically: YES];
mas01mj@700 351
mas01mj@705 352
mas01mj@700 353 }
mas01mj@700 354
mas01mj@669 355 /**
mas01mj@669 356 * Choose the file(s) to be imported.
mas01mj@669 357 */
mas01mj@699 358 -(IBAction)importAudio:(id)sender
mas01mj@669 359 {
mas01mj@669 360 [tracksView reloadData];
mas01mj@669 361
mas01mj@669 362 NSArray *fileTypes = [NSArray arrayWithObject:@"wav"];
mas01mj@669 363 NSOpenPanel* panel = [NSOpenPanel openPanel];
mas01mj@669 364 [panel setAllowsMultipleSelection:TRUE];
mas01mj@669 365 NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes];
mas01mj@669 366 if(response == NSFileHandlingPanelOKButton)
mas01mj@669 367 {
mas01mj@699 368 [indicator startAnimation:self];
mas01mj@692 369
mas01mj@699 370 [NSApp beginSheet:importSheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
mas01mj@699 371 session = [NSApp beginModalSessionForWindow: importSheet];
mas01mj@699 372 [NSApp runModalSession:session];
mas01mj@669 373
mas01mj@669 374 NSArray *filesToOpen = [panel filenames];
mas01mj@669 375
mas01mj@699 376 NSString* extractor = [dbState objectForKey:@"extractor"];
mas01mj@705 377
mas01mj@705 378 NSLog([NSString stringWithFormat:@"rdf/%@.n3", extractor]);
mas01mj@705 379 NSString* extractorPath = [ [ NSBundle mainBundle ] pathForResource:extractor ofType:@"n3" inDirectory:@"rdf"];
mas01mj@705 380 NSLog(@"Extractor path: %@", extractorPath);
mas01mj@705 381
mas01mj@705 382 // NSString* extractorPath = [NSString stringWithFormat:@"/Applications/iAudioDB.app/rdf/%@.n3", extractor];
mas01mj@669 383
mas01mj@669 384 for(int i=0; i<[filesToOpen count]; i++)
mas01mj@699 385 {
mas01mj@699 386 audiodb_close(db);
mas01mj@700 387
mas01mj@700 388 // Get the sample rate for the audio file
mas01mj@700 389
mas01mj@700 390 [self importFile:[filesToOpen objectAtIndex:i] withExtractorConfig:extractorPath];
mas01mj@699 391 db = audiodb_open([dbFilename cStringUsingEncoding:NSUTF8StringEncoding], O_RDONLY);
mas01mj@669 392 [self updateStatus];
mas01mj@669 393 }
mas01mj@669 394
mas01mj@669 395 [NSApp endModalSession:session];
mas01mj@669 396 [importSheet orderOut:nil];
mas01mj@669 397 [NSApp endSheet:importSheet];
mas01mj@669 398 [indicator stopAnimation:self];
mas01mj@669 399 }
mas01mj@669 400 }
mas01mj@669 401
mas01mj@669 402 /**
mas01mj@669 403 * Required table methods begin here.
mas01mj@669 404 */
mas01mj@669 405 -(int)numberOfRowsInTableView:(NSTableView *)v
mas01mj@669 406 {
mas01mj@669 407 return [results count];
mas01mj@669 408 }
mas01mj@669 409
mas01mj@669 410 /**
mas01mj@669 411 * Return appropriate values - or the distance indicator if it's the meter column.
mas01mj@669 412 */
mas01mj@669 413 -(id)tableView:(NSTableView *)v objectValueForTableColumn:(NSTableColumn *)tc row:(NSInteger)row
mas01mj@669 414 {
mas01mj@669 415 id result = [results objectAtIndex:row];
mas01mj@669 416 id value = [result objectForKey:[tc identifier]];
mas01mj@669 417
mas01mj@669 418 if([[tc identifier] isEqualToString:@"meter"])
mas01mj@669 419 {
mas01mj@669 420 NSLevelIndicatorCell *distance = [[NSLevelIndicatorCell alloc] initWithLevelIndicatorStyle:NSRelevancyLevelIndicatorStyle];
mas01mj@699 421 [distance setFloatValue:10.0f-[(NSNumber*)value floatValue]*100.0f];
mas01mj@669 422 return distance;
mas01mj@669 423 }
mas01mj@669 424 else
mas01mj@669 425 {
mas01mj@669 426 return value;
mas01mj@669 427 }
mas01mj@669 428 }
mas01mj@669 429
mas01mj@669 430 /**
mas01mj@669 431 * Handle column sorting.
mas01mj@669 432 */
mas01mj@669 433 - (void)tableView:(NSTableView *)v sortDescriptorsDidChange:(NSArray *)oldDescriptors
mas01mj@669 434 {
mas01mj@669 435 [results sortUsingDescriptors:[v sortDescriptors]];
mas01mj@669 436 [v reloadData];
mas01mj@669 437 }
mas01mj@669 438
mas01mj@669 439 /**
mas01mj@669 440 * Only enable the import menu option if a database is loaded.
mas01mj@669 441 */
mas01mj@669 442 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
mas01mj@669 443 {
mas01mj@669 444 SEL theAction = [anItem action];
mas01mj@669 445 if (theAction == @selector(importAudio:))
mas01mj@669 446 {
mas01mj@669 447 if(!db)
mas01mj@669 448 {
mas01mj@669 449 return NO;
mas01mj@669 450 }
mas01mj@669 451 }
mas01mj@669 452 return YES;
mas01mj@669 453 }
mas01mj@669 454
mas01mj@669 455 /**
mas01mj@669 456 * Ensure play buttons are only enabled if a track is selected.
mas01mj@669 457 */
mas01mj@669 458 -(IBAction)selectedChanged:(id)sender
mas01mj@669 459 {
mas01mj@669 460 if([tracksView numberOfSelectedRows] == 0)
mas01mj@669 461 {
mas01mj@699 462 [playBothButton setEnabled:NO];
mas01mj@699 463 [playResultButton setEnabled:NO];
mas01mj@669 464 }
mas01mj@669 465 else
mas01mj@669 466 {
mas01mj@699 467 [playBothButton setEnabled:YES];
mas01mj@699 468 [playResultButton setEnabled:YES];
mas01mj@669 469 }
mas01mj@669 470 }
mas01mj@669 471
mas01mj@669 472 /**
mas01mj@669 473 * Play just the result track.
mas01mj@669 474 */
mas01mj@669 475 -(IBAction)playResult:(id)sender
mas01mj@669 476 {
mas01mj@669 477
mas01mj@699 478 if([tracksView selectedRow] == -1)
mas01mj@699 479 {
mas01mj@699 480 return;
mas01mj@699 481 }
mas01mj@699 482
mas01mj@669 483 NSDictionary* selectedRow = [results objectAtIndex:[tracksView selectedRow]];
mas01mj@669 484 NSString* value = [selectedRow objectForKey:@"key"];
mas01mj@669 485 float ipos = [[selectedRow objectForKey:@"ipos"] floatValue];
mas01mj@669 486 NSString* filename = [trackMap objectForKey:value];
mas01mj@669 487 NSLog(@"Key: %@ Value: %@", value, filename);
mas01mj@669 488
mas01mj@669 489 if(queryTrack)
mas01mj@669 490 {
mas01mj@669 491 if([queryTrack isPlaying])
mas01mj@669 492 {
mas01mj@669 493 [queryTrack setDelegate:Nil];
mas01mj@669 494 [queryTrack stop];
mas01mj@669 495 }
mas01mj@669 496 [queryTrack release];
mas01mj@699 497 queryTrack = Nil;
mas01mj@669 498 }
mas01mj@669 499
mas01mj@669 500 if(resultTrack)
mas01mj@669 501 {
mas01mj@669 502 if([resultTrack isPlaying])
mas01mj@669 503 {
mas01mj@669 504 [resultTrack setDelegate:Nil];
mas01mj@669 505 [resultTrack stop];
mas01mj@669 506 }
mas01mj@669 507 [resultTrack release];
mas01mj@699 508 resultTrack = Nil;
mas01mj@669 509 }
mas01mj@669 510
mas01mj@669 511 resultTrack = [[[NSSound alloc] initWithContentsOfFile:filename byReference:YES] retain];
mas01mj@669 512 [resultTrack setCurrentTime:ipos];
mas01mj@669 513 [resultTrack setDelegate:self];
mas01mj@669 514 [resultTrack play];
mas01mj@669 515
mas01mj@669 516 [stopButton setEnabled:YES];
mas01mj@669 517 }
mas01mj@669 518
mas01mj@669 519 /**
mas01mj@669 520 * Play the result and query simultaneously.
mas01mj@669 521 */
mas01mj@669 522 -(IBAction)playBoth:(id)sender
mas01mj@669 523 {
mas01mj@669 524
mas01mj@669 525 NSDictionary* selectedRow = [results objectAtIndex:[tracksView selectedRow]];
mas01mj@669 526 NSString* value = [selectedRow objectForKey:@"key"];
mas01mj@669 527 float ipos = [[selectedRow objectForKey:@"ipos"] floatValue];
mas01mj@669 528 NSString* filename = [trackMap objectForKey:value];
mas01mj@669 529 NSLog(@"Key: %@ Value: %@", value, filename);
mas01mj@669 530
mas01mj@669 531 if(queryTrack)
mas01mj@669 532 {
mas01mj@669 533
mas01mj@669 534 if([queryTrack isPlaying])
mas01mj@669 535 {
mas01mj@669 536 [queryTrack setDelegate:Nil];
mas01mj@669 537 [queryTrack stop];
mas01mj@669 538 }
mas01mj@669 539 [queryTrack release];
mas01mj@699 540 queryTrack = Nil;
mas01mj@669 541 }
mas01mj@669 542 if(resultTrack)
mas01mj@669 543 {
mas01mj@669 544 if([resultTrack isPlaying])
mas01mj@669 545 {
mas01mj@669 546 [resultTrack setDelegate:Nil];
mas01mj@669 547 [resultTrack stop];
mas01mj@669 548 }
mas01mj@669 549 [resultTrack release];
mas01mj@699 550 resultTrack = Nil;
mas01mj@669 551 }
mas01mj@669 552
mas01mj@669 553 // Get query track and shift to start point
mas01mj@669 554 queryTrack = [[[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES] retain];
mas01mj@669 555 [queryTrack setDelegate:self];
mas01mj@669 556
mas01mj@669 557 [queryTrack play];
mas01mj@669 558
mas01mj@669 559 resultTrack = [[[NSSound alloc] initWithContentsOfFile:filename byReference:YES] retain];
mas01mj@669 560 [resultTrack setCurrentTime:ipos];
mas01mj@669 561 [resultTrack setDelegate:self];
mas01mj@669 562 [resultTrack play];
mas01mj@669 563
mas01mj@669 564 [stopButton setEnabled:YES];
mas01mj@669 565 }
mas01mj@669 566
mas01mj@669 567 /**
mas01mj@669 568 * Disable the stop button after playback of both tracks.
mas01mj@669 569 */
mas01mj@669 570 - (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)playbackSuccessful
mas01mj@669 571 {
mas01mj@669 572
mas01mj@669 573 if((queryTrack && [queryTrack isPlaying]) || (resultTrack && [resultTrack isPlaying]))
mas01mj@669 574 {
mas01mj@669 575 return;
mas01mj@669 576 }
mas01mj@669 577 else
mas01mj@669 578 {
mas01mj@669 579 [stopButton setEnabled:NO];
mas01mj@669 580 }
mas01mj@669 581 }
mas01mj@669 582
mas01mj@669 583 /**
mas01mj@669 584 * Stop playback.
mas01mj@669 585 */
mas01mj@669 586 -(IBAction)stopPlay:(id)sender
mas01mj@669 587 {
mas01mj@669 588 if(queryTrack)
mas01mj@669 589 {
mas01mj@669 590 [queryTrack stop];
mas01mj@669 591 }
mas01mj@669 592 if(resultTrack)
mas01mj@669 593 {
mas01mj@669 594 [resultTrack stop];
mas01mj@669 595 }
mas01mj@669 596 }
mas01mj@669 597
mas01mj@669 598 /**
mas01mj@669 599 * Select an audio file, determine the key, and fire off a query.
mas01mj@669 600 */
mas01mj@669 601 -(IBAction)chooseQuery:(id)sender
mas01mj@669 602 {
mas01mj@699 603 [queryButton setEnabled:(selectedKey ? YES : NO)];
mas01mj@699 604 [NSApp beginSheet:querySheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
mas01mj@699 605 session = [NSApp beginModalSessionForWindow:querySheet];
mas01mj@699 606 [NSApp runModalSession:session];
mas01mj@699 607 }
mas01mj@699 608
mas01mj@699 609
mas01mj@699 610 -(IBAction)selectQueryFile:(id)sender
mas01mj@699 611 {
mas01mj@669 612 NSArray* fileTypes = [NSArray arrayWithObject:@"wav"];
mas01mj@669 613 NSOpenPanel* panel = [NSOpenPanel openPanel];
mas01mj@669 614 NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes];
mas01mj@669 615 if(response == NSFileHandlingPanelOKButton)
mas01mj@669 616 {
mas01mj@669 617 NSArray* opts = [trackMap allKeysForObject:[panel filename]];
mas01mj@669 618 if([opts count] != 1)
mas01mj@669 619 {
mas01mj@699 620 // TODO : Needs fixing!
mas01mj@699 621
mas01mj@669 622 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
mas01mj@669 623 [alert addButtonWithTitle:@"OK"];
mas01mj@669 624 [alert setMessageText:@"Track not found"];
mas01mj@669 625 [alert setInformativeText:@"Make sure you have specified a valid track identifier."];
mas01mj@669 626 [alert setAlertStyle:NSWarningAlertStyle];
mas01mj@669 627 [alert beginSheetModalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
mas01mj@669 628 }
mas01mj@669 629 else
mas01mj@669 630 {
mas01mj@669 631 selectedKey = [opts objectAtIndex:0];
mas01mj@669 632 [queryKey setStringValue:selectedKey];
mas01mj@699 633 [queryPath setStringValue:selectedKey];
mas01mj@669 634 selectedFilename = [[panel filename] retain];
mas01mj@699 635 [queryButton setEnabled:YES];
mas01mj@699 636
mas01mj@699 637 [self resetLengths:self];
mas01mj@669 638 }
mas01mj@669 639 }
mas01mj@669 640 }
mas01mj@669 641
mas01mj@699 642 -(IBAction)resetLengths:(id)sender
mas01mj@699 643 {
mas01mj@699 644 queryTrack = [[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES];
mas01mj@699 645
mas01mj@702 646 int sampleRate = [self getSampleRate:selectedFilename];
mas01mj@702 647 int hopSize = [self getHopSizeInSamples:selectedFilename];
mas01mj@702 648 int winSize = [self nearestPow2:(hopSize*8)];
mas01mj@702 649
mas01mj@702 650 double samples = ([queryTrack duration]*sampleRate);
mas01mj@699 651
mas01mj@699 652 [queryLengthSeconds setDoubleValue:[queryTrack duration]];
mas01mj@699 653 [queryLengthVectors setDoubleValue:ceil((samples-winSize)/hopSize)];
mas01mj@699 654
mas01mj@699 655 // For now, go with 0
mas01mj@699 656 [queryStartSeconds setDoubleValue:0];
mas01mj@699 657 [queryStartVectors setDoubleValue:0];
mas01mj@699 658
mas01mj@699 659 [queryLengthSeconds setEnabled:YES];
mas01mj@699 660 [queryLengthVectors setEnabled:YES];
mas01mj@699 661 [queryStartSeconds setEnabled:YES];
mas01mj@699 662 [queryStartVectors setEnabled:YES];
mas01mj@699 663 [resetButton setEnabled:YES];
mas01mj@699 664 [multipleCheckBox setEnabled:YES];
mas01mj@702 665 [queryButton setEnabled:YES];
mas01mj@699 666
mas01mj@699 667 }
mas01mj@699 668
mas01mj@699 669 - (void)controlTextDidChange:(NSNotification *)nd
mas01mj@699 670 {
mas01mj@699 671 NSTextField *ed = [nd object];
mas01mj@699 672
mas01mj@702 673 int sampleRate = [self getSampleRate:selectedFilename];
mas01mj@702 674 int hopSize = [self getHopSizeInSamples:selectedFilename];
mas01mj@702 675 int winSize = [self nearestPow2:(hopSize*8)];
mas01mj@699 676
mas01mj@699 677 if(!queryTrack)
mas01mj@699 678 {
mas01mj@699 679 queryTrack = [[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES];
mas01mj@699 680 }
mas01mj@699 681
mas01mj@699 682 double totalDuration = [queryTrack duration];
mas01mj@702 683 double samples = totalDuration * sampleRate;
mas01mj@699 684 double totalVectors = ceil((samples-winSize)/hopSize);
mas01mj@699 685
mas01mj@702 686
mas01mj@699 687 double lengthSecs = [queryLengthSeconds doubleValue];
mas01mj@699 688 double startSecs = [queryStartSeconds doubleValue];
mas01mj@699 689 double lengthVectors = [queryLengthVectors doubleValue];
mas01mj@699 690 double startVectors = [queryStartVectors doubleValue];
mas01mj@699 691
mas01mj@699 692 // Query Length
mas01mj@699 693 if (ed == queryLengthSeconds)
mas01mj@699 694 {
mas01mj@699 695 if(lengthSecs >= 0)
mas01mj@699 696 {
mas01mj@703 697 lengthVectors = ceil((((lengthSecs*sampleRate)-winSize)/hopSize)+1);
mas01mj@699 698 if(lengthVectors < 0) {lengthVectors = 0; }
mas01mj@699 699 [queryLengthVectors setDoubleValue:lengthVectors];
mas01mj@699 700 }
mas01mj@699 701 }
mas01mj@699 702
mas01mj@699 703 if (ed == queryLengthVectors)
mas01mj@699 704 {
mas01mj@699 705 if(lengthVectors >= 0)
mas01mj@699 706 {
mas01mj@703 707 lengthSecs = ((hopSize*(lengthVectors-1))+winSize)/sampleRate;
mas01mj@699 708 if(lengthSecs < 0) { lengthSecs = 0; }
mas01mj@699 709 [queryLengthSeconds setDoubleValue:lengthSecs];
mas01mj@699 710 }
mas01mj@699 711 }
mas01mj@699 712
mas01mj@699 713 // Query start
mas01mj@699 714 if (ed == queryStartSeconds)
mas01mj@699 715 {
mas01mj@699 716 if(startSecs >= 0)
mas01mj@699 717 {
mas01mj@703 718 startVectors = ceil((startSecs*sampleRate)/hopSize);
mas01mj@699 719 if(startVectors < 0) { startVectors = 0; }
mas01mj@699 720 [queryStartVectors setDoubleValue:startVectors];
mas01mj@699 721 }
mas01mj@699 722 }
mas01mj@699 723 if (ed == queryStartVectors)
mas01mj@699 724 {
mas01mj@699 725 if(startVectors >= 0)
mas01mj@699 726 {
mas01mj@703 727 startSecs = (hopSize*startVectors)/sampleRate;
mas01mj@699 728 if(startSecs < 0) { startSecs = 0; }
mas01mj@699 729 [queryStartSeconds setDoubleValue:startSecs];
mas01mj@699 730 }
mas01mj@699 731 }
mas01mj@699 732
mas01mj@699 733 if((lengthSecs + startSecs) > totalDuration || (lengthVectors + startVectors) > totalVectors || lengthVectors == 0)
mas01mj@699 734 {
mas01mj@699 735 [queryButton setEnabled:NO];
mas01mj@699 736 }
mas01mj@699 737 else if(![queryButton isEnabled])
mas01mj@699 738 {
mas01mj@699 739 [queryButton setEnabled:YES];
mas01mj@699 740 }
mas01mj@699 741 }
mas01mj@699 742
mas01mj@699 743 -(IBAction)cancelQuery:(id)sender
mas01mj@699 744 {
mas01mj@699 745 [NSApp endModalSession:session];
mas01mj@699 746 [querySheet orderOut:nil];
mas01mj@699 747 [NSApp endSheet:querySheet];
mas01mj@699 748 }
mas01mj@699 749
mas01mj@669 750 /**
mas01mj@669 751 * Actually perform the query. TODO: Monolithic.
mas01mj@669 752 */
mas01mj@699 753 -(IBAction)performQuery:(id)sender
mas01mj@669 754 {
mas01mj@699 755 [NSApp endModalSession:session];
mas01mj@699 756 [querySheet orderOut:nil];
mas01mj@699 757 [NSApp endSheet:querySheet];
mas01mj@699 758
mas01mj@669 759 NSLog(@"Perform query! %@, %@", selectedKey, selectedFilename);
mas01mj@669 760
mas01mj@669 761 adb_query_spec_t *spec = (adb_query_spec_t *)malloc(sizeof(adb_query_spec_t));
mas01mj@669 762 spec->qid.datum = (adb_datum_t *)malloc(sizeof(adb_datum_t));
mas01mj@669 763
mas01mj@699 764 spec->qid.sequence_length = [queryLengthVectors doubleValue];
mas01mj@699 765 spec->qid.sequence_start = [queryStartVectors doubleValue];
mas01mj@699 766 spec->qid.flags = 0;
mas01mj@699 767 // spec->qid.flags = spec->qid.flags | ADB_QID_FLAG_EXHAUSTIVE;
mas01mj@692 768
mas01mj@669 769 spec->params.accumulation = ADB_ACCUMULATION_PER_TRACK;
mas01mj@699 770
mas01mj@699 771 if([multipleCheckBox state] == NSOnState)
mas01mj@699 772 {
mas01mj@699 773 spec->params.npoints = 100;
mas01mj@699 774 }
mas01mj@699 775 else
mas01mj@699 776 {
mas01mj@699 777 spec->params.npoints = 1;
mas01mj@699 778 }
mas01mj@699 779
mas01mj@669 780 spec->params.distance = ADB_DISTANCE_EUCLIDEAN_NORMED;
mas01mj@669 781
mas01mj@669 782 spec->params.ntracks = 100;
mas01mj@699 783 //spec->refine.radius = 5.0;
mas01mj@669 784 // spec->refine.absolute_threshold = -6;
mas01mj@669 785 // spec->refine.relative_threshold = 10;
mas01mj@669 786 // spec->refine.duration_ratio = 0;
mas01mj@669 787
mas01mj@669 788 spec->refine.flags = 0;
mas01mj@669 789 // spec->refine.flags |= ADB_REFINE_ABSOLUTE_THRESHOLD;
mas01mj@669 790 // spec->refine.flags |= ADB_REFINE_RELATIVE_THRESHOLD;
mas01mj@699 791 // spec->refine.flags |= ADB_REFINE_HOP_SIZE;
mas01mj@699 792 //spec->refine.flags |= ADB_REFINE_RADIUS;
mas01mj@669 793
mas01mj@669 794 adb_query_results_t *result = (adb_query_results_t *)malloc(sizeof(adb_query_results_t));
mas01mj@669 795 spec->qid.datum->data = NULL;
mas01mj@669 796 spec->qid.datum->power = NULL;
mas01mj@669 797 spec->qid.datum->times = NULL;
mas01mj@669 798
mas01mj@669 799 [results removeAllObjects];
mas01mj@669 800
mas01mj@669 801 int ok = audiodb_retrieve_datum(db, [selectedKey cStringUsingEncoding:NSUTF8StringEncoding], spec->qid.datum);
mas01mj@669 802 if(ok == 0)
mas01mj@669 803 {
mas01mj@699 804
mas01mj@699 805 float hopSize = [[dbState objectForKey:@"hopsize"] floatValue];
mas01mj@669 806 NSLog(@"Got a datum");
mas01mj@669 807 result = audiodb_query_spec(db, spec);
mas01mj@669 808 if(result == NULL)
mas01mj@669 809 {
mas01mj@669 810
mas01mj@669 811 NSLog(@"No results");
mas01mj@669 812 }
mas01mj@669 813 else
mas01mj@669 814 {
mas01mj@702 815
mas01mj@699 816 NSLog(@"Populate table: %d", result->nresults);
mas01mj@669 817 for(int i=0; i<result->nresults; i++)
mas01mj@669 818 {
mas01mj@699 819
mas01mj@702 820 NSString* filename = [trackMap objectForKey:[NSString stringWithFormat:@"%s", result->results[i].ikey]];
mas01mj@702 821 int sampleRate = [self getSampleRate:filename];
mas01mj@702 822 int hopSize = [self getHopSizeInSamples:filename];
mas01mj@702 823 float divisor = (sampleRate/hopSize);
mas01mj@702 824
mas01mj@669 825 NSMutableDictionary* dict = [[NSMutableDictionary alloc] initWithCapacity:4];
mas01mj@699 826 [dict setValue:[NSString stringWithFormat:@"%s", result->results[i].ikey] forKey:@"key"];
mas01mj@669 827 [dict setValue:[NSNumber numberWithFloat:result->results[i].dist] forKey:@"distance"];
mas01mj@669 828 [dict setValue:[NSNumber numberWithFloat:result->results[i].dist] forKey:@"meter"];
mas01mj@699 829 [dict setValue:[NSNumber numberWithFloat:result->results[i].ipos/divisor] forKey:@"ipos"];
mas01mj@699 830 NSLog(@"%s ipos: %d, dist: %f", result->results[i].ikey,result->results[i].ipos, result->results[i].dist);
mas01mj@669 831 [results addObject: dict];
mas01mj@669 832 }
mas01mj@669 833 }
mas01mj@669 834
mas01mj@669 835 NSSortDescriptor *distSort = [[NSSortDescriptor alloc]initWithKey:@"meter" ascending:YES];
mas01mj@669 836 NSArray *distDescs = [NSArray arrayWithObject:distSort];
mas01mj@669 837
mas01mj@669 838 [results sortUsingDescriptors:distDescs];
mas01mj@669 839 [tracksView setSortDescriptors:distDescs];
mas01mj@669 840 [tracksView reloadData];
mas01mj@669 841
mas01mj@669 842 }
mas01mj@669 843 else
mas01mj@669 844 {
mas01mj@669 845 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
mas01mj@669 846 [alert addButtonWithTitle:@"OK"];
mas01mj@669 847 [alert setMessageText:@"Track not found"];
mas01mj@669 848 [alert setInformativeText:@"Make sure you have specified a valid track identifier."];
mas01mj@669 849 [alert setAlertStyle:NSWarningAlertStyle];
mas01mj@669 850 [alert beginSheetModalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
mas01mj@669 851 }
mas01mj@669 852 // audiodb_query_free_results(db, spec, result);
mas01mj@669 853 }
mas01mj@669 854
mas01mj@669 855 @end