annotate examples/iAudioDB/AppController.m @ 770:c54bc2ffbf92 tip

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