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

ServoTab_Interfacepyramid
Last change on this file since d249b65 was d249b65, checked in by Steve Castellotti <sc@…>, 7 years ago

Orbit:

  • Signal bar now updating correctly

Tutorial:

  • additional text added to step 6
  • Property mode set to 100644
File size: 9.1 KB
Line 
1//
2//  SignalConverter.m
3//  orbit
4//
5//  Created by Jonathon Horsman on 11/07/2013.
6//  Copyright (c) 2013 Puzzlebox Productions, LLC. All rights reserved.
7//
8
9#import "SignalConverter.h"
10
11#define AUDIO_FILE_NAME @"throttle_hover_ios.wav" // @"iOS_noflip.wav"
12#define POOR_SIGNAL_KEY @"poorSignal"
13#define ATTENTION_KEY @"eSenseAttention"
14#define MEDITATION_KEY @"eSenseMeditation"
15
16#define CHANNEL_A 1
17
18// Converts signals received from the EEG headset to the audio played to fly the helicopter.
19//
20@implementation SignalConverter {
21    // these 2 arrays hold the values of the thrust which should be applied to the helicopter
22    // (by way of volume level through the headphones) at each level of attention and meditation.
23    // this could be calculated on the fly, but because it happens ~20 times per second, the
24    // better option is to store all the values in arrays, and recalculate when the sliders are changed.
25    float attentionPower[101];
26    float meditationPower[101];
27   
28    int signalStrength, attentionLevel, meditationLevel; // the latest readings from the headset
29    int yaw, throttle, pitch;
30}
31
32@synthesize attentionThreshold, meditationThreshold, running, testing;
33
34
35- (id) init
36{
37    self = [super init];
38    if (self)
39    {
40        [[TGAccessoryManager sharedTGAccessoryManager] setupManagerWithInterval:0.05];
41        [[TGAccessoryManager sharedTGAccessoryManager] setDelegate:self];
42        audioPlayer = [[AudioGenerator alloc] init];
43       
44        // defaults
45        throttle = 80;
46        yaw = 78;
47        pitch = 31;
48       
49        // initialise the audio session - this should only be done once
50        AudioSessionInitialize(NULL, NULL, NULL, NULL);
51        AudioSessionSetActive(YES);
52    }
53    return self;
54}
55
56- (void) setValuesForAttention:(float) attention meditation:(float) meditation
57{
58    attentionThreshold = attention;
59    meditationThreshold = meditation;
60    [self calculatePowerValues];
61}
62
63- (void) appStopped
64{
65    running = NO;
66    if ([TGAccessoryManager sharedTGAccessoryManager].accessory != nil) {
67        [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
68    }
69    [audioPlayer stop];
70    if (_delegate != nil) {
71        [_delegate appStopped];
72    }
73    AudioSessionSetActive(NO);
74}
75
76#pragma mark - TGAccessoryDelegate methods
77
78- (void)dataReceived:(NSDictionary *)data {
79    [self performSelectorOnMainThread:@selector(updatedSignalReceived:)
80                           withObject:data
81                        waitUntilDone:NO];
82}
83
84// Updated signal received from the EEG headset.
85- (void) updatedSignalReceived:(id) data
86{
87    [self setValuesFromData:data];
88    // notify listening delegates of updated values so the UI can be updated
89    if (_delegate != nil)
90    {
91        [_delegate updatedValuesForSignal: signalStrength
92                                attention: (float)attentionLevel / 100
93                               meditation: (float)meditationLevel / 100
94                                    power: [self currentPowerLevel]];
95    }
96    [self playAudio];
97}
98
99- (void) setValuesFromData:(id) data
100{
101    [self setSignalStrength:(NSNumber *)[data valueForKey:POOR_SIGNAL_KEY]];
102    [self setAttentionLevel:(NSNumber *)[data valueForKey:ATTENTION_KEY]];
103    [self setMeditationLevel:(NSNumber *)[data valueForKey:MEDITATION_KEY]];
104}
105
106- (void) setSignalStrength:(NSNumber *) value
107{
108    if (value != nil) {
109       
110//        NSLog(@"value: %i", [value intValue]);
111       
112        if ([value intValue] == 0) {
113            signalStrength = 100;
114        }
115       
116        else if ([value intValue] == 200) {
117            signalStrength = 0;
118        }
119       
120        else {
121            signalStrength = [value intValue];
122        }
123       
124//        NSLog(@"signalStrength: %i", signalStrength);
125       
126    }
127}
128
129- (void) setAttentionLevel:(NSNumber *) value
130{
131    if (value != nil) {
132        attentionLevel = [value intValue];
133    }
134}
135
136- (void) setMeditationLevel:(NSNumber *) value
137{
138    if (value != nil) {
139        meditationLevel = [value intValue];
140    }
141}
142
143// The headset was switched on, start the data stream
144- (void)accessoryDidConnect:(EAAccessory *)accessory {
145}
146
147// The headset was switched off (or the Bluetooth signal was dropped).
148- (void)accessoryDidDisconnect {
149    if (_delegate != nil)
150    {
151        [_delegate notifyHeadsetDisconnect];
152    }
153}
154
155- (BOOL) isBluetoothReady
156{
157    return [[TGAccessoryManager sharedTGAccessoryManager] accessory] != NULL;
158}
159
160- (BOOL) isVolumeMax
161{
162    Float32 volume;
163    UInt32 dataSize;
164    AudioSessionGetPropertySize(kAudioSessionProperty_CurrentHardwareOutputVolume, &dataSize);
165    AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputVolume, &dataSize, &volume);
166    NSLog(@"Volume is %f", volume);
167    return 1.0 == volume;
168}
169
170- (BOOL) isAudioJackPlugged
171{
172    UInt32 routeSize;
173   
174    // oddly, without calling this method caused an error.
175    AudioSessionGetPropertySize(kAudioSessionProperty_AudioRouteDescription, &routeSize);
176    CFDictionaryRef desc; // this is the dictionary to contain descriptions
177   
178    // make the call to get the audio description and populate the desc dictionary
179    AudioSessionGetProperty(kAudioSessionProperty_AudioRouteDescription, &routeSize, &desc);
180   
181    // the dictionary contains 2 keys, for input and output. Get output array
182    CFArrayRef outputs = CFDictionaryGetValue(desc, kAudioSession_AudioRouteKey_Outputs);
183   
184    // the output array contains 1 element - a dictionary
185    CFDictionaryRef dict = CFArrayGetValueAtIndex(outputs, 0);
186   
187    // get the output description from the dictionary
188    CFStringRef output = CFDictionaryGetValue(dict, kAudioSession_AudioRouteKey_Type);
189   
190    /**
191     These are the possible output types:
192     kAudioSessionOutputRoute_LineOut
193     kAudioSessionOutputRoute_Headphones
194     kAudioSessionOutputRoute_BluetoothHFP
195     kAudioSessionOutputRoute_BluetoothA2DP
196     kAudioSessionOutputRoute_BuiltInReceiver
197     kAudioSessionOutputRoute_BuiltInSpeaker
198     kAudioSessionOutputRoute_USBAudio
199     kAudioSessionOutputRoute_HDMI
200     kAudioSessionOutputRoute_AirPlay
201     */
202   
203    return CFStringCompare(output, kAudioSessionOutputRoute_Headphones, 0) == kCFCompareEqualTo;
204}
205
206#pragma mark start / stop methods
207
208- (BOOL) startProcessing
209{
210    if (testing) return NO;
211    EAAccessory *accessory = [[TGAccessoryManager sharedTGAccessoryManager] accessory];
212    if (accessory != nil) {
213        running = YES;
214        if (_delegate != nil) {
215            [_delegate notifyDeviceConnected: accessory.name];
216        }
217        [[TGAccessoryManager sharedTGAccessoryManager] startStream];
218    }
219    return running;
220}
221
222- (void) stopProcessing
223{
224    if (running) [self appStopped];
225}
226
227- (void) playTestSound
228{
229    if (!running && !testing) {
230        testing = YES;
231        [audioPlayer playWithThrottle:throttle yaw:yaw pitch:pitch];
232    }
233}
234
235- (void) stopTestSound
236{
237    if (testing) {
238        testing = NO;
239        [audioPlayer stop];
240    }
241}
242
243#pragma mark internal processing methods
244
245// calculate the total power level to output through the headphones, a value between zero and 1.
246// this is the attention signal level and meditation signal level added together
247- (float) currentPowerLevel
248{
249   
250    float attentionScore = attentionPower[attentionLevel];
251    float meditationScore = meditationPower[meditationLevel];
252   
253    if (attentionThreshold < 0.0) {
254        attentionScore = 0;
255    }
256   
257    if (meditationThreshold == 0.0) {
258        meditationScore = 0;
259    }
260   
261    float powerLevel = attentionScore + meditationScore;
262    if (powerLevel > 1) {
263        return 1.0;
264    }
265    return powerLevel;
266}
267
268- (void) setYaw:(int)y throttle:(int)t pitch:(int)p
269{
270    yaw = y;
271    throttle = t;
272    pitch = p;
273}
274
275- (void) playAudio
276{
277    if ([self currentPowerLevel] > 0) {
278        [audioPlayer playWithThrottle:throttle yaw:yaw pitch:pitch];
279    } else {
280        [audioPlayer stop];
281       
282    }
283}
284
285#pragma mark - display calculation and update methods
286
287// when the user adjusts one of the 2 threshold sliders we recalculate the power values at each of the 101 possible levels
288- (void) calculatePowerValues
289{
290    for (int i = 0; i < 101; i++) {
291        attentionPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:attentionThreshold];
292    }
293    for (int i = 0; i < 101; i++) {
294        meditationPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:meditationThreshold];
295    }
296}
297
298// Convert a value (attention or meditation) into the corresponding power output (which is the volume level)
299// given the threshold is set at specified value.
300// The threshold is the value set by the user as the minimum value (attention or meditation) to be reached
301// before the helicopter is given the signal to fly.
302// If the value if below the threshold, the value will be zero (threshold not yet met).
303// Otherwise the returned power value will be between 0 and 1.
304- (float) calculatePowerAt:(float)value withThreshold:(float)threshold
305{
306    if (value < threshold) { // threshold not met
307        return 0;
308    }
309    // e.g. if the threshold is 0.55 and the current value is 0.7:
310    // 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)
311    return (value - threshold) / (1 - threshold);
312}
313
314@end
Note: See TracBrowser for help on using the repository browser.