#import "LEGAudioController.h" @implementation LEGAudioController // deallocation - (void)dealloc { [lCardControls release]; [speechSynth release]; [speechRecog release]; [super dealloc]; } - (void)awakeFromNib { // instance vars lCardSoc = NULL; isLCardAvailable = NO; isAnimatedNow = NO; isAnalyzing = NO; frameCnt = 0; prevNote = Noise; lCardControls = [[NSArray alloc] initWithObjects:animateBtn, fwdBtn, leftBtn, rightBtn, stopBtn, nil]; speechSynth = [[NSSpeechSynthesizer alloc] init]; speechRecog = [[NSSpeechRecognizer alloc] init]; [speechRecog setCommands:[NSArray arrayWithObjects:@"Access", @"Run", @"Left", @"Right", @"Stop", nil]]; [speechRecog setDelegate:self]; [speechRecog startListening]; cameraTimer = nil; audioAnalyzer = [[AudioAnalyzer alloc] init]; // enable multithreading if (![NSThread isMultiThreaded]) { [NSThread detachNewThreadSelector:@selector(exitDummyThread:) toTarget:self withObject:nil]; } } - (void)exitDummyThread:(id)arg { } - (IBAction)accessLCard:(id)sender { NSString *hostStr = [addressField stringValue]; CFStreamClientContext myContext = {0, self, NULL, NULL, NULL}; if (isAnimatedNow) [self animateCameraImage:self]; if (lCardSoc != NULL) { CFWriteStreamClose(lCardSoc); lCardSoc = NULL; } isLCardAvailable = NO; CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)hostStr, LC_PORT, NULL, &lCardSoc); if (lCardSoc == NULL) { NSLog(@"Could not create a socket to L-Card on %@.", hostStr); } CFWriteStreamSetClient(lCardSoc, kCFStreamEventOpenCompleted | kCFStreamEventErrorOccurred, &handleWriteStreamEvents, &myContext); CFWriteStreamScheduleWithRunLoop(lCardSoc, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); if (CFWriteStreamOpen(lCardSoc) == TRUE) { NSURL *lCardURL = [[NSURL alloc] initWithScheme:@"http" host:hostStr path:CAM_CGI]; NSImage *cameraImage = [[NSImage alloc] initWithContentsOfURL:lCardURL]; if (cameraImage == nil) { [speechSynth startSpeakingString:@"L-Card seems available, but the camera is not."]; } else { [cameraView setImage:cameraImage]; [self animateCameraImage:self]; } } else { [speechSynth startSpeakingString:@"The socket for L-Card cannot be opened."]; } [self populateLCardAvailability]; } - (IBAction)animateCameraImage:(id)sender { if (cameraTimer) [cameraTimer invalidate]; cameraTimer = nil; if (isAnimatedNow) { isAnimatedNow = NO; } else if (isLCardAvailable) { isAnimatedNow = YES; cameraTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateCameraImage:) userInfo:nil repeats:YES]; } } - (IBAction)fwdLCard:(id)sender { [self sendCommand:@"01\n"]; } - (IBAction)leftLCard:(id)sender { [self sendCommand:@"02\n"]; } - (IBAction)rightLCard:(id)sender { [self sendCommand:@"03\n"]; } - (IBAction)stopLCard:(id)sender { [self sendCommand:@"04\n"]; } - (BOOL)sendCommand:(NSString *)cmd { if (isLCardAvailable) { const UInt8 *buf = [cmd cString]; CFWriteStreamWrite(lCardSoc, buf, strlen(buf)); } } - (void)populateLCardAvailability { id theControl; NSEnumerator *controlEnum = [lCardControls objectEnumerator]; while (theControl = [controlEnum nextObject]) { [theControl setEnabled:isLCardAvailable]; } } - (void)updateCameraImage:(NSTimer *)theTimer { if (isLCardAvailable) { NSURL *lCardURL = [[NSURL alloc] initWithScheme:@"http" host:[addressField stringValue] path:[NSString stringWithFormat:@"%@&%d", CAM_CGI, ++frameCnt]]; NSImage *cameraImage = [[NSImage alloc] initWithContentsOfURL:lCardURL]; if (cameraImage != nil) { [cameraView setImage:cameraImage]; } } else { [cameraTimer invalidate]; cameraTimer = nil; } } - (BOOL)checkLCardAvailability { BOOL res; if (lCardSoc == NULL) { res = NO; } else if (CFWriteStreamGetStatus(lCardSoc) == kCFStreamStatusOpen) { res = YES; } else if (CFWriteStreamGetStatus(lCardSoc) == kCFStreamStatusError) { CFStreamError error = CFWriteStreamGetError(lCardSoc); NSString *domain, *desc = [NSString stringWithFormat:@"code=%d", error.error]; if (error.domain == kCFStreamErrorDomainPOSIX) { domain = @"POSIX"; desc = [NSString stringWithCString:strerror(error.error)]; } else if (error.domain == kCFStreamErrorDomainSOCKS) domain = @"SOCKS"; else if (error.domain == kCFStreamErrorDomainMacOSStatus) domain = @"MacOSStatus"; else if (error.domain == kCFStreamErrorDomainNetDB) domain = @"NetDB"; else if (error.domain == kCFStreamErrorDomainNetServices) domain = @"NetServices"; else domain = @"other"; NSLog(@"L-Card unavailable: domain=%@, %@.", domain, desc); [speechSynth startSpeakingString:@"L-Card is not available."]; CFWriteStreamClose(lCardSoc); lCardSoc = NULL; res = NO; } isLCardAvailable = res; [self populateLCardAvailability]; return res; } // spech recognizer delegate method - (void)speechRecognizer:(NSSpeechRecognizer *)sender didRecognizeCommand:(id)aCmd { if ([aCmd isEqualTo:@"Access"]) { [self accessLCard:self]; } else if ([aCmd isEqualTo:@"Run"]) { [self fwdLCard:self]; } else if ([aCmd isEqualTo:@"Left"]) { [self leftLCard:self]; } else if ([aCmd isEqualTo:@"Right"]) { [self rightLCard:self]; } else if ([aCmd isEqualTo:@"Stop"]) { [self stopLCard:self]; } } // audio things forwarding - (IBAction)startAudioLog:(id)sender { if (audioAnalyzeTimer) { [audioAnalyzeTimer invalidate]; audioAnalyzeTimer = nil; } if (isAnalyzing) { [audioAnalyzer stop]; [audioAnalyzeItem setTitle:@"Record"]; isAnalyzing = NO; } else { [audioAnalyzer start]; [audioAnalyzeItem setTitle:@"Stop"]; isAnalyzing = YES; audioAnalyzeTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(newAnalysis:) userInfo:nil repeats:YES]; } } - (IBAction)adjustNoise:(id)sender { [audioAnalyzer adjustNoise]; } - (void)newAnalysis:(NSTimer*)timer { NoteName newNote = [audioAnalyzer dominantNote]; if (newNote != prevNote) { prevNote = newNote; switch (newNote) { case A : [noteField setStringValue:@"A"]; [self fwdLCard:self]; break; case B : [noteField setStringValue:@"B"]; [self fwdLCard:self]; break; case C : [noteField setStringValue:@"C"]; [self leftLCard:self]; break; case D : [noteField setStringValue:@"D"]; [self rightLCard:self]; break; case E : [noteField setStringValue:@"E"]; [self rightLCard:self]; break; case F : [noteField setStringValue:@"F"]; [self rightLCard:self]; break; case G : [noteField setStringValue:@"G"]; [self stopLCard:self]; break; default : [noteField setStringValue:@"Noise"]; } } } - (IBAction)reportWaveForm:(id)sender { [audioAnalyzer reportResult]; } @end void handleWriteStreamEvents(CFWriteStreamRef stream, CFStreamEventType event, void *myPtr) { LEGAudioController *myController = (LEGAudioController*)myPtr; [myController checkLCardAvailability]; return; }