source: orbit/iOS/Orbit/Orbit/SignalConverter.m @ 57848ad

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