comparison examples/iAudioDB/AppController.m @ 669:780ebab29268

Added initial increment of OSX audioDB interface
author mas01mj
date Wed, 03 Mar 2010 17:17:08 +0000
parents
children 15e71890b584
comparison
equal deleted inserted replaced
668:ce5ff00168e1 669:780ebab29268
1 //
2 // AppController.m
3 // iAudioDB
4 //
5 // Created by Mike Jewell on 27/01/2010.
6 // Copyright 2010 __MyCompanyName__. All rights reserved.
7 //
8
9 #import "AppController.h"
10
11
12 @implementation AppController
13
14 -(id)init
15 {
16 [super init];
17
18 // A max of 100 results.
19 results = [[NSMutableArray alloc] initWithCapacity: 100];
20
21
22 return self;
23 }
24
25
26 /**
27 * Create a new database, given the selected filename.
28 */
29 -(IBAction)newDatabase:(id)sender
30 {
31 NSSavePanel* panel = [NSSavePanel savePanel];
32 NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@""];
33
34 [results removeAllObjects];
35 [tracksView reloadData];
36
37 if(response == NSFileHandlingPanelOKButton)
38 {
39 // TODO: Refactor this into a 'tidy' method.
40 // Tidy any existing references up.
41 if(db)
42 {
43 audiodb_close(db);
44 }
45
46 if(dbFilename)
47 {
48 [dbFilename release];
49 [dbName release];
50 [plistFilename release];
51 }
52
53 // Create new db, and set flags.
54 db = audiodb_create([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], 0, 0, 0);
55 audiodb_l2norm(db);
56 audiodb_power(db);
57
58 // Store useful paths.
59 dbName = [[[panel URL] relativePath] retain];
60 dbFilename = [[panel filename] retain];
61 plistFilename = [[NSString stringWithFormat:@"%@.plist", [dbFilename stringByDeletingPathExtension]] retain];
62
63 // Create the plist file (contains mapping from filename to key).
64 trackMap = [[NSMutableDictionary alloc] init];
65 [trackMap writeToFile:plistFilename atomically:YES];
66
67 [queryKey setStringValue:@"None Selected"];
68 [self updateStatus];
69 }
70 }
71
72 /**
73 * Open an existing adb (which must have a plist)
74 */
75 -(IBAction)openDatabase:(id)sender
76 {
77 NSArray *fileTypes = [NSArray arrayWithObject:@"adb"];
78 NSOpenPanel* panel = [NSOpenPanel openPanel];
79 NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes];
80 if(response == NSFileHandlingPanelOKButton)
81 {
82 // Tidy any existing references up.
83 if(db)
84 {
85 audiodb_close(db);
86 }
87
88 if(dbFilename)
89 {
90 [dbFilename release];
91 [dbName release];
92 [plistFilename release];
93 }
94
95 // Store useful paths.
96 db = audiodb_open([[panel filename] cStringUsingEncoding:NSUTF8StringEncoding], O_RDWR);
97 dbName = [[[panel URL] relativePath] retain];
98 dbFilename = [[panel filename] retain];
99
100 // TODO: Verify this exists!
101 plistFilename = [[NSString stringWithFormat:@"%@.plist", [dbFilename stringByDeletingPathExtension]] retain];
102
103 // Clear out any old results.
104 [results removeAllObjects];
105 [tracksView reloadData];
106
107 [queryKey setStringValue:@"None Selected"];
108 [self updateStatus];
109
110 adb_liszt_results_t* liszt_results = audiodb_liszt(db);
111
112 for(int k=0; k<liszt_results->nresults; k++)
113 {
114 NSMutableString *trackVal = [[NSMutableString alloc] init];
115 [trackVal appendFormat:@"%s", liszt_results->entries[k].key];
116 }
117
118 audiodb_liszt_free_results(db, liszt_results);
119 trackMap = [[[NSMutableDictionary alloc] initWithContentsOfFile:plistFilename] retain];
120 NSLog(@"Size: %d", [trackMap count]);
121 }
122 }
123
124 /**
125 * Update button states and status field based on current state.
126 */
127 -(void)updateStatus
128 {
129 if(db)
130 {
131 adb_status_ptr status = (adb_status_ptr)malloc(sizeof(struct adbstatus));
132 int flags;
133 flags = audiodb_status(db, status);
134 [statusField setStringValue: [NSString stringWithFormat:@"Database: %@ Dimensions: %d Files: %d", dbName, status->dim, status->numFiles]];
135 [chooseButton setEnabled:YES];
136 }
137 else
138 {
139 [chooseButton setEnabled:NO];
140 [playBothButton setEnabled:FALSE];
141 [playResultButton setEnabled:FALSE];
142 }
143 }
144
145 /**
146 * Get user's import choices.
147 */
148 -(IBAction)importAudio:(id)sender
149 {
150 [NSApp beginSheet:importSheet modalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
151 session = [NSApp beginModalSessionForWindow: importSheet];
152 [NSApp runModalSession:session];
153 }
154
155 /**
156 * Cancel the import (at configuration time).
157 */
158 -(IBAction)cancelImport:(id)sender;
159 {
160 [NSApp endModalSession:session];
161 [importSheet orderOut:nil];
162 [NSApp endSheet:importSheet];
163 }
164
165 /**
166 * Choose the file(s) to be imported.
167 * TODO: Currently handles the import process too - split this off.
168 */
169 -(IBAction)selectFiles:(id)sender
170 {
171 [tracksView reloadData];
172
173 NSArray *fileTypes = [NSArray arrayWithObject:@"wav"];
174 NSOpenPanel* panel = [NSOpenPanel openPanel];
175 [panel setAllowsMultipleSelection:TRUE];
176 NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes];
177 if(response == NSFileHandlingPanelOKButton)
178 {
179 NSRect newFrame;
180
181 [extractingBox setHidden:FALSE];
182 newFrame.origin.x = [importSheet frame].origin.x;
183 newFrame.origin.y = [importSheet frame].origin.y - [extractingBox frame].size.height;
184 newFrame.size.width = [importSheet frame].size.width;
185 newFrame.size.height = [importSheet frame].size.height + [extractingBox frame].size.height;
186
187 [indicator startAnimation:self];
188 [importSheet setFrame:newFrame display:YES animate:YES];
189
190 NSArray *filesToOpen = [panel filenames];
191
192 NSLog(@"Begin import");
193
194 // Work out which extractor to use
195 NSString* extractor = @"chromagram";
196 switch([extractorOptions selectedTag])
197 {
198 case 0:
199 extractor = @"mfcc";
200 break;
201 case 1:
202 extractor = @"chromagram";
203 break;
204 }
205
206 for(int i=0; i<[filesToOpen count]; i++)
207 {
208 // First extract powers
209
210 NSString *tempFileTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"powers.XXXXXX"];
211 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
212 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
213 strcpy(tempFileNameCString, tempFileTemplateCString);
214 mktemp(tempFileNameCString);
215
216 NSString* powersFileName = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
217 free(tempFileNameCString);
218
219 NSTask *task = [[NSTask alloc] init];
220 [task setLaunchPath:@"/usr/local/bin/fftExtract2"];
221 NSArray *args = [NSArray arrayWithObjects:@"-P", @"-h", @"11025", @"-w", @"16384", @"-n", @"32768", @"-i", @"1000", [filesToOpen objectAtIndex:i], powersFileName, nil];
222 [task setArguments:args];
223 [task launch];
224 [task waitUntilExit];
225 [task release];
226
227 // Then features
228
229 tempFileTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"features.XXXXXX"];
230 tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
231 tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
232 strcpy(tempFileNameCString, tempFileTemplateCString);
233 mktemp(tempFileNameCString);
234
235 NSString* featuresFileName = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
236 free(tempFileNameCString);
237
238 task = [[NSTask alloc] init];
239
240 [task setLaunchPath:@"/usr/local/bin/fftExtract2"];
241
242 NSArray *args2;
243
244 // Choose the args (TODO: This should use sonic annotator eventually)
245 if([extractor isEqualToString:@"chromagram"])
246 {
247 args2 = [NSArray arrayWithObjects:@"-p",@"/Users/moj/planfile",@"-c", @"36", @"-h", @"11025", @"-w", @"16384", @"-n", @"32768", @"-i", @"1000", [filesToOpen objectAtIndex:i], featuresFileName, nil];
248 }
249 else
250 {
251 args2 = [NSArray arrayWithObjects:@"-p",@"/Users/moj/planfile",@"-m", @"13", @"-h", @"11025", @"-w", @"16384", @"-n ", @"32768", @"-i", @"1000", [filesToOpen objectAtIndex:i], featuresFileName, nil];
252 }
253 [task setArguments:args2];
254 [task launch];
255 [task waitUntilExit];
256 [task release];
257
258 NSString* val = [[filesToOpen objectAtIndex:i] retain];
259 NSString* key = [[[filesToOpen objectAtIndex:i] lastPathComponent] retain];
260
261 adb_insert_t insert;
262 insert.features = [featuresFileName cStringUsingEncoding:NSUTF8StringEncoding];
263 insert.power = [powersFileName cStringUsingEncoding:NSUTF8StringEncoding];
264 insert.times = NULL;
265 insert.key = [key cStringUsingEncoding:NSUTF8StringEncoding];
266
267 // Insert into db.
268 if(audiodb_insert(db, &insert))
269 {
270 // TODO: Show an error message.
271 NSLog(@"Weep: %@ %@ %@", featuresFileName, powersFileName, key);
272 continue;
273 }
274
275 // Update the plist store.
276 [trackMap setValue:val forKey:key];
277 [trackMap writeToFile:plistFilename atomically: YES];
278
279 [self updateStatus];
280 }
281
282 newFrame.origin.x = [importSheet frame].origin.x;
283 newFrame.origin.y = [importSheet frame].origin.y + [extractingBox frame].size.height;
284 newFrame.size.width = [importSheet frame].size.width;
285 newFrame.size.height = [importSheet frame].size.height - [extractingBox frame].size.height;
286
287 [importSheet setFrame:newFrame display:YES animate:YES];
288
289 [NSApp endModalSession:session];
290 [importSheet orderOut:nil];
291 [NSApp endSheet:importSheet];
292 [indicator stopAnimation:self];
293 [extractingBox setHidden:TRUE];
294 }
295 }
296
297 /**
298 * Required table methods begin here.
299 */
300 -(int)numberOfRowsInTableView:(NSTableView *)v
301 {
302 return [results count];
303 }
304
305 /**
306 * Return appropriate values - or the distance indicator if it's the meter column.
307 */
308 -(id)tableView:(NSTableView *)v objectValueForTableColumn:(NSTableColumn *)tc row:(NSInteger)row
309 {
310 id result = [results objectAtIndex:row];
311 id value = [result objectForKey:[tc identifier]];
312
313 if([[tc identifier] isEqualToString:@"meter"])
314 {
315 NSLevelIndicatorCell *distance = [[NSLevelIndicatorCell alloc] initWithLevelIndicatorStyle:NSRelevancyLevelIndicatorStyle];
316 [distance setFloatValue:10-[(NSNumber*)value floatValue]*100];
317 return distance;
318 }
319 else
320 {
321 return value;
322 }
323 }
324
325 /**
326 * Handle column sorting.
327 */
328 - (void)tableView:(NSTableView *)v sortDescriptorsDidChange:(NSArray *)oldDescriptors
329 {
330 [results sortUsingDescriptors:[v sortDescriptors]];
331 [v reloadData];
332 }
333
334 /**
335 * Only enable the import menu option if a database is loaded.
336 */
337 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
338 {
339 SEL theAction = [anItem action];
340 if (theAction == @selector(importAudio:))
341 {
342 if(!db)
343 {
344 return NO;
345 }
346 }
347 return YES;
348 }
349
350 /**
351 * Ensure play buttons are only enabled if a track is selected.
352 */
353 -(IBAction)selectedChanged:(id)sender
354 {
355 if([tracksView numberOfSelectedRows] == 0)
356 {
357 [playBothButton setEnabled:FALSE];
358 [playResultButton setEnabled:FALSE];
359 }
360 else
361 {
362 [playBothButton setEnabled:TRUE];
363 [playResultButton setEnabled:TRUE];
364 }
365 }
366
367 /**
368 * Play just the result track.
369 */
370 -(IBAction)playResult:(id)sender
371 {
372
373 NSDictionary* selectedRow = [results objectAtIndex:[tracksView selectedRow]];
374 NSString* value = [selectedRow objectForKey:@"key"];
375 float ipos = [[selectedRow objectForKey:@"ipos"] floatValue];
376 NSString* filename = [trackMap objectForKey:value];
377 NSLog(@"Key: %@ Value: %@", value, filename);
378
379 if(queryTrack)
380 {
381 if([queryTrack isPlaying])
382 {
383 [queryTrack setDelegate:Nil];
384 [queryTrack stop];
385 }
386 [queryTrack release];
387 }
388
389 if(resultTrack)
390 {
391 if([resultTrack isPlaying])
392 {
393 [resultTrack setDelegate:Nil];
394 [resultTrack stop];
395 }
396 [resultTrack release];
397 }
398
399 resultTrack = [[[NSSound alloc] initWithContentsOfFile:filename byReference:YES] retain];
400 [resultTrack setCurrentTime:ipos];
401 [resultTrack setDelegate:self];
402 [resultTrack play];
403
404 [stopButton setEnabled:YES];
405 }
406
407 /**
408 * Play the result and query simultaneously.
409 */
410 -(IBAction)playBoth:(id)sender
411 {
412
413 NSDictionary* selectedRow = [results objectAtIndex:[tracksView selectedRow]];
414 NSString* value = [selectedRow objectForKey:@"key"];
415 float ipos = [[selectedRow objectForKey:@"ipos"] floatValue];
416 float qpos = [[selectedRow objectForKey:@"qpos"] floatValue];
417 NSString* filename = [trackMap objectForKey:value];
418 NSLog(@"Key: %@ Value: %@", value, filename);
419
420 if(queryTrack)
421 {
422
423 if([queryTrack isPlaying])
424 {
425 [queryTrack setDelegate:Nil];
426 [queryTrack stop];
427 }
428 [queryTrack release];
429 }
430 if(resultTrack)
431 {
432 if([resultTrack isPlaying])
433 {
434 [resultTrack setDelegate:Nil];
435 [resultTrack stop];
436 }
437 [resultTrack release];
438 }
439
440 // Get query track and shift to start point
441 queryTrack = [[[NSSound alloc] initWithContentsOfFile:selectedFilename byReference:YES] retain];
442 [queryTrack setCurrentTime:qpos];
443 [queryTrack setDelegate:self];
444
445 [queryTrack play];
446
447 resultTrack = [[[NSSound alloc] initWithContentsOfFile:filename byReference:YES] retain];
448 [resultTrack setCurrentTime:ipos];
449 [resultTrack setDelegate:self];
450 [resultTrack play];
451
452 [stopButton setEnabled:YES];
453 }
454
455 /**
456 * Disable the stop button after playback of both tracks.
457 */
458 - (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)playbackSuccessful
459 {
460
461 if((queryTrack && [queryTrack isPlaying]) || (resultTrack && [resultTrack isPlaying]))
462 {
463 return;
464 }
465 else
466 {
467 [stopButton setEnabled:NO];
468 }
469 }
470
471 /**
472 * Stop playback.
473 */
474 -(IBAction)stopPlay:(id)sender
475 {
476 if(queryTrack)
477 {
478 [queryTrack stop];
479 }
480 if(resultTrack)
481 {
482 [resultTrack stop];
483 }
484 }
485
486 /**
487 * Select an audio file, determine the key, and fire off a query.
488 */
489 -(IBAction)chooseQuery:(id)sender
490 {
491 NSArray* fileTypes = [NSArray arrayWithObject:@"wav"];
492 NSOpenPanel* panel = [NSOpenPanel openPanel];
493 NSInteger response = [panel runModalForDirectory:NSHomeDirectory() file:@"" types:fileTypes];
494 if(response == NSFileHandlingPanelOKButton)
495 {
496 NSLog(@"%@", [panel filename]);
497 // Grab key
498 NSArray* opts = [trackMap allKeysForObject:[panel filename]];
499 if([opts count] != 1)
500 {
501 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
502 [alert addButtonWithTitle:@"OK"];
503 [alert setMessageText:@"Track not found"];
504 [alert setInformativeText:@"Make sure you have specified a valid track identifier."];
505 [alert setAlertStyle:NSWarningAlertStyle];
506 [alert beginSheetModalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
507 }
508 else
509 {
510 selectedKey = [opts objectAtIndex:0];
511 [queryKey setStringValue:selectedKey];
512 selectedFilename = [[panel filename] retain];
513 [self performQuery];
514 }
515 }
516 }
517
518 /**
519 * Actually perform the query. TODO: Monolithic.
520 */
521 -(void)performQuery
522 {
523 NSLog(@"Perform query! %@, %@", selectedKey, selectedFilename);
524
525 adb_query_spec_t *spec = (adb_query_spec_t *)malloc(sizeof(adb_query_spec_t));
526 spec->qid.datum = (adb_datum_t *)malloc(sizeof(adb_datum_t));
527
528 spec->qid.sequence_length = 20;
529 spec->qid.sequence_start = 0;
530 spec->qid.flags = 0;
531
532 // spec->qid.flags = spec->qid.flags | ADB_QID_FLAG_EXHAUSTIVE;
533 spec->params.accumulation = ADB_ACCUMULATION_PER_TRACK;
534 spec->params.distance = ADB_DISTANCE_EUCLIDEAN_NORMED;
535
536 spec->params.npoints = 1;
537 spec->params.ntracks = 100;
538 //spec->refine.radius = 5.0;
539 spec->refine.hopsize = 1;
540 // spec->refine.absolute_threshold = -6;
541 // spec->refine.relative_threshold = 10;
542 // spec->refine.duration_ratio = 0;
543
544 spec->refine.flags = 0;
545 // spec->refine.flags |= ADB_REFINE_ABSOLUTE_THRESHOLD;
546 // spec->refine.flags |= ADB_REFINE_RELATIVE_THRESHOLD;
547 spec->refine.flags |= ADB_REFINE_HOP_SIZE;
548 //spec->refine.flags |= ADB_REFINE_RADIUS;
549
550 adb_query_results_t *result = (adb_query_results_t *)malloc(sizeof(adb_query_results_t));
551 spec->qid.datum->data = NULL;
552 spec->qid.datum->power = NULL;
553 spec->qid.datum->times = NULL;
554
555 [results removeAllObjects];
556
557 int ok = audiodb_retrieve_datum(db, [selectedKey cStringUsingEncoding:NSUTF8StringEncoding], spec->qid.datum);
558 if(ok == 0)
559 {
560 NSLog(@"Got a datum");
561 result = audiodb_query_spec(db, spec);
562 if(result == NULL)
563 {
564
565 NSLog(@"No results");
566 }
567 else
568 {
569 for(int i=0; i<result->nresults; i++)
570 {
571 NSMutableDictionary* dict = [[NSMutableDictionary alloc] initWithCapacity:4];
572 [dict setValue:[NSString stringWithFormat:@"%s", result->results[i].key] forKey:@"key"];
573 [dict setValue:[NSNumber numberWithFloat:result->results[i].dist] forKey:@"distance"];
574 [dict setValue:[NSNumber numberWithFloat:result->results[i].dist] forKey:@"meter"];
575 [dict setValue:[NSNumber numberWithFloat:result->results[i].qpos/4] forKey:@"qpos"];
576 [dict setValue:[NSNumber numberWithFloat:result->results[i].ipos/4] forKey:@"ipos"];
577 NSLog(@"%s qpos %d ipos %d", result->results[i].key, result->results[i].qpos/4, result->results[i].ipos/4);
578 [results addObject: dict];
579 }
580 }
581
582 NSSortDescriptor *distSort = [[NSSortDescriptor alloc]initWithKey:@"meter" ascending:YES];
583 NSArray *distDescs = [NSArray arrayWithObject:distSort];
584
585 [results sortUsingDescriptors:distDescs];
586 [tracksView setSortDescriptors:distDescs];
587 [tracksView reloadData];
588
589 }
590 else
591 {
592 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
593 [alert addButtonWithTitle:@"OK"];
594 [alert setMessageText:@"Track not found"];
595 [alert setInformativeText:@"Make sure you have specified a valid track identifier."];
596 [alert setAlertStyle:NSWarningAlertStyle];
597 [alert beginSheetModalForWindow:mainWindow modalDelegate:self didEndSelector:NULL contextInfo:nil];
598 }
599 // audiodb_query_free_results(db, spec, result);
600 }
601
602 @end