source: orbit/iOS/Orbit/Orbit/SignalConverter.m @ 2246ba0

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