source: orbit/iOS/Orbit/Orbit/SignalConverter.m @ 9015b1e

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

UI working with headset correctly now.
Check Bluetooth enabled and volume at full

  • Property mode set to 100644
File size: 7.8 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}
30
31@synthesize attentionThreshold, meditationThreshold, running;
32
33
34- (id) init
35{
36    self = [super init];
37    if (self)
38    {
39        [[TGAccessoryManager sharedTGAccessoryManager] setupManagerWithInterval:0.05];
40        [[TGAccessoryManager sharedTGAccessoryManager] setDelegate:self];
41    }
42    return self;
43}
44
45- (void) setValuesForAttention:(float) attention meditation:(float) meditation
46{
47    attentionThreshold = attention;
48    meditationThreshold = meditation;
49    [self calculatePowerValues];
50}
51
52- (void) prepare
53{
54    NSURL *audioFilePath = [[NSBundle mainBundle] URLForResource:AUDIO_FILE_NAME withExtension:nil];
55    audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFilePath error:nil];
56    if (audioPlayer) {
57        [audioPlayer prepareToPlay];
58    }
59}
60
61- (void) appStopped
62{
63    running = NO;
64    if ([TGAccessoryManager sharedTGAccessoryManager].accessory != nil) {
65        [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
66    }
67    [audioPlayer stop];
68    if (_delegate != nil) {
69        [_delegate appStopped];
70    }
71}
72
73#pragma mark - TGAccessoryDelegate methods
74
75- (void)dataReceived:(NSDictionary *)data {
76    [self performSelectorOnMainThread:@selector(updatedSignalReceived:)
77                           withObject:data
78                        waitUntilDone:NO];
79}
80
81// Updated signal received from the EEG headset.
82- (void) updatedSignalReceived:(id) data
83{
84    [self setValuesFromData:data];
85    // notify listening delegates of updated values so the UI can be updated
86    if (_delegate != nil)
87    {
88        [_delegate updatedValuesForSignal: signalStrength
89                                    attention: (float)attentionLevel / 100
90                                   meditation: (float)meditationLevel / 100
91                                        power: [self currentPowerLevel]];
92    }
93    [self playAudio];
94}
95
96- (void) setValuesFromData:(id) data
97{
98    [self setSignalStrength:(NSNumber *)[data valueForKey:POOR_SIGNAL_KEY]];
99    [self setAttentionLevel:(NSNumber *)[data valueForKey:ATTENTION_KEY]];
100    [self setMeditationLevel:(NSNumber *)[data valueForKey:MEDITATION_KEY]];   
101}
102
103- (void) setSignalStrength:(NSNumber *) value
104{
105    if (value != nil) {
106        signalStrength = (200.0 - [value intValue]) / 200.0;
107    }
108}
109
110- (void) setAttentionLevel:(NSNumber *) value
111{
112    if (value != nil) {
113        attentionLevel = [value intValue];
114    }
115}
116
117- (void) setMeditationLevel:(NSNumber *) value
118{
119    if (value != nil) {
120        meditationLevel = [value intValue];
121    }
122}
123
124// The headset was switched on, start the data stream
125- (void)accessoryDidConnect:(EAAccessory *)accessory {
126}
127
128// The headset was switched off (or the Bluetooth signal was dropped).
129- (void)accessoryDidDisconnect {
130    if (_delegate != nil)
131    {
132        [_delegate notifyHeadsetDisconnect];
133    }
134}
135
136- (BOOL) isBluetoothReady
137{
138    return [[TGAccessoryManager sharedTGAccessoryManager] accessory] != NULL;
139}
140
141- (BOOL) isVolumeMax
142{
143    float volume = [[AVAudioSession sharedInstance] outputVolume];
144//    Float32 volume;
145//    UInt32 dataSize = sizeof(Float32);
146//   
147//    AudioSessionGetProperty (
148//                             kAudioSessionProperty_CurrentHardwareOutputVolume,
149//                             &dataSize,
150//                             &volume
151//                             );
152    return volume == 1.0;
153}
154
155#pragma mark start / stop methods
156
157- (BOOL) startProcessing
158{
159    EAAccessory *accessory = [[TGAccessoryManager sharedTGAccessoryManager] accessory];
160    if (accessory != nil) {
161        running = YES;
162        if (_delegate != nil) {
163            [_delegate notifyDeviceConnected: accessory.name];
164        }
165        [[TGAccessoryManager sharedTGAccessoryManager] startStream];
166    }
167    return running;
168}
169
170- (void) stopProcessing
171{
172    [self appStopped];
173    [self prepare];   
174}
175
176#pragma mark internal processing methods
177
178// calculate the total power level to output through the headphones, a value between zero and 1.
179// this is the attention signal level and meditation signal level added together
180- (float) currentPowerLevel
181{
182    float powerLevel = attentionPower[attentionLevel] + meditationPower[meditationLevel];
183    if (powerLevel > 1) {
184        return 1.0;
185    }
186    return powerLevel;
187}
188
189- (void) playAudio
190{
191    //   audioPlayer.volume = [self currentPowerLevel];
192    if ([self currentPowerLevel] > 0) {
193        audioPlayer.volume = 1.0;
194        [audioPlayer play];
195    } else {
196        [audioPlayer stop];
197        [self prepare];
198       
199    }
200}
201
202#pragma mark - display calculation and update methods
203
204// when the user adjusts one of the 2 threshold sliders we recalculate the power values at each of the 101 possible levels
205- (void) calculatePowerValues
206{
207    for (int i = 0; i < 101; i++) {
208        attentionPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:attentionThreshold];
209    }
210    for (int i = 0; i < 101; i++) {
211        meditationPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:meditationThreshold];
212    }
213}
214
215// Convert a value (attention or meditation) into the corresponding power output (which is the volume level)
216// given the threshold is set at specified value.
217// The threshold is the value set by the user as the minimum value (attention or meditation) to be reached
218// before the helicopter is given the signal to fly.
219// If the value if below the threshold, the value will be zero (threshold not yet met).
220// Otherwise the returned power value will be between 0 and 1.
221- (float) calculatePowerAt:(float)value withThreshold:(float)threshold
222{
223    if (value < threshold) { // threshold not met
224        return 0;
225    }
226    // e.g. if the threshold is 0.55 and the current value is 0.7:
227    // 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)
228    return (value - threshold) / (1 - threshold);
229}
230
231// calculate the checksum for the generated code used to generate the WAV array
232- (int) codeChecksum:(int)code
233{
234    int checksum = 0;
235    for (int i = 0; i < 7; i++) {
236        checksum += (code >> 4*i) & 15;
237    }
238    return 16 - (checksum & 15);
239}
240
241// Generate the code used to create the WAV file based on the given throttle, yaw and pitch.
242// Copied from AudioService.java in the Android app
243// throttle: 0~127, nothing will happen if this value is below 30.
244// yaw: 0~127, normally 78 will keep orbit from rotating.
245// pitch: 0~63, normally 31 will stop the top propeller.
246// channel: 1=Channel A, 0=Channel B 2= Channel C, depend on which channel you want to pair to the orbit. You can fly at most 3 orbit in a same room.
247- (int) generateCodeFromThrottle: (int)throttle yaw: (int)yaw pitch: (int)pitch channel: (int)channel
248{
249    int code = (throttle << 21) + 1048576 + (yaw << 12) + (pitch << 4) + (((channel >> 1) & 1) << 19) + ((channel & 1) << 11);
250    return code;// + codeChecksum(code);
251}
252
253@end
Note: See TracBrowser for help on using the repository browser.