• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#import "WALTClient.h"
18
19#include <ctype.h>
20#include <dispatch/dispatch.h>
21#include <mach/clock.h>
22#include <mach/mach.h>
23#include <mach/mach_host.h>
24#include <stdlib.h>
25#include <time.h>
26
27#import "MIDIEndpoint.h"
28#import "MIDIMessage.h"
29
30NSString * const WALTClientErrorDomain = @"WALTClientErrorDomain";
31
32static NSString * const kWALTVersion = @"v 4";
33
34static const MIDIChannel kWALTChannel = 1;
35static const MIDIByte kWALTSerialOverMIDIProgram = 1;
36static const MIDIMessageType kWALTCommandType = MIDIMessageChannelPressure;
37
38const NSTimeInterval kWALTReadTimeout = 0.2;
39static const NSTimeInterval kWALTDuplicateTimeout = 0.01;
40
41static const int kWALTSyncIterations = 7;
42#define kWALTSyncDigitMax 9  // #define to avoid variable length array warnings.
43
44/** Similar to atoll(), but only reads a maximum of n characters from s. */
45static unsigned long long antoull(const char *s, size_t n) {
46  unsigned long long result = 0;
47  while (s && n-- && *s && isdigit(*s)) {
48    result = result * 10 + (*s - '0');
49    ++s;
50  }
51  return result;
52}
53
54/** Converts a mach_timespec_t to its equivalent number of microseconds. */
55static int64_t TimespecToMicroseconds(const mach_timespec_t ts) {
56  return ((int64_t)ts.tv_sec) * USEC_PER_SEC + ts.tv_nsec / NSEC_PER_USEC;
57}
58
59/** Returns the current time (in microseconds) on a clock. */
60static int64_t CurrentTime(clock_serv_t clock) {
61  mach_timespec_t time = {0};
62  clock_get_time(clock, &time);
63  return TimespecToMicroseconds(time);
64}
65
66/** Sleeps the current thread for us microseconds. */
67static void Sleep(int64_t us) {
68  const struct timespec ts = {
69    .tv_sec = (long)(us / USEC_PER_SEC),
70    .tv_nsec = (us % USEC_PER_SEC) * NSEC_PER_USEC,
71  };
72  nanosleep(&ts, NULL);
73}
74
75@interface WALTClient ()
76@property (readwrite, nonatomic, getter=isConnected) BOOL connected;
77
78- (void)drainResponseQueue;
79- (BOOL)improveSyncBoundsWithError:(NSError **)error;
80- (BOOL)improveMinBoundWithError:(NSError **)error;
81- (BOOL)improveMaxBoundWithError:(NSError **)error;
82- (BOOL)readRemoteTimestamps:(uint64_t[kWALTSyncDigitMax])times error:(NSError **)error;
83- (WALTTrigger)readTrigger:(NSData *)response;
84@end
85
86@implementation WALTClient {
87  MIDIClient *_client;
88
89  // Responses from the MIDIClient are queued up here with a signal to the semaphore.
90  NSMutableArray<NSData *> *_responseQueue;  // TODO(pquinn): Lock-free circular buffer?
91  dispatch_semaphore_t _responseSemaphore;
92
93  BOOL _syncCompleted;
94
95  clock_serv_t _clock;
96
97  NSData *_lastData;
98  NSTimeInterval _lastDataTimestamp;
99
100  struct {
101    // All microseconds.
102    int64_t base;
103    int64_t minError;
104    int64_t maxError;
105  } _sync;
106}
107
108- (instancetype)initWithError:(NSError **)error {
109  if ((self = [super init])) {
110    _responseQueue = [[NSMutableArray<NSData *> alloc] init];
111    _responseSemaphore = dispatch_semaphore_create(0);
112
113    // NB: It's important that this is the same clock used as the base for UIEvent's -timestamp.
114    kern_return_t result = host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &_clock);
115
116    if (result != KERN_SUCCESS || ![self checkConnectionWithError:error]) {
117      self = nil;
118    }
119  }
120  return self;
121}
122
123- (void)dealloc {
124  [self drainResponseQueue];
125  mach_port_deallocate(mach_task_self(), _clock);
126}
127
128// Ensure only one KVO notification is sent when the connection state is changed.
129+ (BOOL)automaticallyNotifiesObserversOfConnected {
130  return NO;
131}
132
133- (void)setConnected:(BOOL)connected {
134  if (_connected != connected) {
135    [self willChangeValueForKey:@"connected"];
136    _connected = connected;
137    [self didChangeValueForKey:@"connected"];
138  }
139}
140
141- (BOOL)checkConnectionWithError:(NSError **)error {
142  if (_client.source.isOnline && _client.destination.isOnline && _syncCompleted) {
143    self.connected = YES;
144    return YES;  // Everything's fine.
145  }
146
147  _syncCompleted = NO;  // Reset the sync state.
148  [self drainResponseQueue];
149
150  // Create a new client.
151  // This probably isn't strictly necessary, but solves some of the flakiness on iOS.
152  _client.delegate = nil;
153  _client = [[MIDIClient alloc] initWithName:@"WALT" error:error];
154  _client.delegate = self;
155  if (!_client) {
156    self.connected = NO;
157    return NO;
158  }
159
160  if (!_client.source.isOnline) {
161    // Try to connect to the first available input source.
162    // TODO(pquinn): Make this user-configurable.
163    NSArray<MIDISource *> *sources = [MIDISource allSources];
164    if (sources.count) {
165      if (![_client connectToSource:sources.firstObject error:error]) {
166        self.connected = NO;
167        return NO;
168      }
169    }
170  }
171
172  if (!_client.destination.isOnline) {
173    // Try to connect to the first available input source.
174    // TODO(pquinn): Make this user-configurable.
175    NSArray<MIDIDestination *> *destinations = [MIDIDestination allDestinations];
176    if (destinations.count) {
177      if (![_client connectToDestination:destinations.firstObject error:error]) {
178        self.connected = NO;
179        return NO;
180      }
181    }
182
183    if (_client.destination.isOnline) {
184      // Switch to Serial-over-MIDI mode.
185      NSData *message = MIDIMessageCreateSimple1(MIDIMessageProgramChange,
186                                                 kWALTChannel,
187                                                 kWALTSerialOverMIDIProgram);
188      if (![_client sendData:message error:error]) {
189        self.connected = NO;
190        return NO;
191      }
192
193      // Make sure it's using a known protocol version.
194      message = MIDIMessageCreateSimple1(kWALTCommandType, kWALTChannel, WALTVersionCommand);
195      if (![_client sendData:message error:error]) {
196        self.connected = NO;
197        return NO;
198      }
199
200      NSData *response = [self readResponseWithTimeout:kWALTReadTimeout];
201      NSString *versionString = [[NSString alloc] initWithData:response
202                                                      encoding:NSASCIIStringEncoding];
203      if (![versionString isEqualToString:kWALTVersion]) {
204        if (error) {
205          *error = [NSError errorWithDomain:WALTClientErrorDomain
206                                       code:0
207                                   userInfo:@{NSLocalizedDescriptionKey:
208                                                [@"Unknown WALT version: "
209                                                  stringByAppendingString:versionString]}];
210        }
211        self.connected = NO;
212        return NO;
213      }
214
215      if (![self syncClocksWithError:error]) {
216        self.connected = NO;
217        return NO;
218      }
219
220      _syncCompleted = YES;
221    }
222  }
223
224  self.connected = (_client.source.isOnline && _client.destination.isOnline && _syncCompleted);
225  return YES;
226}
227
228#pragma mark - Clock Synchronisation
229
230- (BOOL)syncClocksWithError:(NSError **)error {
231  _sync.base = CurrentTime(_clock);
232
233  if (![self sendCommand:WALTZeroSyncCommand error:error]) {
234    return NO;
235  }
236
237  NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
238  if (![self checkResponse:data forCommand:WALTZeroSyncCommand]) {
239    if (error) {
240      *error = [NSError errorWithDomain:WALTClientErrorDomain
241                                   code:0
242                               userInfo:@{NSLocalizedDescriptionKey:
243          [NSString stringWithFormat:@"Bad acknowledgement for WALTZeroSyncCommand: %@", data]}];
244    }
245    return NO;
246  }
247
248  _sync.maxError = CurrentTime(_clock) - _sync.base;
249  _sync.minError = 0;
250
251  for (int i = 0; i < kWALTSyncIterations; ++i) {
252    if (![self improveSyncBoundsWithError:error]) {
253      return NO;
254    }
255  }
256
257  // Shift the time base so minError == 0
258  _sync.base += _sync.minError;
259  _sync.maxError -= _sync.minError;
260  _sync.minError = 0;
261  return YES;
262}
263
264- (BOOL)updateSyncBoundsWithError:(NSError **)error {
265  // Reset the bounds to unrealistic values
266  _sync.minError = -1e7;
267  _sync.maxError =  1e7;
268
269  for (int i = 0; i < kWALTSyncIterations; ++i) {
270    if (![self improveSyncBoundsWithError:error]) {
271      return NO;
272    }
273  }
274
275  return YES;
276}
277
278- (int64_t)minError {
279  return _sync.minError;
280}
281
282- (int64_t)maxError {
283  return _sync.maxError;
284}
285
286- (BOOL)improveSyncBoundsWithError:(NSError **)error {
287  return ([self improveMinBoundWithError:error] && [self improveMaxBoundWithError:error]);
288}
289
290- (BOOL)improveMinBoundWithError:(NSError **)error {
291  if (![self sendCommand:WALTResetCommand error:error]) {
292    return NO;
293  }
294
295  NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
296  if (![self checkResponse:data forCommand:WALTResetCommand]) {
297    if (error) {
298      *error = [NSError errorWithDomain:WALTClientErrorDomain
299                                   code:0
300                               userInfo:@{NSLocalizedDescriptionKey:
301          [NSString stringWithFormat:@"Bad acknowledgement for WALTResetCommand: %@", data]}];
302    }
303    return NO;
304  }
305
306  const uint64_t kMaxSleepTime = 700; // µs
307  const uint64_t kMinSleepTime = 70;  // µs
308  const uint64_t kSleepTimeDivider = 10;
309
310  uint64_t sleepTime = (_sync.maxError - _sync.minError) / kSleepTimeDivider;
311  if (sleepTime > kMaxSleepTime) { sleepTime = kMaxSleepTime; }
312  if (sleepTime < kMinSleepTime) { sleepTime = kMinSleepTime; }
313
314  struct {
315    uint64_t local[kWALTSyncDigitMax];
316    uint64_t remote[kWALTSyncDigitMax];
317  } digitTimes = {0};
318
319  // Send the digits 1 through 9 and record the times they were sent in digitTimes.local.
320  for (int i = 0; i < kWALTSyncDigitMax; ++i) {
321    digitTimes.local[i] = CurrentTime(_clock) - _sync.base;
322
323    char c = '1' + i;
324    if (![self sendCommand:c error:error]) {
325      if (error) {
326        *error = [NSError errorWithDomain:WALTClientErrorDomain
327                                     code:0
328                                 userInfo:@{NSLocalizedDescriptionKey:
329            [NSString stringWithFormat:@"Error sending digit %d", i + 1],
330                                            NSUnderlyingErrorKey: *error}];
331      }
332      return NO;
333    }
334    // Sleep between digits
335    Sleep(sleepTime);
336  }
337
338  if (![self readRemoteTimestamps:digitTimes.remote error:error]) {
339    return NO;
340  }
341
342  // Adjust minError to be the largest delta between local and remote.
343  for (int i = 0; i < kWALTSyncDigitMax; ++i) {
344    int64_t delta = digitTimes.local[i] - digitTimes.remote[i];
345    if (digitTimes.local[i] != 0 && digitTimes.remote[i] != 0 && delta > _sync.minError) {
346      _sync.minError = delta;
347    }
348  }
349  return YES;
350}
351
352- (BOOL)improveMaxBoundWithError:(NSError **)error {
353  struct {
354    uint64_t local[kWALTSyncDigitMax];
355    uint64_t remote[kWALTSyncDigitMax];
356  } digitTimes = {0};
357
358  // Ask the WALT to send the digits 1 through 9, and record the times they are received in
359  // digitTimes.local.
360  if (![self sendCommand:WALTSendSyncCommand error:error]) {
361    return NO;
362  }
363
364  for (int i = 0; i < kWALTSyncDigitMax; ++i) {
365    NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
366    if (data.length != 1) {
367      if (error) {
368        *error = [NSError errorWithDomain:WALTClientErrorDomain
369                                     code:0
370                                 userInfo:@{NSLocalizedDescriptionKey:
371            [NSString stringWithFormat:@"Error receiving digit %d: %@", i + 1, data]}];
372      }
373      return NO;
374    }
375
376    char c = ((const char *)data.bytes)[0];
377    if (!isdigit(c)) {
378      if (error) {
379        *error = [NSError errorWithDomain:WALTClientErrorDomain
380                                     code:0
381                                 userInfo:@{NSLocalizedDescriptionKey:
382            [NSString stringWithFormat:@"Error parsing digit response: %c", c]}];
383      }
384      return NO;
385    }
386
387    int digit = c - '0';
388    digitTimes.local[digit - 1] = CurrentTime(_clock) - _sync.base;
389  }
390
391  if (![self readRemoteTimestamps:digitTimes.remote error:error]) {
392    return NO;
393  }
394
395  // Adjust maxError to be the smallest delta between local and remote
396  for (int i = 0; i < kWALTSyncDigitMax; ++i) {
397    int64_t delta = digitTimes.local[i] - digitTimes.remote[i];
398    if (digitTimes.local[i] != 0 && digitTimes.remote[i] != 0 && delta < _sync.maxError) {
399      _sync.maxError = delta;
400    }
401  }
402  return YES;
403}
404
405- (BOOL)readRemoteTimestamps:(uint64_t [9])times error:(NSError **)error {
406  for (int i = 0; i < kWALTSyncDigitMax; ++i) {
407    // Ask the WALT for each digit's recorded timestamp
408    if (![self sendCommand:WALTReadoutSyncCommand error:error]) {
409      return NO;
410    }
411
412    NSData *data = [self readResponseWithTimeout:kWALTReadTimeout];
413    if (data.length < 3) {
414      if (error) {
415        *error = [NSError errorWithDomain:WALTClientErrorDomain
416                                     code:0
417                                 userInfo:@{NSLocalizedDescriptionKey:
418            [NSString stringWithFormat:@"Error receiving sync digit %d: %@", i + 1, data]}];
419      }
420      return NO;
421    }
422
423    // The reply data is formatted as n:xxxx, where n is a digit between 1 and 9, and xxxx
424    // is a microsecond timestamp.
425    int digit = (int)antoull(data.bytes, 1);
426    uint64_t timestamp = antoull(((const char *)data.bytes) + 2, data.length - 2);
427
428    if (digit != (i + 1) || timestamp == 0) {
429      if (error) {
430        *error = [NSError errorWithDomain:WALTClientErrorDomain
431                                     code:0
432                                 userInfo:@{NSLocalizedDescriptionKey:
433            [NSString stringWithFormat:@"Error parsing remote time response for %d: %@", i, data]}];
434      }
435      return NO;
436    }
437    times[digit - 1] = timestamp;
438  }
439  return YES;
440}
441
442#pragma mark - MIDIClient Delegate
443
444// TODO(pquinn): Errors from these callbacks aren't propoagated anywhere.
445
446- (void)MIDIClientEndpointAdded:(MIDIClient *)client {
447  [self performSelectorOnMainThread:@selector(checkConnectionWithError:)
448                         withObject:nil
449                      waitUntilDone:NO];
450}
451
452- (void)MIDIClientEndpointRemoved:(MIDIClient *)client {
453  [self performSelectorOnMainThread:@selector(checkConnectionWithError:)
454                         withObject:nil
455                      waitUntilDone:NO];
456}
457
458- (void)MIDIClientConfigurationChanged:(MIDIClient *)client {
459  [self performSelectorOnMainThread:@selector(checkConnectionWithError:)
460                         withObject:nil
461                      waitUntilDone:NO];
462}
463
464- (void)MIDIClient:(MIDIClient *)client receivedError:(NSError *)error {
465  // TODO(pquinn): What's the scope of these errors?
466  NSLog(@"WALTClient received unhandled error: %@", error);
467}
468
469- (void)MIDIClient:(MIDIClient *)client receivedData:(NSData *)message {
470  NSData *body = MIDIMessageBody(message);
471  @synchronized (_responseQueue) {
472    // Sometimes a message will be received twice in quick succession. It's not clear where the bug
473    // is (the WALT, CoreMIDI, or this application), and it cannot be reliably reproduced. As a
474    // hack, simply ignore messages that appear to be duplicates and arrive within
475    // kWALTDuplicateTimeout seconds.
476    if (self.currentTime - _lastDataTimestamp <= kWALTDuplicateTimeout &&
477        [body isEqualToData:_lastData]) {
478      NSLog(@"Ignoring duplicate response within kWALTDuplicateTimeout: %@", message);
479      return;
480    }
481
482    [_responseQueue addObject:body];
483    _lastData = body;
484    _lastDataTimestamp = self.currentTime;
485  }
486  dispatch_semaphore_signal(_responseSemaphore);
487}
488
489#pragma mark - Send/Receive
490
491- (void)drainResponseQueue {
492  @synchronized (_responseQueue) {
493    // Drain out any stale responses or the semaphore destructor will assert.
494    while (_responseQueue.count) {
495      dispatch_semaphore_wait(_responseSemaphore, DISPATCH_TIME_FOREVER);
496      [_responseQueue removeObjectAtIndex:0];
497    }
498  }
499}
500
501- (NSData *)readResponseWithTimeout:(NSTimeInterval)timeout {
502  if (dispatch_semaphore_wait(_responseSemaphore,
503                              dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC))) {
504    return nil;
505  }
506
507  @synchronized (_responseQueue) {
508    NSAssert(_responseQueue.count > 0, @"_responseQueue is empty!");
509    NSData *response = _responseQueue.firstObject;
510    [_responseQueue removeObjectAtIndex:0];
511    return response;
512  }
513}
514
515- (BOOL)sendCommand:(WALTCommand)command error:(NSError **)error {
516  NSData *message = MIDIMessageCreateSimple1(kWALTCommandType, kWALTChannel, command);
517  [self drainResponseQueue];
518  return [_client sendData:message error:error];
519}
520
521- (BOOL)checkResponse:(NSData *)response forCommand:(WALTCommand)command {
522  const WALTCommand flipped = isupper(command) ? tolower(command) : toupper(command);
523  if (response.length < 1 || ((const char *)response.bytes)[0] != flipped) {
524    return NO;
525  } else {
526    return YES;
527  }
528}
529
530#pragma mark - Specific Commands
531
532- (NSTimeInterval)lastShockTimeWithError:(NSError **)error {
533  if (![self sendCommand:WALTGShockCommand error:error]) {
534    return -1;
535  }
536
537  NSData *response = [self readResponseWithTimeout:kWALTReadTimeout];
538  if (!response) {
539    if (error) {
540      *error = [NSError errorWithDomain:WALTClientErrorDomain
541                                   code:0
542                               userInfo:@{NSLocalizedDescriptionKey:
543                                            @"Error receiving shock response."}];
544    }
545    return -1;
546  }
547
548  uint64_t microseconds = antoull(response.bytes, response.length);
549  return ((NSTimeInterval)microseconds + _sync.base) / USEC_PER_SEC;
550}
551
552- (WALTTrigger)readTrigger:(NSData *)response {
553  NSString *responseString =
554      [[NSString alloc] initWithData:response encoding:NSASCIIStringEncoding];
555  NSArray<NSString *> *components = [responseString componentsSeparatedByString:@" "];
556
557  WALTTrigger result = {0};
558
559  if (components.count != 5 ||
560      ![[components objectAtIndex:0] isEqualToString:@"G"] ||
561      [components objectAtIndex:1].length != 1) {
562    return result;
563  }
564
565  result.tag = [[components objectAtIndex:1] characterAtIndex:0];
566
567  uint64_t microseconds = atoll([components objectAtIndex:2].UTF8String);
568  result.t = ((NSTimeInterval)microseconds + _sync.base) / USEC_PER_SEC;
569  result.value = (int)atoll([components objectAtIndex:3].UTF8String);
570  result.count = (unsigned int)atoll([components objectAtIndex:4].UTF8String);
571  return result;
572}
573
574- (WALTTrigger)readTriggerWithTimeout:(NSTimeInterval)timeout {
575  return [self readTrigger:[self readResponseWithTimeout:timeout]];
576}
577
578#pragma mark - Time
579
580- (NSTimeInterval)baseTime {
581  return ((NSTimeInterval)_sync.base) / USEC_PER_SEC;
582}
583
584- (NSTimeInterval)currentTime {
585  return ((NSTimeInterval)CurrentTime(_clock)) / USEC_PER_SEC;
586}
587@end
588