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