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

Servo
Last change on this file since ac05196 was c9b9edd, checked in by Steve Castellotti <sc@…>, 9 years ago

iOS:

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