source: orbit/iOS/Orbit/Orbit/SignalConverter.m @ fc960da

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