source: orbit/iOS/Orbit/Orbit/SignalConverter.m @ 84e25a1

Servo
Last change on this file since 84e25a1 was 84e25a1, checked in by Steve Castellotti <sc@…>, 9 years ago
  • Property mode set to 100644
File size: 11.9 KB
RevLine 
[ab9d63b]1//
2//  SignalConverter.m
3//  orbit
4//
5//  Copyright (c) 2013 Puzzlebox Productions, LLC. All rights reserved.
[6db346c]6//  Originally created by Jonathon Horsman.
7//
8//  This code is released under the GNU Public License (GPL) version 2
9//  For more information please refer to http://www.gnu.org/copyleft/gpl.html
[ab9d63b]10//
11
12#import "SignalConverter.h"
13
[fc960da]14#import "HistoryAppDataObject.h"
15#import "AppDelegateProtocol.h"
16
[ab9d63b]17#define AUDIO_FILE_NAME @"throttle_hover_ios.wav" // @"iOS_noflip.wav"
18#define POOR_SIGNAL_KEY @"poorSignal"
19#define ATTENTION_KEY @"eSenseAttention"
20#define MEDITATION_KEY @"eSenseMeditation"
21
[9015b1e]22#define CHANNEL_A 1
23
[ab9d63b]24// Converts signals received from the EEG headset to the audio played to fly the helicopter.
25//
26@implementation SignalConverter {
27    // these 2 arrays hold the values of the thrust which should be applied to the helicopter
28    // (by way of volume level through the headphones) at each level of attention and meditation.
29    // this could be calculated on the fly, but because it happens ~20 times per second, the
30    // better option is to store all the values in arrays, and recalculate when the sliders are changed.
31    float attentionPower[101];
32    float meditationPower[101];
33   
34    int signalStrength, attentionLevel, meditationLevel; // the latest readings from the headset
[ce0a7ee]35    int yaw, throttle, pitch;
[2246ba0]36   
37    int historyAttention[30];
38    int historyMeditation[30];
[fc960da]39    int historyIndex;
[2246ba0]40   
[ab9d63b]41}
42
[de10cbc]43@synthesize attentionThreshold, meditationThreshold, running, testing;
[ab9d63b]44
[9015b1e]45
46- (id) init
47{
48    self = [super init];
49    if (self)
50    {
51        [[TGAccessoryManager sharedTGAccessoryManager] setupManagerWithInterval:0.05];
52        [[TGAccessoryManager sharedTGAccessoryManager] setDelegate:self];
[ad40faa]53        // Generate Signal
54        //        audioPlayer = [[AudioGenerator alloc] init];
[ce0a7ee]55       
56        // defaults
57        throttle = 80;
58        yaw = 78;
59        pitch = 31;
[359a504]60       
[fc960da]61        historyIndex = 0;
[2246ba0]62       
[359a504]63        // initialise the audio session - this should only be done once
64        AudioSessionInitialize(NULL, NULL, NULL, NULL);
[7945ecb]65        AudioSessionSetActive(YES);
[c9b9edd]66       
67#if USE_AUDIO_GENERATOR
68        audioPlayer = [[AudioGenerator alloc] init];
69#endif
[9015b1e]70    }
71    return self;
72}
73
[fc960da]74#pragma mark -
75#pragma mark instance methods
76
77- (HistoryAppDataObject*) theAppDataObject;
78{
79    id<AppDelegateProtocol> theDelegate = (id<AppDelegateProtocol>) [UIApplication sharedApplication].delegate;
80    HistoryAppDataObject* theDataObject;
81    theDataObject = (HistoryAppDataObject*) theDelegate.theAppDataObject;
82    return theDataObject;
83}
84
[ab9d63b]85- (void) setValuesForAttention:(float) attention meditation:(float) meditation
86{
87    attentionThreshold = attention;
88    meditationThreshold = meditation;
89    [self calculatePowerValues];
90}
91
[9015b1e]92- (void) appStopped
[ab9d63b]93{
[9015b1e]94    running = NO;
95    if ([TGAccessoryManager sharedTGAccessoryManager].accessory != nil) {
[ab9d63b]96        [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
97    }
[9015b1e]98    [audioPlayer stop];
99    if (_delegate != nil) {
100        [_delegate appStopped];
101    }
[7945ecb]102    AudioSessionSetActive(NO);
[ab9d63b]103}
104
105#pragma mark - TGAccessoryDelegate methods
106
107- (void)dataReceived:(NSDictionary *)data {
108    [self performSelectorOnMainThread:@selector(updatedSignalReceived:)
109                           withObject:data
110                        waitUntilDone:NO];
111}
112
[d249b65]113// Updated signal received from the EEG headset.
[ab9d63b]114- (void) updatedSignalReceived:(id) data
115{
116    [self setValuesFromData:data];
117    // notify listening delegates of updated values so the UI can be updated
118    if (_delegate != nil)
119    {
120        [_delegate updatedValuesForSignal: signalStrength
[d249b65]121                                attention: (float)attentionLevel / 100
122                               meditation: (float)meditationLevel / 100
123                                    power: [self currentPowerLevel]];
[84e25a1]124       
125        // TODO Insert Servo Control Here
126       
[ab9d63b]127    }
128    [self playAudio];
129}
130
131- (void) setValuesFromData:(id) data
132{
133    [self setSignalStrength:(NSNumber *)[data valueForKey:POOR_SIGNAL_KEY]];
134    [self setAttentionLevel:(NSNumber *)[data valueForKey:ATTENTION_KEY]];
[d249b65]135    [self setMeditationLevel:(NSNumber *)[data valueForKey:MEDITATION_KEY]];
[2246ba0]136   
[fc960da]137    historyAttention[historyIndex] = attentionLevel;
138    historyMeditation[historyIndex] = meditationLevel;
[2246ba0]139   
[fc960da]140    historyIndex = historyIndex + 1;
141   
142    HistoryAppDataObject* theDataObject = [self theAppDataObject];
143    theDataObject.lastAttention = attentionLevel;
144    theDataObject.lastMeditation = meditationLevel;
145
[2246ba0]146   
[fc960da]147    if (historyIndex == 30) {
148        NSLog(@"History Arrays Full");
149        historyIndex = 0;
[2246ba0]150       
151        for (int i=0; i<30; i++){
[fc960da]152            NSLog(@"%d: %d", i, historyMeditation[i]);
[2246ba0]153        }
154       
155    }
156   
[ab9d63b]157}
158
159- (void) setSignalStrength:(NSNumber *) value
160{
161    if (value != nil) {
[d249b65]162       
[ad40faa]163        //        NSLog(@"value: %i", [value intValue]);
[d249b65]164       
165        if ([value intValue] == 0) {
166            signalStrength = 100;
167        }
168       
169        else if ([value intValue] == 200) {
170            signalStrength = 0;
171        }
172       
173        else {
174            signalStrength = [value intValue];
175        }
176       
[ad40faa]177        //        NSLog(@"signalStrength: %i", signalStrength);
[d249b65]178       
[ab9d63b]179    }
180}
181
182- (void) setAttentionLevel:(NSNumber *) value
183{
184    if (value != nil) {
185        attentionLevel = [value intValue];
186    }
187}
188
189- (void) setMeditationLevel:(NSNumber *) value
190{
191    if (value != nil) {
192        meditationLevel = [value intValue];
193    }
194}
195
196// The headset was switched on, start the data stream
197- (void)accessoryDidConnect:(EAAccessory *)accessory {
198}
199
200// The headset was switched off (or the Bluetooth signal was dropped).
201- (void)accessoryDidDisconnect {
202    if (_delegate != nil)
203    {
204        [_delegate notifyHeadsetDisconnect];
205    }
206}
207
[9015b1e]208- (BOOL) isBluetoothReady
209{
210    return [[TGAccessoryManager sharedTGAccessoryManager] accessory] != NULL;
211}
212
213- (BOOL) isVolumeMax
214{
[7945ecb]215    Float32 volume;
216    UInt32 dataSize;
217    AudioSessionGetPropertySize(kAudioSessionProperty_CurrentHardwareOutputVolume, &dataSize);
218    AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputVolume, &dataSize, &volume);
[16249e0]219    //    NSLog(@"Volume is %f", volume);
[359a504]220    return 1.0 == volume;
221}
222
223- (BOOL) isAudioJackPlugged
224{
225    UInt32 routeSize;
226   
227    // oddly, without calling this method caused an error.
228    AudioSessionGetPropertySize(kAudioSessionProperty_AudioRouteDescription, &routeSize);
229    CFDictionaryRef desc; // this is the dictionary to contain descriptions
230   
231    // make the call to get the audio description and populate the desc dictionary
[7945ecb]232    AudioSessionGetProperty(kAudioSessionProperty_AudioRouteDescription, &routeSize, &desc);
[d249b65]233   
[359a504]234    // the dictionary contains 2 keys, for input and output. Get output array
235    CFArrayRef outputs = CFDictionaryGetValue(desc, kAudioSession_AudioRouteKey_Outputs);
236   
237    // the output array contains 1 element - a dictionary
238    CFDictionaryRef dict = CFArrayGetValueAtIndex(outputs, 0);
239   
240    // get the output description from the dictionary
241    CFStringRef output = CFDictionaryGetValue(dict, kAudioSession_AudioRouteKey_Type);
242   
243    /**
244     These are the possible output types:
245     kAudioSessionOutputRoute_LineOut
246     kAudioSessionOutputRoute_Headphones
247     kAudioSessionOutputRoute_BluetoothHFP
248     kAudioSessionOutputRoute_BluetoothA2DP
249     kAudioSessionOutputRoute_BuiltInReceiver
250     kAudioSessionOutputRoute_BuiltInSpeaker
251     kAudioSessionOutputRoute_USBAudio
252     kAudioSessionOutputRoute_HDMI
253     kAudioSessionOutputRoute_AirPlay
254     */
[d249b65]255   
[359a504]256    return CFStringCompare(output, kAudioSessionOutputRoute_Headphones, 0) == kCFCompareEqualTo;
[9015b1e]257}
258
[ab9d63b]259#pragma mark start / stop methods
260
[9015b1e]261- (BOOL) startProcessing
[ab9d63b]262{
[de10cbc]263    if (testing) return NO;
[ab9d63b]264    EAAccessory *accessory = [[TGAccessoryManager sharedTGAccessoryManager] accessory];
265    if (accessory != nil) {
[9015b1e]266        running = YES;
267        if (_delegate != nil) {
[ab9d63b]268            [_delegate notifyDeviceConnected: accessory.name];
269        }
270        [[TGAccessoryManager sharedTGAccessoryManager] startStream];
271    }
[9015b1e]272    return running;
[ab9d63b]273}
274
275- (void) stopProcessing
276{
[de10cbc]277    if (running) [self appStopped];
278}
279
280- (void) playTestSound
281{
282    if (!running && !testing) {
283        testing = YES;
[c9b9edd]284#if USE_AUDIO_GENERATOR
[ad40faa]285        // Generate Sound
[c9b9edd]286        [audioPlayer playWithThrottle:throttle yaw:yaw pitch:pitch];
287#else
[e37a889]288        // Play WAV
289        [self prepareAudio];
290        [audioPlayer play];
[c9b9edd]291#endif
[de10cbc]292    }
293}
294
295- (void) stopTestSound
296{
297    if (testing) {
298        testing = NO;
299        [audioPlayer stop];
300    }
[ab9d63b]301}
302
303#pragma mark internal processing methods
304
[e59da66]305// Calculate power for throttle. In theory this can be tied to the amount of throttle
306// to set based on how far based the trigger thresholds attention and meditation are
307// currently measured
[ab9d63b]308- (float) currentPowerLevel
309{
[d249b65]310   
[ba2b6ff]311    float attentionScore = attentionPower[attentionLevel];
312    float meditationScore = meditationPower[meditationLevel];
313   
[89934ea]314    if (attentionThreshold == 0.0) {
[ba2b6ff]315        attentionScore = 0;
316    }
317   
318    if (meditationThreshold == 0.0) {
319        meditationScore = 0;
320    }
[d249b65]321   
[ba2b6ff]322    float powerLevel = attentionScore + meditationScore;
[e59da66]323   
324    // For now just set Power to 100% if helicopter should be flying
[16249e0]325    //    if (powerLevel > 1) {
[e59da66]326    if (powerLevel > 0) {
[ab9d63b]327        return 1.0;
328    }
329    return powerLevel;
330}
331
[b094d6c]332- (void) setControlSettings:(int)y throttle:(int)t pitch:(int)p
[ce0a7ee]333{
[b094d6c]334   
[16249e0]335    //    NSLog(@"DEBUG: Signal Converter: throttle:%d yaw:%d pitch:%d", t, y, p);
[b094d6c]336   
[ce0a7ee]337    throttle = t;
[b094d6c]338    yaw = y;
[ce0a7ee]339    pitch = p;
[c9b9edd]340   
[37ca4e8]341    if (testing) {
342        [audioPlayer playWithThrottle:throttle yaw:yaw pitch:pitch];
343    }
344   
[ce0a7ee]345}
346
[ad40faa]347
[ab9d63b]348- (void) playAudio
349{
[ad40faa]350   
351    //   audioPlayer.volume = [self currentPowerLevel];
[ab9d63b]352    if ([self currentPowerLevel] > 0) {
[e37a889]353       
[c9b9edd]354#if USE_AUDIO_GENERATOR
[e37a889]355        // Generate Signal
[c9b9edd]356        [audioPlayer playWithThrottle:throttle yaw:yaw pitch:pitch];
357#else
358        audioPlayer.volume = 1.0;
[16249e0]359       
[e37a889]360        // Play WAV
361        [self prepareAudio];
[ad40faa]362        [audioPlayer play];
[c9b9edd]363#endif
[e37a889]364       
[ab9d63b]365    } else {
366        [audioPlayer stop];
[ad40faa]367        [self prepareAudio];
[ab9d63b]368       
369    }
370}
371
372#pragma mark - display calculation and update methods
373
374// when the user adjusts one of the 2 threshold sliders we recalculate the power values at each of the 101 possible levels
375- (void) calculatePowerValues
376{
377    for (int i = 0; i < 101; i++) {
378        attentionPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:attentionThreshold];
379    }
380    for (int i = 0; i < 101; i++) {
381        meditationPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:meditationThreshold];
382    }
383}
384
385// Convert a value (attention or meditation) into the corresponding power output (which is the volume level)
386// given the threshold is set at specified value.
387// The threshold is the value set by the user as the minimum value (attention or meditation) to be reached
388// before the helicopter is given the signal to fly.
389// If the value if below the threshold, the value will be zero (threshold not yet met).
390// Otherwise the returned power value will be between 0 and 1.
391- (float) calculatePowerAt:(float)value withThreshold:(float)threshold
392{
393    if (value < threshold) { // threshold not met
394        return 0;
395    }
396    // e.g. if the threshold is 0.55 and the current value is 0.7:
397    // there is 0.45 remaining of the threshold slider, and the value is 0.15 past the threshold (0.7 - 0.55), which is 0.33 (0.15 / 0.45)
398    return (value - threshold) / (1 - threshold);
399}
400
[ad40faa]401- (void) prepareAudio
402{
[c9b9edd]403#if USE_AUDIO_GENERATOR
404#else
[ad40faa]405    NSURL *audioFilePath = [[NSBundle mainBundle] URLForResource:AUDIO_FILE_NAME withExtension:nil];
406   
407    NSError *error;
408    audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFilePath error:&error];
409    if (!audioPlayer) {
410        //        [self logMessage:[NSString stringWithFormat:@"Failed to initialize audio player: %@", [error debugDescription]]];
411        NSLog(@"Failed to initialize audio player: %@", [error debugDescription]);
412    } else {
413        [audioPlayer prepareToPlay];
414    }
[c9b9edd]415#endif
[ad40faa]416}
417
[fc960da]418- (int) getAttentionHistory:(int) index
419{
420    return historyAttention[index];
421}
422
[ab9d63b]423@end
Note: See TracBrowser for help on using the repository browser.