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

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

Port Android AudioService? to iOS
Dummy page for tutorial
icons for tabs (which don't work)

  • Property mode set to 100644
File size: 6.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;
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    float volume = [[AVAudioSession sharedInstance] outputVolume];
142    return volume == 1.0;
143}
144
145#pragma mark start / stop methods
146
147- (BOOL) startProcessing
148{
149    EAAccessory *accessory = [[TGAccessoryManager sharedTGAccessoryManager] accessory];
150    if (accessory != nil) {
151        running = YES;
152        if (_delegate != nil) {
153            [_delegate notifyDeviceConnected: accessory.name];
154        }
155        [[TGAccessoryManager sharedTGAccessoryManager] startStream];
156    }
157    return running;
158}
159
160- (void) stopProcessing
161{
162    [self appStopped];
163}
164
165#pragma mark internal processing methods
166
167// calculate the total power level to output through the headphones, a value between zero and 1.
168// this is the attention signal level and meditation signal level added together
169- (float) currentPowerLevel
170{
171    float powerLevel = attentionPower[attentionLevel] + meditationPower[meditationLevel];
172    if (powerLevel > 1) {
173        return 1.0;
174    }
175    return powerLevel;
176}
177
178- (void) setYaw:(int)y throttle:(int)t pitch:(int)p
179{
180    yaw = y;
181    throttle = t;
182    pitch = p;
183}
184
185- (void) playAudio
186{
187    if ([self currentPowerLevel] > 0) {
188        [audioPlayer playWithThrottle:throttle yaw:yaw pitch:pitch];
189    } else {
190        [audioPlayer stop];
191       
192    }
193}
194
195#pragma mark - display calculation and update methods
196
197// when the user adjusts one of the 2 threshold sliders we recalculate the power values at each of the 101 possible levels
198- (void) calculatePowerValues
199{
200    for (int i = 0; i < 101; i++) {
201        attentionPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:attentionThreshold];
202    }
203    for (int i = 0; i < 101; i++) {
204        meditationPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:meditationThreshold];
205    }
206}
207
208// Convert a value (attention or meditation) into the corresponding power output (which is the volume level)
209// given the threshold is set at specified value.
210// The threshold is the value set by the user as the minimum value (attention or meditation) to be reached
211// before the helicopter is given the signal to fly.
212// If the value if below the threshold, the value will be zero (threshold not yet met).
213// Otherwise the returned power value will be between 0 and 1.
214- (float) calculatePowerAt:(float)value withThreshold:(float)threshold
215{
216    if (value < threshold) { // threshold not met
217        return 0;
218    }
219    // e.g. if the threshold is 0.55 and the current value is 0.7:
220    // 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)
221    return (value - threshold) / (1 - threshold);
222}
223
224@end
Note: See TracBrowser for help on using the repository browser.