source: orbit/iOS/Orbit/Orbit/ViewController.m @ f323fb4

Raw_EEG_Plot
Last change on this file since f323fb4 was 0caeed7, checked in by Steve Castellotti <sc@…>, 9 years ago
  • Call to "[self prepareAudio];" added anywhere [audioPlayer stop]; is called. This is intended to reset playback of the infrared audio control file in between flights so the initialization string is always sent to the IR dongle. Should address Ticket #10.
  • Property mode set to 100644
File size: 11.2 KB
Line 
1//
2//  ViewController.m
3//  Orbit
4//
5//  Copyright (c) 2012 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 "ViewController.h"
13#import <AVFoundation/AVFoundation.h>
14
15//#define LOGGING true // set to false to stop each received signal being logged
16#define LOGGING false // set to false to stop each received signal being logged
17
18#define AUDIO_FILE_NAME @"throttle_hover_ios.wav" // @"iOS_noflip.wav" //
19#define POOR_SIGNAL_KEY @"poorSignal"
20#define ATTENTION_KEY @"eSenseAttention"
21#define MEDITATION_KEY @"eSenseMeditation"
22
23@implementation ViewController {
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 currentAttentionLevel, currentMeditationLevel; // the latest readings from the headset
32   BOOL processingDevice;
33   BOOL demoRunning;
34}
35
36@synthesize log, attention, meditation, signal, power, attentionThreshold, meditationThreshold, connectButton, demoButton;
37
38- (void)viewDidLoad
39{
40   [super viewDidLoad];
41   [self prepareAudio];
42   [self.attentionThreshold addTarget:self action:@selector(calculatePowerValues) forControlEvents:UIControlEventValueChanged];
43   [self.meditationThreshold addTarget:self action:@selector(calculatePowerValues) forControlEvents:UIControlEventValueChanged];
44   [self calculatePowerValues];
45   
46}
47
48- (void)didReceiveMemoryWarning
49{
50   [super didReceiveMemoryWarning];
51   // Dispose of any resources that can be recreated.
52   [self stopDemo];
53}
54
55// Start the data stream from the bluetooth headset if it's attached, and reset the display output
56- (void) appForegrounded
57{
58   [self resetViews]; // just in case it didn't happen on close
59}
60
61// Set all levels back to zero and clear the log
62- (void) resetViews
63{
64   [self resetOutputToZero];
65   self.log.text = @"";
66   [self printInstructions];
67   
68}
69
70- (void) printInstructions
71{
72   [self logMessage:@"\nWelcome to Puzzlebox Orbit!\n\nInstructions for Use:\n1. Pair with EEG headset under Bluetooth Settings\n2. Attach infrared dongle to headphone port\n3. Raise volume to maximum level\n4. Press Connect button below to begin processing\n5. When Attention or Meditation levels cross the indicated threshold, the helicopter will take off\n\nPlease visit http://orbit.puzzlebox.info for more details"];
73}
74
75- (void) resetOutputToZero
76{
77   [self showSignalStrength:[NSNumber numberWithInt:200]];
78   [self setAttentionLevel:[NSNumber numberWithInt:0]];
79   [self setMeditationLevel:[NSNumber numberWithInt:0]];
80   [self showPowerLevel];
81}
82
83- (void) appClosed
84{
85   [self stopDemo];
86   [self resetViews];
87   if ([[TGAccessoryManager sharedTGAccessoryManager] connected]) {
88      [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
89   }
90}
91
92- (void) prepareAudio
93{
94   NSURL *audioFilePath = [[NSBundle mainBundle] URLForResource:AUDIO_FILE_NAME withExtension:nil];
95   
96   NSError *error;
97   audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFilePath error:&error];
98   if (!audioPlayer) {
99      [self logMessage:[NSString stringWithFormat:@"Failed to initialize audio player: %@", [error debugDescription]]];
100   } else {
101      [audioPlayer prepareToPlay];
102   }
103}
104
105- (void) logMessage:(NSString *) str
106{
107   log.text = [[NSString alloc] initWithFormat:@"%@\n%@", str, log.text];
108}
109
110- (void) logDataReceived:(NSDictionary *) data
111{
112   NSNumber *sig = [data valueForKey:POOR_SIGNAL_KEY];
113   if (sig != NULL) {
114      int val = (200 - [sig intValue]) / 2;
115      [self logMessage:[NSString stringWithFormat:@"Signal strength: %u%%, Attention: %@%%, Meditation: %@%%",
116                        val, [data valueForKey:ATTENTION_KEY], [data valueForKey:MEDITATION_KEY]]];
117   }
118   
119}
120
121#pragma mark - TGAccessoryDelegate methods
122
123- (void)dataReceived:(NSDictionary *)data {
124   [self performSelectorOnMainThread:@selector(updatedSignalReceived:)
125                          withObject:data
126                       waitUntilDone:NO];
127}
128
129- (void) updatedSignalReceived:(id) data
130{
131   if (LOGGING) {
132      [self logDataReceived: data];
133   }
134   [self showSignalStrength:(NSNumber *)[data valueForKey:POOR_SIGNAL_KEY]];
135   [self setAttentionLevel:(NSNumber *)[data valueForKey:ATTENTION_KEY]];
136   [self setMeditationLevel:(NSNumber *)[data valueForKey:MEDITATION_KEY]];
137   [self showPowerLevel];
138   [self playAudio];
139}
140
141// The headset was switched on, start the data stream
142- (void)accessoryDidConnect:(EAAccessory *)accessory {
143   [self logMessage:[NSString stringWithFormat:@"%@ connected to this device", [accessory name]]];
144   if ([[TGAccessoryManager sharedTGAccessoryManager] accessory] != nil) {
145      [[TGAccessoryManager sharedTGAccessoryManager] startStream];
146   }
147}
148
149// The headset was switched off (or the Bluetooth signal was dropped).
150// Reset the outputs back to zero
151- (void)accessoryDidDisconnect {
152   [self logMessage:@"Accessory was disconnected"];
153   [self resetOutputToZero];
154}
155
156#pragma mark - display calculation and update methods
157
158// when the user adjusts one of the 2 threshold sliders we recalculate the power values at each of the 101 possible levels
159- (void) calculatePowerValues
160{
161   float threshold = self.attentionThreshold.value; // this is the user-selected minimum threshold - between 0 and 1
162   for (int i = 0; i < 101; i++) {
163      attentionPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:threshold];
164   }
165   threshold = self.meditationThreshold.value; // this is the user-selected minimum threshold - between 0 and 1
166   for (int i = 0; i < 101; i++) {
167      meditationPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:threshold];
168   }
169}
170
171// Convert a value (attention or meditation) into the corresponding power output (which is the volume level)
172// given the threshold is set at specified value.
173// The threshold is the value set by the user as the minimum value (attention or meditation) to be reached
174// before the helicopter is given the signal to fly.
175// If the value if below the threshold, the value will be zero (threshold not yet met).
176// Otherwise the returned power value will be between 0 and 1.
177- (float) calculatePowerAt:(float)value withThreshold:(float)threshold
178{
179   if (value < threshold) { // threshold not met
180      return 0;
181   }
182   // e.g. if the threshold is 0.55 and the current value is 0.7:
183   // 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)
184   return (value - threshold) / (1 - threshold);
185}
186
187- (void) showSignalStrength:(NSNumber *) value
188{
189   if (value != nil) {
190      self.signal.progress = (200.0 - [value intValue]) / 200.0;
191   }
192}
193
194- (void) setAttentionLevel:(NSNumber *) value
195{
196   if (value != nil) {
197      currentAttentionLevel = [value intValue];
198      self.attention.progress = (float)currentAttentionLevel / 100;
199   }
200}
201
202- (void) setMeditationLevel:(NSNumber *) value
203{
204   if (value != nil) {
205      currentMeditationLevel = [value intValue];
206      self.meditation.progress = (float)currentMeditationLevel / 100;
207   }
208}
209
210// updates the power output progress bar
211- (void) showPowerLevel
212{
213   self.power.progress = [self currentPowerLevel];
214}
215
216// calculate the total power level to output through the headphones, a value between zero and 1.
217// this is the attention signal level and meditation signal level added together
218- (float) currentPowerLevel
219{
220   float powerLevel = attentionPower[currentAttentionLevel] + meditationPower[currentMeditationLevel];
221   if (powerLevel > 1) {
222      return 1.0;
223   }
224   return powerLevel;
225}
226
227- (void) playAudio
228{
229   //   audioPlayer.volume = [self currentPowerLevel];
230   if ([self currentPowerLevel] > 0) {
231      audioPlayer.volume = 1.0;
232      [audioPlayer play];
233   } else {
234      [audioPlayer stop];
235      [self prepareAudio];
236     
237   }
238}
239
240#pragma mark - button press events
241
242// allow simulation of values being generated by a headset and being received on the phone
243- (IBAction) demoButtonPressed:(id) sender
244{
245   if (demoRunning) {
246      [self stopDemo];
247   } else {
248      [self startDemo];
249   }
250}
251
252- (void) stopDemo
253{
254   demoRunning = NO;
255   [self resetOutputToZero];
256   [demoButton setTitle:@"Demo" forState:UIControlStateNormal];
257   if (audioPlayer != NULL && [audioPlayer isPlaying]) {
258      [audioPlayer stop];
259      [self prepareAudio];
260   }
261   [self logMessage:@"Demo stopped"];
262}
263
264- (void) startDemo
265{
266   demoRunning = YES;
267   [self logMessage:@"Running demo"];
268   [demoButton setTitle:@"Stop" forState:UIControlStateNormal];
269   [self performSelectorInBackground:@selector(generateDemoData) withObject:nil];
270}
271
272// generate some data in a background thread and invoke the methods the headset would invoke with the data
273// for testing without a headset.
274- (void) generateDemoData
275{
276   while (demoRunning) {
277      for (int i = 0; i < 100; i++) {
278         if (!demoRunning) {
279            break; // break out if we stop the demo
280         }
281         NSDictionary *values = [NSDictionary dictionaryWithObjectsAndKeys:
282                                 [NSNumber numberWithInt:i], ATTENTION_KEY,
283                                 [NSNumber numberWithInt:i * 0.8], MEDITATION_KEY,
284                                 [NSNumber numberWithInt:0], POOR_SIGNAL_KEY,
285                                 nil];
286         [self dataReceived:values];
287         [NSThread sleepForTimeInterval:0.1];
288      }
289     
290      for (int i = 100; i > 0; i--) {
291         if (!demoRunning) {
292            break; // break out if we stop the demo
293         }
294         NSDictionary *values = [NSDictionary dictionaryWithObjectsAndKeys:
295                                 [NSNumber numberWithInt:i], ATTENTION_KEY,
296                                 [NSNumber numberWithInt:i * 0.8], MEDITATION_KEY,
297                                 [NSNumber numberWithInt:0], POOR_SIGNAL_KEY,
298                                 nil];
299         [self dataReceived:values];
300         [NSThread sleepForTimeInterval:0.1];
301      }
302     
303   }
304}
305
306- (void) startProcessing
307{
308   processingDevice = YES;
309   [self logMessage:@"Connecting to EEG Headset"];
310   [connectButton setTitle:@"Disconnect" forState:UIControlStateNormal];
311   
312   EAAccessory *accessory = [[TGAccessoryManager sharedTGAccessoryManager] accessory];
313   if (accessory != nil) {
314      [self logMessage:[NSString stringWithFormat:@"EEG device %@ connected", accessory.name]];
315      [[TGAccessoryManager sharedTGAccessoryManager] startStream];
316   }
317   
318}
319
320- (void) stopProcessing
321{
322   processingDevice = NO;
323   [self logMessage:@"Disconnecting from EEG Headset"];
324   if ([[TGAccessoryManager sharedTGAccessoryManager] connected]) {
325      [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
326   }
327   [connectButton setTitle:@"Connect" forState:UIControlStateNormal];
328   
329   [self resetOutputToZero];
330   [audioPlayer stop];
331   [self prepareAudio];
332   
333}
334
335- (IBAction) connectButtonPressed:(id) sender {
336   
337   if (processingDevice) {
338      [self stopProcessing];
339   } else {
340      [self startProcessing];
341   }
342   
343}
344@end
Note: See TracBrowser for help on using the repository browser.