source: orbit/iOS/Orbit/Orbit/SignalConverter.m @ 359a504

ServoTab_Interfacepyramid
Last change on this file since 359a504 was 359a504, checked in by Jonathon Horsman <jonathon@…>, 7 years ago

Add check for headphones being plugged in

  • Property mode set to 100644
File size: 8.4 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    }
52    return self;
53}
54
55- (void) setValuesForAttention:(float) attention meditation:(float) meditation
56{
57    attentionThreshold = attention;
58    meditationThreshold = meditation;
59    [self calculatePowerValues];
60}
61
62- (void) appStopped
63{
64    running = NO;
65    if ([TGAccessoryManager sharedTGAccessoryManager].accessory != nil) {
66        [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
67    }
68    [audioPlayer stop];
69    if (_delegate != nil) {
70        [_delegate appStopped];
71    }
72}
73
74#pragma mark - TGAccessoryDelegate methods
75
76- (void)dataReceived:(NSDictionary *)data {
77    [self performSelectorOnMainThread:@selector(updatedSignalReceived:)
78                           withObject:data
79                        waitUntilDone:NO];
80}
81
82// Updated signal received from the EEG headset.
83- (void) updatedSignalReceived:(id) data
84{
85    [self setValuesFromData:data];
86    // notify listening delegates of updated values so the UI can be updated
87    if (_delegate != nil)
88    {
89        [_delegate updatedValuesForSignal: signalStrength
90                                    attention: (float)attentionLevel / 100
91                                   meditation: (float)meditationLevel / 100
92                                        power: [self currentPowerLevel]];
93    }
94    [self playAudio];
95}
96
97- (void) setValuesFromData:(id) data
98{
99    [self setSignalStrength:(NSNumber *)[data valueForKey:POOR_SIGNAL_KEY]];
100    [self setAttentionLevel:(NSNumber *)[data valueForKey:ATTENTION_KEY]];
101    [self setMeditationLevel:(NSNumber *)[data valueForKey:MEDITATION_KEY]];   
102}
103
104- (void) setSignalStrength:(NSNumber *) value
105{
106    if (value != nil) {
107        signalStrength = (200.0 - [value intValue]) / 200.0;
108    }
109}
110
111- (void) setAttentionLevel:(NSNumber *) value
112{
113    if (value != nil) {
114        attentionLevel = [value intValue];
115    }
116}
117
118- (void) setMeditationLevel:(NSNumber *) value
119{
120    if (value != nil) {
121        meditationLevel = [value intValue];
122    }
123}
124
125// The headset was switched on, start the data stream
126- (void)accessoryDidConnect:(EAAccessory *)accessory {
127}
128
129// The headset was switched off (or the Bluetooth signal was dropped).
130- (void)accessoryDidDisconnect {
131    if (_delegate != nil)
132    {
133        [_delegate notifyHeadsetDisconnect];
134    }
135}
136
137- (BOOL) isBluetoothReady
138{
139    return [[TGAccessoryManager sharedTGAccessoryManager] accessory] != NULL;
140}
141
142- (BOOL) isVolumeMax
143{
144    AudioSessionSetActive(YES);
145    float volume = [[AVAudioSession sharedInstance] outputVolume];
146    NSLog(@"Volume is %f", volume);
147    return 1.0 == volume;
148}
149
150- (BOOL) isAudioJackPlugged
151{
152    UInt32 routeSize;
153   
154    // oddly, without calling this method caused an error.
155    AudioSessionGetPropertySize(kAudioSessionProperty_AudioRouteDescription, &routeSize);
156    CFDictionaryRef desc; // this is the dictionary to contain descriptions
157   
158    // make the call to get the audio description and populate the desc dictionary
159    AudioSessionGetProperty (kAudioSessionProperty_AudioRouteDescription, &routeSize, &desc);
160
161    // the dictionary contains 2 keys, for input and output. Get output array
162    CFArrayRef outputs = CFDictionaryGetValue(desc, kAudioSession_AudioRouteKey_Outputs);
163   
164    // the output array contains 1 element - a dictionary
165    CFDictionaryRef dict = CFArrayGetValueAtIndex(outputs, 0);
166   
167    // get the output description from the dictionary
168    CFStringRef output = CFDictionaryGetValue(dict, kAudioSession_AudioRouteKey_Type);
169   
170    /**
171     These are the possible output types:
172     kAudioSessionOutputRoute_LineOut
173     kAudioSessionOutputRoute_Headphones
174     kAudioSessionOutputRoute_BluetoothHFP
175     kAudioSessionOutputRoute_BluetoothA2DP
176     kAudioSessionOutputRoute_BuiltInReceiver
177     kAudioSessionOutputRoute_BuiltInSpeaker
178     kAudioSessionOutputRoute_USBAudio
179     kAudioSessionOutputRoute_HDMI
180     kAudioSessionOutputRoute_AirPlay
181     */
182
183    NSLog(@"Output: %@ is %@", output, kAudioSessionOutputRoute_Headphones);
184    return CFStringCompare(output, kAudioSessionOutputRoute_Headphones, 0) == kCFCompareEqualTo;
185}
186
187#pragma mark start / stop methods
188
189- (BOOL) startProcessing
190{
191    if (testing) return NO;
192    EAAccessory *accessory = [[TGAccessoryManager sharedTGAccessoryManager] accessory];
193    if (accessory != nil) {
194        running = YES;
195        if (_delegate != nil) {
196            [_delegate notifyDeviceConnected: accessory.name];
197        }
198        [[TGAccessoryManager sharedTGAccessoryManager] startStream];
199    }
200    return running;
201}
202
203- (void) stopProcessing
204{
205    if (running) [self appStopped];
206}
207
208- (void) playTestSound
209{
210    if (!running && !testing) {
211        testing = YES;
212        [audioPlayer playWithThrottle:throttle yaw:yaw pitch:pitch];
213    }
214}
215
216- (void) stopTestSound
217{
218    if (testing) {
219        testing = NO;
220        [audioPlayer stop];
221    }
222}
223
224#pragma mark internal processing methods
225
226// calculate the total power level to output through the headphones, a value between zero and 1.
227// this is the attention signal level and meditation signal level added together
228- (float) currentPowerLevel
229{
230    float powerLevel = attentionPower[attentionLevel] + meditationPower[meditationLevel];
231    if (powerLevel > 1) {
232        return 1.0;
233    }
234    return powerLevel;
235}
236
237- (void) setYaw:(int)y throttle:(int)t pitch:(int)p
238{
239    yaw = y;
240    throttle = t;
241    pitch = p;
242}
243
244- (void) playAudio
245{
246    if ([self currentPowerLevel] > 0) {
247        [audioPlayer playWithThrottle:throttle yaw:yaw pitch:pitch];
248    } else {
249        [audioPlayer stop];
250       
251    }
252}
253
254#pragma mark - display calculation and update methods
255
256// when the user adjusts one of the 2 threshold sliders we recalculate the power values at each of the 101 possible levels
257- (void) calculatePowerValues
258{
259    for (int i = 0; i < 101; i++) {
260        attentionPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:attentionThreshold];
261    }
262    for (int i = 0; i < 101; i++) {
263        meditationPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:meditationThreshold];
264    }
265}
266
267// Convert a value (attention or meditation) into the corresponding power output (which is the volume level)
268// given the threshold is set at specified value.
269// The threshold is the value set by the user as the minimum value (attention or meditation) to be reached
270// before the helicopter is given the signal to fly.
271// If the value if below the threshold, the value will be zero (threshold not yet met).
272// Otherwise the returned power value will be between 0 and 1.
273- (float) calculatePowerAt:(float)value withThreshold:(float)threshold
274{
275    if (value < threshold) { // threshold not met
276        return 0;
277    }
278    // e.g. if the threshold is 0.55 and the current value is 0.7:
279    // 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)
280    return (value - threshold) / (1 - threshold);
281}
282
283@end
Note: See TracBrowser for help on using the repository browser.