annotate examples/iAudioDB/AppController.m @ 701:e21abbac820e

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