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

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

Add a test button on flight tab
Add a reset button on advanced tab
Correctly detect device volume
Add more tutorial HTML

  • Property mode set to 100644
File size: 6.7 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    return self;
50}
51
52- (void) setValuesForAttention:(float) attention meditation:(float) meditation
53{
54    attentionThreshold = attention;
55    meditationThreshold = meditation;
56    [self calculatePowerValues];
57}
58
59- (void) appStopped
60{
61    running = NO;
62    if ([TGAccessoryManager sharedTGAccessoryManager].accessory != nil) {
63        [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
64    }
65    [audioPlayer stop];
66    if (_delegate != nil) {
67        [_delegate appStopped];
68    }
69}
70
71#pragma mark - TGAccessoryDelegate methods
72
73- (void)dataReceived:(NSDictionary *)data {
74    [self performSelectorOnMainThread:@selector(updatedSignalReceived:)
75                           withObject:data
76                        waitUntilDone:NO];
77}
78
79// Updated signal received from the EEG headset.
80- (void) updatedSignalReceived:(id) data
81{
82    [self setValuesFromData:data];
83    // notify listening delegates of updated values so the UI can be updated
84    if (_delegate != nil)
85    {
86        [_delegate updatedValuesForSignal: signalStrength
87                                    attention: (float)attentionLevel / 100
88                                   meditation: (float)meditationLevel / 100
89                                        power: [self currentPowerLevel]];
90    }
91    [self playAudio];
92}
93
94- (void) setValuesFromData:(id) data
95{
96    [self setSignalStrength:(NSNumber *)[data valueForKey:POOR_SIGNAL_KEY]];
97    [self setAttentionLevel:(NSNumber *)[data valueForKey:ATTENTION_KEY]];
98    [self setMeditationLevel:(NSNumber *)[data valueForKey:MEDITATION_KEY]];   
99}
100
101- (void) setSignalStrength:(NSNumber *) value
102{
103    if (value != nil) {
104        signalStrength = (200.0 - [value intValue]) / 200.0;
105    }
106}
107
108- (void) setAttentionLevel:(NSNumber *) value
109{
110    if (value != nil) {
111        attentionLevel = [value intValue];
112    }
113}
114
115- (void) setMeditationLevel:(NSNumber *) value
116{
117    if (value != nil) {
118        meditationLevel = [value intValue];
119    }
120}
121
122// The headset was switched on, start the data stream
123- (void)accessoryDidConnect:(EAAccessory *)accessory {
124}
125
126// The headset was switched off (or the Bluetooth signal was dropped).
127- (void)accessoryDidDisconnect {
128    if (_delegate != nil)
129    {
130        [_delegate notifyHeadsetDisconnect];
131    }
132}
133
134- (BOOL) isBluetoothReady
135{
136    return [[TGAccessoryManager sharedTGAccessoryManager] accessory] != NULL;
137}
138
139- (BOOL) isVolumeMax
140{
141    AudioSessionSetActive(YES);
142    return 1.0 == [[AVAudioSession sharedInstance] outputVolume];
143}
144
145#pragma mark start / stop methods
146
147- (BOOL) startProcessing
148{
149    if (testing) return NO;
150    EAAccessory *accessory = [[TGAccessoryManager sharedTGAccessoryManager] accessory];
151    if (accessory != nil) {
152        running = YES;
153        if (_delegate != nil) {
154            [_delegate notifyDeviceConnected: accessory.name];
155        }
156        [[TGAccessoryManager sharedTGAccessoryManager] startStream];
157    }
158    return running;
159}
160
161- (void) stopProcessing
162{
163    if (running) [self appStopped];
164}
165
166- (void) playTestSound
167{
168    if (!running && !testing) {
169        testing = YES;
170        [audioPlayer playWithThrottle:throttle yaw:yaw pitch:pitch];
171    }
172}
173
174- (void) stopTestSound
175{
176    if (testing) {
177        testing = NO;
178        [audioPlayer stop];
179    }
180}
181
182#pragma mark internal processing methods
183
184// calculate the total power level to output through the headphones, a value between zero and 1.
185// this is the attention signal level and meditation signal level added together
186- (float) currentPowerLevel
187{
188    float powerLevel = attentionPower[attentionLevel] + meditationPower[meditationLevel];
189    if (powerLevel > 1) {
190        return 1.0;
191    }
192    return powerLevel;
193}
194
195- (void) setYaw:(int)y throttle:(int)t pitch:(int)p
196{
197    yaw = y;
198    throttle = t;
199    pitch = p;
200}
201
202- (void) playAudio
203{
204    if ([self currentPowerLevel] > 0) {
205        [audioPlayer playWithThrottle:throttle yaw:yaw pitch:pitch];
206    } else {
207        [audioPlayer stop];
208       
209    }
210}
211
212#pragma mark - display calculation and update methods
213
214// when the user adjusts one of the 2 threshold sliders we recalculate the power values at each of the 101 possible levels
215- (void) calculatePowerValues
216{
217    for (int i = 0; i < 101; i++) {
218        attentionPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:attentionThreshold];
219    }
220    for (int i = 0; i < 101; i++) {
221        meditationPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:meditationThreshold];
222    }
223}
224
225// Convert a value (attention or meditation) into the corresponding power output (which is the volume level)
226// given the threshold is set at specified value.
227// The threshold is the value set by the user as the minimum value (attention or meditation) to be reached
228// before the helicopter is given the signal to fly.
229// If the value if below the threshold, the value will be zero (threshold not yet met).
230// Otherwise the returned power value will be between 0 and 1.
231- (float) calculatePowerAt:(float)value withThreshold:(float)threshold
232{
233    if (value < threshold) { // threshold not met
234        return 0;
235    }
236    // e.g. if the threshold is 0.55 and the current value is 0.7:
237    // 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)
238    return (value - threshold) / (1 - threshold);
239}
240
241@end
Note: See TracBrowser for help on using the repository browser.