From ac45cd14233f82271b9092b23e98340bdd724b2a Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Sat, 20 Jan 2024 00:35:24 +0100 Subject: [PATCH 01/12] feat: fuzzing function to see if we can shed some light on the keys on MK3 --- KompleteSynthesia.xcodeproj/project.pbxproj | 10 ++ KompleteSynthesia/AppDelegate.m | 30 +++++ KompleteSynthesia/Base.lproj/MainMenu.xib | 4 +- KompleteSynthesia/FuzzingWindowController.h | 30 +++++ KompleteSynthesia/FuzzingWindowController.m | 86 +++++++++++++ KompleteSynthesia/FuzzingWindowController.xib | 120 ++++++++++++++++++ KompleteSynthesia/HIDController.h | 13 +- KompleteSynthesia/HIDController.m | 62 +++++++-- .../PreferencesWindowController.m | 6 - .../PreferencesWindowController.xib | 20 +-- KompleteSynthesia/USBController.m | 2 + 11 files changed, 352 insertions(+), 31 deletions(-) create mode 100644 KompleteSynthesia/FuzzingWindowController.h create mode 100644 KompleteSynthesia/FuzzingWindowController.m create mode 100644 KompleteSynthesia/FuzzingWindowController.xib diff --git a/KompleteSynthesia.xcodeproj/project.pbxproj b/KompleteSynthesia.xcodeproj/project.pbxproj index dbc9f16..fe79ed3 100644 --- a/KompleteSynthesia.xcodeproj/project.pbxproj +++ b/KompleteSynthesia.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + BB1A48DC2B5B152A00BCB863 /* FuzzingWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = BB1A48DA2B5B152A00BCB863 /* FuzzingWindowController.m */; }; + BB1A48DD2B5B152A00BCB863 /* FuzzingWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BB1A48DB2B5B152A00BCB863 /* FuzzingWindowController.xib */; }; BB229913299040C500CFFB7C /* ApplicationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = BB229912299040C500CFFB7C /* ApplicationObserver.m */; }; BB31821629679983005EB410 /* MIDIController.m in Sources */ = {isa = PBXBuildFile; fileRef = BB31821529679983005EB410 /* MIDIController.m */; }; BB3182192967A037005EB410 /* HIDController.m in Sources */ = {isa = PBXBuildFile; fileRef = BB3182182967A037005EB410 /* HIDController.m */; }; @@ -33,6 +35,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + BB1A48D92B5B152A00BCB863 /* FuzzingWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FuzzingWindowController.h; sourceTree = ""; }; + BB1A48DA2B5B152A00BCB863 /* FuzzingWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FuzzingWindowController.m; sourceTree = ""; }; + BB1A48DB2B5B152A00BCB863 /* FuzzingWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FuzzingWindowController.xib; sourceTree = ""; }; BB229911299040C500CFFB7C /* ApplicationObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ApplicationObserver.h; sourceTree = ""; }; BB229912299040C500CFFB7C /* ApplicationObserver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ApplicationObserver.m; sourceTree = ""; }; BB31821429679983005EB410 /* MIDIController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIDIController.h; sourceTree = ""; }; @@ -189,6 +194,9 @@ BB7AB64129807A700081B05E /* PaletteViewController.xib */, BB7AB63C298042520081B05E /* ColorField.h */, BB7AB63D298042530081B05E /* ColorField.m */, + BB1A48D92B5B152A00BCB863 /* FuzzingWindowController.h */, + BB1A48DA2B5B152A00BCB863 /* FuzzingWindowController.m */, + BB1A48DB2B5B152A00BCB863 /* FuzzingWindowController.xib */, ); name = UI; sourceTree = ""; @@ -265,6 +273,7 @@ BB49B3B4295CC8AF00D18605 /* Assets.xcassets in Resources */, BB7AB64329807A700081B05E /* PaletteViewController.xib in Resources */, BB49B3B7295CC8AF00D18605 /* MainMenu.xib in Resources */, + BB1A48DD2B5B152A00BCB863 /* FuzzingWindowController.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -314,6 +323,7 @@ BB76A1A4297B333D00A869D8 /* PreferencesWindowController.m in Sources */, BB5C2E9B2AF6E96A0043F444 /* VirtualEvent.m in Sources */, BBA0166929611D1D0018B2DB /* MIDI2HIDController.m in Sources */, + BB1A48DC2B5B152A00BCB863 /* FuzzingWindowController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/KompleteSynthesia/AppDelegate.m b/KompleteSynthesia/AppDelegate.m index 7520848..3e86d6e 100644 --- a/KompleteSynthesia/AppDelegate.m +++ b/KompleteSynthesia/AppDelegate.m @@ -9,6 +9,7 @@ #import "ApplicationObserver.h" #import "CoreAudioTools.h" +#import "FuzzingWindowController.h" #import "HIDController.h" #import "LogViewController.h" #import "MIDI2HIDController.h" @@ -27,6 +28,7 @@ @interface AppDelegate () @property (nonatomic, strong) LogViewController* log; @property (nonatomic, strong) SynthesiaController* synthesia; @property (nonatomic, strong) PreferencesWindowController* preferences; +@property (nonatomic, strong) FuzzingWindowController* fuzzing; @property (nonatomic, strong) ApplicationObserver* observer; @property (nonatomic, strong) NSPopover* popover; @@ -146,6 +148,8 @@ - (void)applicationDidFinishLaunching:(NSNotification*)aNotification - (void)applicationDidFinishInitializingWithUSBHighwayOpen:(BOOL)usbHighwayOpen { usbAvailable = usbHighwayOpen; + + // Bootstrap Synthesia. _synthesia = [[SynthesiaController alloc] initWithLogViewController:_log delegate:self]; [_synthesia cachedAssertSynthesiaConfiguration]; @@ -190,6 +194,7 @@ - (void)applicationDidFinishInitializingWithSynthesiaRunning _midi2hidController.forwardButtonsToSynthesiaOnly = [userDefaults boolForKey:kAppDefaultActivateSynthesia]; if (usbAvailable == YES) { + // FIXME: So far we only support MK2 controllers for Synthesia window mirroring. if (_hidController.mk == 2) { _videoController = [[VideoController alloc] initWithUSBController:_usbController logViewController:_log @@ -231,6 +236,8 @@ - (void)applicationDidFinishInitializingWithSynthesiaRunning [menu addItemWithTitle:@"Reset" action:@selector(reset:) keyEquivalent:@""]; [menu addItemWithTitle:@"Show Log" action:@selector(showLog:) keyEquivalent:@""]; [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:@"Fuzzing MK3" action:@selector(showFuzzing:) keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; [menu addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; menu.delegate = self; @@ -268,6 +275,23 @@ - (void)preferences:(id)sender [[NSApplication sharedApplication] activateIgnoringOtherApps:NO]; } +- (void)showFuzzing:(id)sender +{ + if (_fuzzing == nil) { + _fuzzing = [[FuzzingWindowController alloc] initWithWindowNibName:@"FuzzingWindowController"]; + _fuzzing.delegate = self; + } + _fuzzing.hidController = _hidController; + + NSWindow* window = [_fuzzing window]; + + // We need to do some trickery here as the Application itself has no window. Not sure + // if this really works in all cases but it does for me, so far. + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; + [window makeKeyAndOrderFront:sender]; + [[NSApplication sharedApplication] activateIgnoringOtherApps:NO]; +} + - (void)showStatusMenu:(id)sender { self.statusItem.menu = self.statusMenu; @@ -453,6 +477,12 @@ - (void)preferencesUpdatedKeyState:(int)keyState forKeyIndex:(int)index NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setInteger:keyState forKey:userDefaultKeys[index]]; + + assert(index < kColorMapSize); + _midi2hidController.colors[index] = keyState; + if (index == 0) { + [_midi2hidController lightsDefault]; + } } @end diff --git a/KompleteSynthesia/Base.lproj/MainMenu.xib b/KompleteSynthesia/Base.lproj/MainMenu.xib index bdd2cea..144f452 100644 --- a/KompleteSynthesia/Base.lproj/MainMenu.xib +++ b/KompleteSynthesia/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + diff --git a/KompleteSynthesia/FuzzingWindowController.h b/KompleteSynthesia/FuzzingWindowController.h new file mode 100644 index 0000000..5fc0790 --- /dev/null +++ b/KompleteSynthesia/FuzzingWindowController.h @@ -0,0 +1,30 @@ +// +// FuzzingWindowController.h +// KompleteSynthesia +// +// Created by Till Toenshoff on 19.01.24. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class HIDController; +@protocol PreferencesDelegate; + +@interface FuzzingWindowController : NSWindowController + +@property (nonatomic, weak) IBOutlet NSTextField* initialCommand; +@property (nonatomic, weak) IBOutlet NSTextField* currentControlCommand; +@property (nonatomic, weak) IBOutlet NSSliderCell* delaySlider; + +@property (nonatomic, weak) HIDController* hidController; + +@property (nonatomic, weak) id delegate; + +- (IBAction)start:(id)sender; +- (IBAction)stop:(id)sender; + +@end + +NS_ASSUME_NONNULL_END diff --git a/KompleteSynthesia/FuzzingWindowController.m b/KompleteSynthesia/FuzzingWindowController.m new file mode 100644 index 0000000..d210355 --- /dev/null +++ b/KompleteSynthesia/FuzzingWindowController.m @@ -0,0 +1,86 @@ +// +// FuzzingWindowController.m +// KompleteSynthesia +// +// Created by Till Toenshoff on 19.01.24. +// + +#import "FuzzingWindowController.h" +#import "HIDController.h" +#import "PreferencesWindowController.h" + +/// Hacked together window for getting some ideas on how to control the lightguide on MK3 devices - totally ugly! + +static const NSTimeInterval kCommandUpdateTimerDelay = 0.01; +static const NSTimeInterval kFuzzTimerDelay = 0.05; + +@interface FuzzingWindowController () +@end + +@implementation FuzzingWindowController { + NSTimer* commandUpdateTimer; + NSTimer* fuzzTimer; +} + +- (NSString*)hexStringFromBinaryData:(unsigned char*)data withLength:(size_t)length +{ + NSString* output = @""; + for (int i = 0; i < length; i++) { + if (i > 0) { + output = [NSString stringWithFormat:@"%@ ", output]; + } + output = [NSString stringWithFormat:@"%@%02X", output, data[i]]; + } + return output; +} + +- (void)windowDidLoad +{ + [super windowDidLoad]; + + [_delegate preferencesUpdatedKeyState:0x00 forKeyIndex:0]; + + _initialCommand.stringValue = [self hexStringFromBinaryData:_hidController.initialCommand + withLength:_hidController.initialCommandLength]; + + commandUpdateTimer = [NSTimer + scheduledTimerWithTimeInterval:kCommandUpdateTimerDelay + repeats:YES + block:^(NSTimer* timer) { + self->_currentControlCommand.stringValue = + [self hexStringFromBinaryData:self->_hidController.lightGuideUpdateMessage + withLength:self->_hidController.lightGuideUpdateMessageSize]; + }]; +} + +- (IBAction)stop:(id)sender +{ + if (fuzzTimer != nil) { + [fuzzTimer invalidate]; + } +} + +- (IBAction)start:(id)sender +{ + self->_hidController.lightGuideUpdateMessage[0] = 0x01; + [_delegate preferencesUpdatedKeyState:0x06 forKeyIndex:0]; + + if (fuzzTimer != nil) { + [fuzzTimer invalidate]; + } + + fuzzTimer = [NSTimer + scheduledTimerWithTimeInterval:kFuzzTimerDelay * _delaySlider.intValue + repeats:YES + block:^(NSTimer* timer) { + [self->_hidController initKeyboardController:nil]; + if (self->_hidController.lightGuideUpdateMessage[0] == 0xFF) { + self->_hidController.lightGuideUpdateMessage[1] += 0x0C; + } + [_delegate preferencesUpdatedKeyState:self->_hidController.lightGuideUpdateMessage[1] + forKeyIndex:0]; + self->_hidController.lightGuideUpdateMessage[0]++; + }]; +} + +@end diff --git a/KompleteSynthesia/FuzzingWindowController.xib b/KompleteSynthesia/FuzzingWindowController.xib new file mode 100644 index 0000000..7682de8 --- /dev/null +++ b/KompleteSynthesia/FuzzingWindowController.xib @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Attempting to run through a range of commands to see what works for controlling the lightguide or anything else on MK3 controllers. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KompleteSynthesia/HIDController.h b/KompleteSynthesia/HIDController.h index c06e61e..602350f 100644 --- a/KompleteSynthesia/HIDController.h +++ b/KompleteSynthesia/HIDController.h @@ -130,10 +130,16 @@ enum { @property (nonatomic, assign) int mk; @property (nonatomic, assign) int keyOffset; @property (nonatomic, assign) unsigned int keyCount; -@property (assign, nonatomic) unsigned char* keys; -@property (assign, nonatomic) unsigned char* buttons; +@property (assign, nonatomic) uint8_t* keys; +@property (assign, nonatomic) uint8_t* buttons; @property (nonatomic, weak) id delegate; +@property (assign, nonatomic) uint8_t* initialCommand; +@property (assign, nonatomic) size_t initialCommandLength; + +@property (assign, nonatomic) uint8_t* lightGuideUpdateMessage; +@property (assign, nonatomic) size_t lightGuideUpdateMessageSize; + + (NSColor*)colorWithKeyState:(const unsigned char)keyState; - (id)initWithUSBController:(USBController*)uc logViewController:(LogViewController*)lc; @@ -149,6 +155,9 @@ enum { - (BOOL)swooshIsActive; - (BOOL)updateButtonLightMap:(NSError**)error; +- (BOOL)registerKeyboardController:(NSError**)error; +- (BOOL)initKeyboardController:(NSError**)error; + - (void)deviceRemoved; - (unsigned char)keyColor:(int)note; diff --git a/KompleteSynthesia/HIDController.m b/KompleteSynthesia/HIDController.m index 804fb62..947b933 100644 --- a/KompleteSynthesia/HIDController.m +++ b/KompleteSynthesia/HIDController.m @@ -34,12 +34,17 @@ // By @jasonbrent: // Seems to be overall device state/Mode Sending just 0xa0 initializes the // device and keyboard light control works. +// +// Confirmed that this is the way KompleteKontrol initializes the controller by capturing +// the USB traffic. const uint8_t kCommandInit = 0xA0; const uint8_t kKompleteKontrolInit[] = {kCommandInit, 0x00, 0x00}; +/* // FIXME: This likely is not be enough to get the MK3 controller fully initialized. It is what // FIXME: Komplete Kontrol sends on an 8 second interval to the controller. const uint8_t kKompleteKontrolInitMK3[] = {0x06, 0x00, 0x00, 0x00, 0x93, 0x02, 0xcd, 0x01, 0x2c, 0x90}; +*/ const uint8_t kCommandLightGuideUpdateMK1 = 0x82; const uint8_t kCommandLightGuideUpdateMK2 = 0x81; @@ -109,8 +114,6 @@ @implementation HIDController { LogViewController* log; USBController* usb; - size_t lightGuideUpdateMessageSize; - unsigned char* lightGuideUpdateMessage; NSMutableData* lightGuideStreamMK3; unsigned char buttonLightingFeedback[kKompleteKontrolButtonsMessageSize]; @@ -171,16 +174,22 @@ - (BOOL)setupWithError:(NSError**)error return NO; } + if ([self registerKeyboardController:error] == NO) { + return NO; + } if ([self initKeyboardController:error] == NO) { return NO; } + /* if (_mk == 3) { // TODO: Make this less magic. Consider abstracting away from this direct buffer access. _keys = lightGuideUpdateMessage + 4 + sizeof(kKompleteKontrolLightGuidePrefixMK3); } else { _keys = &lightGuideUpdateMessage[1]; } + */ + _keys = &_lightGuideUpdateMessage[1]; [self lightKeysWithColor:kKeyColorUnpressed]; @@ -435,12 +444,13 @@ - (IOHIDDeviceRef)detectKeyboardController:(NSError**)error _mk = [supportedDevices[@(product)][@"mk"] intValue]; _keyOffset = [supportedDevices[@(product)][@"offset"] intValue]; - lightGuideUpdateMessageSize = - _mk == 3 ? kKompleteKontrolLightGuideMessageSizeMK3 : kKompleteKontrolLightGuideMessageSize; - - lightGuideStreamMK3 = nil; + /* + _lightGuideUpdateMessageSize = + _mk == 3 ? kKompleteKontrolLightGuideMessageSizeMK3 : kKompleteKontrolLightGuideMessageSize; + lightGuideStreamMK3 = nil; if (_mk == 3) { + // Nice try but doesnt work at all :( lightGuideStreamMK3 = [[NSMutableData alloc] initWithCapacity:lightGuideUpdateMessageSize]; unsigned int length = (unsigned int)lightGuideUpdateMessageSize - 4; [lightGuideStreamMK3 appendBytes:&length length:sizeof(length)]; @@ -454,7 +464,19 @@ - (IOHIDDeviceRef)detectKeyboardController:(NSError**)error } else { lightGuideUpdateMessage = calloc(lightGuideUpdateMessageSize, 1); lightGuideUpdateMessage[0] = _mk == 1 ? kCommandLightGuideUpdateMK1 : kCommandLightGuideUpdateMK2; + + _initialCommand = kKompleteKontrolInit; + _initialCommandLength = sizeof(kKompleteKontrolInit); } + */ + + _lightGuideUpdateMessageSize = kKompleteKontrolLightGuideMessageSize; + + _lightGuideUpdateMessage = calloc(_lightGuideUpdateMessageSize, sizeof(uint8_t)); + _lightGuideUpdateMessage[0] = _mk == 1 ? kCommandLightGuideUpdateMK1 : kCommandLightGuideUpdateMK2; + + _initialCommand = kKompleteKontrolInit; + _initialCommandLength = sizeof(kKompleteKontrolInit); // FIXME: This is likely wrong for MK1 devices! buttonLightingUpdateMessage[0] = kCommandButtonLightsUpdate; @@ -484,7 +506,7 @@ - (IOHIDDeviceRef)detectKeyboardController:(NSError**)error return NULL; } -- (BOOL)initKeyboardController:(NSError**)error +- (BOOL)registerKeyboardController:(NSError**)error { IOHIDDeviceRegisterRemovalCallback(device, HIDDeviceRemovedCallback, (__bridge void*)self); @@ -510,9 +532,16 @@ - (BOOL)initKeyboardController:(NSError**)error IOHIDDeviceRegisterInputReportCallback(device, inputBuffer, sizeof(inputBuffer), HIDInputCallback, (__bridge void*)self); - const uint8_t* init = _mk == 3 ? kKompleteKontrolInitMK3 : kKompleteKontrolInit; - size_t length = _mk == 3 ? sizeof(kKompleteKontrolInitMK3) : sizeof(kKompleteKontrolInit); - ret = IOHIDDeviceSetReport(device, kIOHIDReportTypeOutput, *init, init, length); + return YES; +} + +- (BOOL)initKeyboardController:(NSError**)error +{ + // // This was guessing from captured USB traffic. It does however not really do anything, it seems. + // const uint8_t* init = _mk == 3 ? kKompleteKontrolInitMK3 : kKompleteKontrolInit; + // size_t length = _mk == 3 ? sizeof(kKompleteKontrolInitMK3) : sizeof(kKompleteKontrolInit); + IOReturn ret = + IOHIDDeviceSetReport(device, kIOHIDReportTypeOutput, *_initialCommand, _initialCommand, _initialCommandLength); if (ret != kIOReturnSuccess) { if (error != nil) { NSDictionary* userInfo = @{ @@ -563,12 +592,15 @@ - (BOOL)updateLightGuideMap:(NSError**)error { extern const double kTimeoutDelay; + /* if (_mk == 3) { + // Initial attempt - does nothing even though it looked promising from when reversing. BOOL ret = [usb bulkWriteData:lightGuideStreamMK3 error:error]; [usb waitForBulkTransfer:kTimeoutDelay]; return ret; } - return [self setReport:lightGuideUpdateMessage length:lightGuideUpdateMessageSize error:error]; + */ + return [self setReport:_lightGuideUpdateMessage length:_lightGuideUpdateMessageSize error:error]; } - (void)lightKey:(int)key color:(unsigned char)color @@ -581,9 +613,13 @@ - (void)lightKey:(int)key color:(unsigned char)color _keys[key] = color; break; case 3: + /* + // Initial attempt - does nothing even though it looked promising from when reversing. _keys[key * 3 + 0] = kCommandLightGuideKeyCommandMK3; _keys[key * 3 + 1] = key; _keys[key * 3 + 2] = color; + */ + _keys[key] = color; break; } [self updateLightGuideMap:nil]; @@ -602,15 +638,19 @@ - (void)lightKeysWithColor:(unsigned char)color } break; case 2: + case 3: memset(_keys, color, kKompleteKontrolLightGuideKeyMapSize); break; + /* case 3: + // Initial attempt - does nothing even though it looked promising from when reversing. for (unsigned int i = 0; i < 128; i++) { _keys[i * 3 + 0] = kCommandLightGuideKeyCommandMK3; _keys[i * 3 + 1] = i; _keys[i * 3 + 2] = color; } break; + */ } [self updateLightGuideMap:nil]; diff --git a/KompleteSynthesia/PreferencesWindowController.m b/KompleteSynthesia/PreferencesWindowController.m index 51eae05..e1f5421 100644 --- a/KompleteSynthesia/PreferencesWindowController.m +++ b/KompleteSynthesia/PreferencesWindowController.m @@ -81,12 +81,6 @@ - (void)keyStatePicked:(const unsigned char)keyState index:(const unsigned char) { NSLog(@"picked key state %02Xh for map index %d", keyState, index); - assert(index < kColorMapSize); - _midi2hid.colors[index] = keyState; - if (index == 0) { - [_midi2hid lightsDefault]; - } - assert(controls.count > index); ColorField* colorField = controls[index]; colorField.keyState = keyState; diff --git a/KompleteSynthesia/PreferencesWindowController.xib b/KompleteSynthesia/PreferencesWindowController.xib index 0ed478f..770ceaa 100644 --- a/KompleteSynthesia/PreferencesWindowController.xib +++ b/KompleteSynthesia/PreferencesWindowController.xib @@ -47,7 +47,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -65,7 +65,7 @@ - + @@ -74,7 +74,7 @@ - + @@ -83,7 +83,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -101,7 +101,7 @@ - + @@ -110,7 +110,7 @@ - + @@ -297,7 +297,7 @@ That file belongs to Synthesia and should be located in the folder ~/Library/App - + @@ -358,7 +358,7 @@ That file belongs to Synthesia and should be located in the folder ~/Library/App - + diff --git a/KompleteSynthesia/USBController.m b/KompleteSynthesia/USBController.m index 0952384..84d5624 100644 --- a/KompleteSynthesia/USBController.m +++ b/KompleteSynthesia/USBController.m @@ -24,6 +24,8 @@ const uint32_t kUSBDeviceInterfaceMK2 = 0x03; const uint32_t kUSBDeviceInterfaceEndpointMK2 = 0x03; +// FIXME: While this appears to be the interface and endpoint KompleteKontrol is using when +// FIXME: communicating with the controller, it also does nothing in my attempts so far. const uint32_t kUSBDeviceInterfaceMK3 = 0x04; const uint32_t kUSBDeviceInterfaceEndpointMK3 = 0x04; From bac1a9e7426d11dfd639f75dbc65becbd383013e Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Wed, 24 Jan 2024 17:58:20 +0100 Subject: [PATCH 02/12] feat: applying things learned to the MK3 data flow - still incomplete and unvalidated --- KompleteSynthesia/HIDController.m | 32 +++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/KompleteSynthesia/HIDController.m b/KompleteSynthesia/HIDController.m index 947b933..6e07ff6 100644 --- a/KompleteSynthesia/HIDController.m +++ b/KompleteSynthesia/HIDController.m @@ -48,23 +48,27 @@ const uint8_t kCommandLightGuideUpdateMK1 = 0x82; const uint8_t kCommandLightGuideUpdateMK2 = 0x81; +const uint8_t kCommandLightGuideUpdateMK3 = 0x83; // FIXME: This appears to be wrong for MK3 devices -- instead of lighting keys, we are // FIXME: lighting the touchstrip with 0x81. // const uint8_t kCommandLightGuideUpdateMK3 = 0x81; // See https://github.com/tillt/KompleteSynthesia/discussions/29#discussioncomment-8089141 -const uint8_t kKompleteKontrolLightGuidePrefixMK3[] = {0x93, 0x02, 0xCD, 0x01, 0x16, 0x92, 0xCD, 0x01, - 0x51, 0x81, 0xCC, 0xFC, 0xDC, 0x00, 0x80}; -const uint8_t kCommandLightGuideKeyCommandMK3 = 0x92; +// const uint8_t kKompleteKontrolLightGuidePrefixMK3[] = {0x93, 0x02, 0xCD, 0x01, 0x16, 0x92, 0xCD, 0x01, +// 0x51, 0x81, 0xCC, 0xFC, 0xDC, 0x00, 0x80}; +// const uint8_t kCommandLightGuideKeyCommandMK3 = 0x92; -const size_t kKompleteKontrolLightGuideMessageSizeMK3 = 403; +// const size_t kKompleteKontrolLightGuideMessageSizeMK3 = 403; const size_t kKompleteKontrolLightGuideMessageSize = 250; const size_t kKompleteKontrolLightGuideKeyMapSize = kKompleteKontrolLightGuideMessageSize - 1; -// This buttons lighting message likely is MK2 specific. -const uint8_t kCommandButtonLightsUpdate = 0x80; +const uint8_t kCommandButtonLightsUpdateMK1 = + 0x80; // FIXME: Noone ever confirmed if this was working as intended so far. +const uint8_t kCommandButtonLightsUpdateMK2 = 0x80; +const uint8_t kCommandButtonLightsUpdateMK3 = 0x82; // TODO: Validate this with user feedback. + const size_t kKompleteKontrolButtonsMessageSize = 80; const size_t kKompleteKontrolButtonsMapSize = kKompleteKontrolButtonsMessageSize - 1; @@ -473,13 +477,25 @@ - (IOHIDDeviceRef)detectKeyboardController:(NSError**)error _lightGuideUpdateMessageSize = kKompleteKontrolLightGuideMessageSize; _lightGuideUpdateMessage = calloc(_lightGuideUpdateMessageSize, sizeof(uint8_t)); - _lightGuideUpdateMessage[0] = _mk == 1 ? kCommandLightGuideUpdateMK1 : kCommandLightGuideUpdateMK2; _initialCommand = kKompleteKontrolInit; _initialCommandLength = sizeof(kKompleteKontrolInit); // FIXME: This is likely wrong for MK1 devices! - buttonLightingUpdateMessage[0] = kCommandButtonLightsUpdate; + switch (_mk) { + case 1: + buttonLightingUpdateMessage[0] = kCommandButtonLightsUpdateMK1; + _lightGuideUpdateMessage[0] = kCommandLightGuideUpdateMK1; + break; + case 2: + buttonLightingUpdateMessage[0] = kCommandButtonLightsUpdateMK2; + _lightGuideUpdateMessage[0] = kCommandLightGuideUpdateMK2; + break; + case 3: + buttonLightingUpdateMessage[0] = kCommandButtonLightsUpdateMK3; + _lightGuideUpdateMessage[0] = kCommandLightGuideUpdateMK3; + break; + } _deviceName = [NSString stringWithFormat:@"%@Kontrol S%d MK%d", _mk != 3 ? @"Komplete " : @"", _keyCount, _mk]; return devices[i]; From 233f1d3046a159034003ce9ccbb98206c6edb46c Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Thu, 25 Jan 2024 00:27:48 +0100 Subject: [PATCH 03/12] feat: lazy commit, more fuzzing and a few fixes --- KompleteSynthesia/AppDelegate.m | 6 ++ KompleteSynthesia/FuzzingWindowController.h | 7 +- KompleteSynthesia/FuzzingWindowController.m | 77 +++++++++++++++++-- KompleteSynthesia/FuzzingWindowController.xib | 28 +++++-- KompleteSynthesia/HIDController.m | 12 ++- KompleteSynthesia/SynthesiaController.m | 18 ++--- 6 files changed, 121 insertions(+), 27 deletions(-) diff --git a/KompleteSynthesia/AppDelegate.m b/KompleteSynthesia/AppDelegate.m index 3e86d6e..315cc2a 100644 --- a/KompleteSynthesia/AppDelegate.m +++ b/KompleteSynthesia/AppDelegate.m @@ -442,6 +442,12 @@ - (void)synthesiaStateUpdate:(NSString*)status { if (self.statusMenu.itemArray.count > 1) { NSMenuItem* item = self.statusMenu.itemArray[1]; + if ([item.title compare:status] == NSOrderedSame) { + // Lets avoid useless further UI and controller updates as we are receiving + // a lot of state updates for Synthesia. + // FIXME: Seems weird that we get flooded by state updates. + return; + } item.title = status; } [self updateButtonStates]; diff --git a/KompleteSynthesia/FuzzingWindowController.h b/KompleteSynthesia/FuzzingWindowController.h index 5fc0790..70cd133 100644 --- a/KompleteSynthesia/FuzzingWindowController.h +++ b/KompleteSynthesia/FuzzingWindowController.h @@ -12,18 +12,23 @@ NS_ASSUME_NONNULL_BEGIN @class HIDController; @protocol PreferencesDelegate; -@interface FuzzingWindowController : NSWindowController +@interface FuzzingWindowController : NSWindowController @property (nonatomic, weak) IBOutlet NSTextField* initialCommand; @property (nonatomic, weak) IBOutlet NSTextField* currentControlCommand; @property (nonatomic, weak) IBOutlet NSSliderCell* delaySlider; +@property (nonatomic, weak) IBOutlet NSButton* pauseButton; +@property (nonatomic, weak) IBOutlet NSButton* stopButton; +@property (nonatomic, weak) IBOutlet NSButton* startButton; + @property (nonatomic, weak) HIDController* hidController; @property (nonatomic, weak) id delegate; - (IBAction)start:(id)sender; - (IBAction)stop:(id)sender; +- (IBAction)pause:(id)sender; @end diff --git a/KompleteSynthesia/FuzzingWindowController.m b/KompleteSynthesia/FuzzingWindowController.m index d210355..fcc4927 100644 --- a/KompleteSynthesia/FuzzingWindowController.m +++ b/KompleteSynthesia/FuzzingWindowController.m @@ -20,6 +20,7 @@ @interface FuzzingWindowController () @implementation FuzzingWindowController { NSTimer* commandUpdateTimer; NSTimer* fuzzTimer; + BOOL paused; } - (NSString*)hexStringFromBinaryData:(unsigned char*)data withLength:(size_t)length @@ -34,14 +35,26 @@ - (NSString*)hexStringFromBinaryData:(unsigned char*)data withLength:(size_t)len return output; } +- (void)binaryDataFromHexString:(NSString*)input withData:(unsigned char*)data withLength:(size_t)length +{ + NSArray* hexStringBytes = [input componentsSeparatedByString:@" "]; + size_t byteCount = MIN(length, hexStringBytes.count); + for (size_t i = 0; i < byteCount; i++) { + NSString* hex = hexStringBytes[i]; + const char* chars = [hex UTF8String]; + data[i] = strtoul(chars, NULL, 16); + } +} + - (void)windowDidLoad { [super windowDidLoad]; - [_delegate preferencesUpdatedKeyState:0x00 forKeyIndex:0]; - _initialCommand.stringValue = [self hexStringFromBinaryData:_hidController.initialCommand withLength:_hidController.initialCommandLength]; + _initialCommand.delegate = self; + + [_delegate preferencesUpdatedKeyState:0x00 forKeyIndex:0]; commandUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:kCommandUpdateTimerDelay @@ -51,20 +64,62 @@ - (void)windowDidLoad [self hexStringFromBinaryData:self->_hidController.lightGuideUpdateMessage withLength:self->_hidController.lightGuideUpdateMessageSize]; }]; + paused = NO; + [self updateButtonStates]; } -- (IBAction)stop:(id)sender +- (void)controlTextDidEndEditing:(NSNotification*)notification +{ + NSTextField* textField = [notification object]; + if (textField != _initialCommand) { + return; + } + [self binaryDataFromHexString:textField.stringValue + withData:_hidController.initialCommand + withLength:_hidController.initialCommandLength]; +} + +- (void)updateButtonStates +{ + if (paused) { + _startButton.enabled = NO; + _pauseButton.enabled = YES; + _stopButton.enabled = YES; + } else { + _startButton.enabled = fuzzTimer == nil; + _stopButton.enabled = fuzzTimer != nil; + _pauseButton.enabled = fuzzTimer != nil; + } +} + +- (void)stopTimer { if (fuzzTimer != nil) { [fuzzTimer invalidate]; } + fuzzTimer = nil; } -- (IBAction)start:(id)sender +- (IBAction)stop:(id)sender { - self->_hidController.lightGuideUpdateMessage[0] = 0x01; - [_delegate preferencesUpdatedKeyState:0x06 forKeyIndex:0]; + [self stopTimer]; + paused = NO; + [self updateButtonStates]; +} + +- (IBAction)pause:(id)sender +{ + if (fuzzTimer != nil) { + paused = YES; + [self stopTimer]; + } else { + paused = NO; + [self startTimer]; + } +} +- (void)startTimer +{ if (fuzzTimer != nil) { [fuzzTimer invalidate]; } @@ -83,4 +138,14 @@ - (IBAction)start:(id)sender }]; } +- (IBAction)start:(id)sender +{ + _hidController.lightGuideUpdateMessage[0] = 0x01; + [_delegate preferencesUpdatedKeyState:0x06 forKeyIndex:0]; + + [self startTimer]; + paused = NO; + [self updateButtonStates]; +} + @end diff --git a/KompleteSynthesia/FuzzingWindowController.xib b/KompleteSynthesia/FuzzingWindowController.xib index 7682de8..654126d 100644 --- a/KompleteSynthesia/FuzzingWindowController.xib +++ b/KompleteSynthesia/FuzzingWindowController.xib @@ -11,6 +11,9 @@ + + + @@ -47,7 +50,7 @@ - + @@ -72,7 +75,7 @@ + - + - + - + - + diff --git a/KompleteSynthesia/HIDController.m b/KompleteSynthesia/HIDController.m index 6e07ff6..3f54811 100644 --- a/KompleteSynthesia/HIDController.m +++ b/KompleteSynthesia/HIDController.m @@ -38,7 +38,7 @@ // Confirmed that this is the way KompleteKontrol initializes the controller by capturing // the USB traffic. const uint8_t kCommandInit = 0xA0; -const uint8_t kKompleteKontrolInit[] = {kCommandInit, 0x00, 0x00}; +uint8_t kKompleteKontrolInit[] = {kCommandInit, 0x00, 0x00}; /* // FIXME: This likely is not be enough to get the MK3 controller fully initialized. It is what @@ -314,10 +314,14 @@ - (void)receivedReport:(unsigned char*)report length:(int)length for (int i = 0; i < length; i++) { [hex appendFormat:@"%02x ", report[i]]; } - NSLog(@"hid report: %@", hex); - [log logLine:[NSString stringWithFormat:@"hid report: %@", hex]]; + NSString* trail = @""; + if (report[0] != 0x01) { + NSLog(@"ignoring report %02Xh", report[0]); + trail = [trail stringByAppendingString:@" (ignored!)"]; + } + [log logLine:[NSString stringWithFormat:@"hid report: %@%@", hex, trail]]; #endif - + NSLog(@"hid report: %@", hex); if (report[0] != 0x01) { NSLog(@"ignoring report %02Xh", report[0]); return; diff --git a/KompleteSynthesia/SynthesiaController.m b/KompleteSynthesia/SynthesiaController.m index b655b2f..2263acc 100644 --- a/KompleteSynthesia/SynthesiaController.m +++ b/KompleteSynthesia/SynthesiaController.m @@ -103,15 +103,15 @@ - (id)initWithLogViewController:(LogViewController*)logViewController delegate:( dataFormatExpected = NO; needsConfigurationPatch = YES; - [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self - selector:@selector(synthesiaMayHaveChangedStatus:) - name:NSWorkspaceDidActivateApplicationNotification - object:nil]; - - [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self - selector:@selector(synthesiaMayHaveChangedStatus:) - name:NSWorkspaceDidTerminateApplicationNotification - object:nil]; + NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; + [[workspace notificationCenter] addObserver:self + selector:@selector(synthesiaMayHaveChangedStatus:) + name:NSWorkspaceDidActivateApplicationNotification + object:nil]; + [[workspace notificationCenter] addObserver:self + selector:@selector(synthesiaMayHaveChangedStatus:) + name:NSWorkspaceDidTerminateApplicationNotification + object:nil]; } return self; } From 403243b9414f89a325fe1791516eb86d83cfd44c Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Sat, 27 Apr 2024 20:48:44 +0200 Subject: [PATCH 04/12] chore: renamed class methods --- KompleteSynthesia/AppDelegate.m | 2 +- KompleteSynthesia/UpdateManager.h | 4 ++-- KompleteSynthesia/UpdateManager.m | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/KompleteSynthesia/AppDelegate.m b/KompleteSynthesia/AppDelegate.m index 315cc2a..33108ad 100644 --- a/KompleteSynthesia/AppDelegate.m +++ b/KompleteSynthesia/AppDelegate.m @@ -249,7 +249,7 @@ - (void)applicationDidFinishInitializingWithSynthesiaRunning [userDefaults registerDefaults:@{kAppDefaultCheckForUpdate : @(YES)}]; BOOL checkForUpdate = [userDefaults boolForKey:kAppDefaultCheckForUpdate]; if (checkForUpdate) { - [UpdateManager UpdateCheckWithCompletion:^(NSString* state) { + [UpdateManager updateCheckWithCompletion:^(NSString* state) { NSString* message = [NSString stringWithFormat:@"update check: %@", state]; [self.log logLine:message]; }]; diff --git a/KompleteSynthesia/UpdateManager.h b/KompleteSynthesia/UpdateManager.h index 1b62c34..24df559 100644 --- a/KompleteSynthesia/UpdateManager.h +++ b/KompleteSynthesia/UpdateManager.h @@ -13,8 +13,8 @@ extern NSString* kAppDefaultCheckForUpdate; @interface UpdateManager : NSObject -+ (void)UpdateCheckWithCompletion:(void (^)(NSString* status))completion; -+ (BOOL)CheckForUpdates; ++ (void)updateCheckWithCompletion:(void (^)(NSString* status))completion; ++ (BOOL)checkForUpdates; @end diff --git a/KompleteSynthesia/UpdateManager.m b/KompleteSynthesia/UpdateManager.m index 4681a69..2ed1ad8 100644 --- a/KompleteSynthesia/UpdateManager.m +++ b/KompleteSynthesia/UpdateManager.m @@ -18,7 +18,7 @@ @implementation UpdateManager -+ (NSString*)LatestReleaseTag:(NSArray*)releases forPreReleases:(BOOL)pre ++ (NSString*)latestReleaseTag:(NSArray*)releases forPreReleases:(BOOL)pre { // GitHub returns the tags in chronological order. That means we can pass through // the returned tags and find the first one that is not a pre-release to find the @@ -37,7 +37,7 @@ + (NSString*)LatestReleaseTag:(NSArray*)releases forPreReleases:(BOOL)pre return nil; } -+ (void)UpdateCheckWithCompletion:(void (^)(NSString* status))completion ++ (void)updateCheckWithCompletion:(void (^)(NSString* status))completion { NSString* repo = [NSString stringWithFormat:@"https://api.github.com/repos/%@/%@/tags", kOwner, kProject]; @@ -63,7 +63,7 @@ + (void)UpdateCheckWithCompletion:(void (^)(NSString* status))completion // If we were running a pre-release, we would be interested in // pre-release updates. BOOL runningPreRelease = dots != 1; - tag = [UpdateManager LatestReleaseTag:results forPreReleases:runningPreRelease]; + tag = [UpdateManager latestReleaseTag:results forPreReleases:runningPreRelease]; if ([tag compare:versionTag] != NSOrderedSame) { NSLog(@"there is a different version available"); status = @"update available"; @@ -105,7 +105,7 @@ + (void)UpdateCheckWithCompletion:(void (^)(NSString* status))completion [dataTask resume]; } -+ (BOOL)CheckForUpdates ++ (BOOL)checkForUpdates { NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; return [userDefaults boolForKey:kAppDefaultCheckForUpdate]; From c9a0c6fec5e6593965870a27c81a7b44ef2d2fb5 Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Sat, 27 Apr 2024 20:49:20 +0200 Subject: [PATCH 05/12] chore: warning fix --- KompleteSynthesia/FuzzingWindowController.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/KompleteSynthesia/FuzzingWindowController.m b/KompleteSynthesia/FuzzingWindowController.m index fcc4927..8ee7a1e 100644 --- a/KompleteSynthesia/FuzzingWindowController.m +++ b/KompleteSynthesia/FuzzingWindowController.m @@ -132,8 +132,9 @@ - (void)startTimer if (self->_hidController.lightGuideUpdateMessage[0] == 0xFF) { self->_hidController.lightGuideUpdateMessage[1] += 0x0C; } - [_delegate preferencesUpdatedKeyState:self->_hidController.lightGuideUpdateMessage[1] - forKeyIndex:0]; + [self->_delegate + preferencesUpdatedKeyState:self->_hidController.lightGuideUpdateMessage[1] + forKeyIndex:0]; self->_hidController.lightGuideUpdateMessage[0]++; }]; } From e191e1541671fb7aa80cba32139fe4db030959b8 Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Sat, 27 Apr 2024 20:49:57 +0200 Subject: [PATCH 06/12] chore: xib updates --- KompleteSynthesia/FuzzingWindowController.xib | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/KompleteSynthesia/FuzzingWindowController.xib b/KompleteSynthesia/FuzzingWindowController.xib index 654126d..27e525e 100644 --- a/KompleteSynthesia/FuzzingWindowController.xib +++ b/KompleteSynthesia/FuzzingWindowController.xib @@ -1,8 +1,8 @@ - + - + @@ -28,7 +28,7 @@ - + @@ -37,7 +37,7 @@ - + @@ -47,7 +47,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -65,7 +65,7 @@ - + @@ -114,7 +114,7 @@ - + From e52c0a82986f39cf9034443872e221ea4e84ba92 Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Sat, 27 Apr 2024 20:52:39 +0200 Subject: [PATCH 07/12] chore: refactorings and comment cleanups --- KompleteSynthesia/HIDController.m | 104 ++---------------- KompleteSynthesia/MIDI2HIDController.m | 10 +- KompleteSynthesia/MIDIController.m | 4 +- .../PreferencesWindowController.m | 30 ++--- .../PreferencesWindowController.xib | 1 - 5 files changed, 34 insertions(+), 115 deletions(-) diff --git a/KompleteSynthesia/HIDController.m b/KompleteSynthesia/HIDController.m index 3f54811..71988bd 100644 --- a/KompleteSynthesia/HIDController.m +++ b/KompleteSynthesia/HIDController.m @@ -38,32 +38,17 @@ // Confirmed that this is the way KompleteKontrol initializes the controller by capturing // the USB traffic. const uint8_t kCommandInit = 0xA0; -uint8_t kKompleteKontrolInit[] = {kCommandInit, 0x00, 0x00}; - -/* -// FIXME: This likely is not be enough to get the MK3 controller fully initialized. It is what -// FIXME: Komplete Kontrol sends on an 8 second interval to the controller. -const uint8_t kKompleteKontrolInitMK3[] = {0x06, 0x00, 0x00, 0x00, 0x93, 0x02, 0xcd, 0x01, 0x2c, 0x90}; -*/ const uint8_t kCommandLightGuideUpdateMK1 = 0x82; const uint8_t kCommandLightGuideUpdateMK2 = 0x81; const uint8_t kCommandLightGuideUpdateMK3 = 0x83; -// FIXME: This appears to be wrong for MK3 devices -- instead of lighting keys, we are -// FIXME: lighting the touchstrip with 0x81. -// const uint8_t kCommandLightGuideUpdateMK3 = 0x81; - -// See https://github.com/tillt/KompleteSynthesia/discussions/29#discussioncomment-8089141 -// const uint8_t kKompleteKontrolLightGuidePrefixMK3[] = {0x93, 0x02, 0xCD, 0x01, 0x16, 0x92, 0xCD, 0x01, -// 0x51, 0x81, 0xCC, 0xFC, 0xDC, 0x00, 0x80}; -// const uint8_t kCommandLightGuideKeyCommandMK3 = 0x92; - -// const size_t kKompleteKontrolLightGuideMessageSizeMK3 = 403; - +const size_t kKompleteKontrolInitMessageSize = 3; const size_t kKompleteKontrolLightGuideMessageSize = 250; const size_t kKompleteKontrolLightGuideKeyMapSize = kKompleteKontrolLightGuideMessageSize - 1; +const uint8_t kCommandInitMessage[kKompleteKontrolInitMessageSize] = {kCommandInit, 0x00, 0x00}; + const uint8_t kCommandButtonLightsUpdateMK1 = 0x80; // FIXME: Noone ever confirmed if this was working as intended so far. const uint8_t kCommandButtonLightsUpdateMK2 = 0x80; @@ -120,8 +105,9 @@ @implementation HIDController { NSMutableData* lightGuideStreamMK3; - unsigned char buttonLightingFeedback[kKompleteKontrolButtonsMessageSize]; unsigned char buttonLightingUpdateMessage[kKompleteKontrolButtonsMessageSize]; + // We are duplicating the original lighting state here for feedback. + unsigned char buttonLightingFeedback[kKompleteKontrolButtonsMessageSize]; // FIXME: This may need double-buffering, not sure. unsigned char inputBuffer[kInputBufferSize]; @@ -181,28 +167,21 @@ - (BOOL)setupWithError:(NSError**)error if ([self registerKeyboardController:error] == NO) { return NO; } + + _initialCommand = (uint8_t*)kCommandInitMessage; + if ([self initKeyboardController:error] == NO) { return NO; } - /* - if (_mk == 3) { - // TODO: Make this less magic. Consider abstracting away from this direct buffer access. - _keys = lightGuideUpdateMessage + 4 + sizeof(kKompleteKontrolLightGuidePrefixMK3); - } else { - _keys = &lightGuideUpdateMessage[1]; - } - */ _keys = &_lightGuideUpdateMessage[1]; - - [self lightKeysWithColor:kKeyColorUnpressed]; - _buttons = &buttonLightingUpdateMessage[1]; _feedbackIntensityBuffer = &buttonLightingFeedback[1]; - memset(_buttons, 0, kKompleteKontrolButtonsMapSize); memset(_feedbackIntensityBuffer, 0, kKompleteKontrolButtonsMapSize); + [self lightKeysWithColor:kKeyColorUnpressed]; + // Supported controls get illuminated. _buttons[kKompleteKontrolButtonIdPlay] = kKompleteKontrolColorWhite; _buttons[kKompleteKontrolButtonIdJogDown] = kKompleteKontrolColorWhite; @@ -452,38 +431,9 @@ - (IOHIDDeviceRef)detectKeyboardController:(NSError**)error _mk = [supportedDevices[@(product)][@"mk"] intValue]; _keyOffset = [supportedDevices[@(product)][@"offset"] intValue]; - /* - _lightGuideUpdateMessageSize = - _mk == 3 ? kKompleteKontrolLightGuideMessageSizeMK3 : kKompleteKontrolLightGuideMessageSize; - - lightGuideStreamMK3 = nil; - if (_mk == 3) { - // Nice try but doesnt work at all :( - lightGuideStreamMK3 = [[NSMutableData alloc] initWithCapacity:lightGuideUpdateMessageSize]; - unsigned int length = (unsigned int)lightGuideUpdateMessageSize - 4; - [lightGuideStreamMK3 appendBytes:&length length:sizeof(length)]; - [lightGuideStreamMK3 appendBytes:&kKompleteKontrolLightGuidePrefixMK3 - length:sizeof(kKompleteKontrolLightGuidePrefixMK3)]; - for (int i = 0; i < 128; i++) { - unsigned char entry[] = {0x92, 0x00, 0x00}; - [lightGuideStreamMK3 appendBytes:entry length:sizeof(entry)]; - } - lightGuideUpdateMessage = (unsigned char*)lightGuideStreamMK3.bytes; - } else { - lightGuideUpdateMessage = calloc(lightGuideUpdateMessageSize, 1); - lightGuideUpdateMessage[0] = _mk == 1 ? kCommandLightGuideUpdateMK1 : kCommandLightGuideUpdateMK2; - - _initialCommand = kKompleteKontrolInit; - _initialCommandLength = sizeof(kKompleteKontrolInit); - } - */ - _lightGuideUpdateMessageSize = kKompleteKontrolLightGuideMessageSize; - _lightGuideUpdateMessage = calloc(_lightGuideUpdateMessageSize, sizeof(uint8_t)); - - _initialCommand = kKompleteKontrolInit; - _initialCommandLength = sizeof(kKompleteKontrolInit); + _initialCommandLength = kKompleteKontrolInitMessageSize; // FIXME: This is likely wrong for MK1 devices! switch (_mk) { @@ -557,9 +507,6 @@ - (BOOL)registerKeyboardController:(NSError**)error - (BOOL)initKeyboardController:(NSError**)error { - // // This was guessing from captured USB traffic. It does however not really do anything, it seems. - // const uint8_t* init = _mk == 3 ? kKompleteKontrolInitMK3 : kKompleteKontrolInit; - // size_t length = _mk == 3 ? sizeof(kKompleteKontrolInitMK3) : sizeof(kKompleteKontrolInit); IOReturn ret = IOHIDDeviceSetReport(device, kIOHIDReportTypeOutput, *_initialCommand, _initialCommand, _initialCommandLength); if (ret != kIOReturnSuccess) { @@ -567,7 +514,7 @@ - (BOOL)initKeyboardController:(NSError**)error NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Keyboard Error: %@", [USBController descriptionWithIOReturn:ret]], - NSLocalizedRecoverySuggestionErrorKey : @"This is entirely unexpected - how did you get here?" + NSLocalizedRecoverySuggestionErrorKey : @"Init of the controller failed" }; *error = [NSError errorWithDomain:[[NSBundle bundleForClass:[self class]] bundleIdentifier] code:ret @@ -611,15 +558,6 @@ - (BOOL)setReport:(const unsigned char*)report length:(size_t)length error:(NSEr - (BOOL)updateLightGuideMap:(NSError**)error { extern const double kTimeoutDelay; - - /* - if (_mk == 3) { - // Initial attempt - does nothing even though it looked promising from when reversing. - BOOL ret = [usb bulkWriteData:lightGuideStreamMK3 error:error]; - [usb waitForBulkTransfer:kTimeoutDelay]; - return ret; - } - */ return [self setReport:_lightGuideUpdateMessage length:_lightGuideUpdateMessageSize error:error]; } @@ -630,15 +568,7 @@ - (void)lightKey:(int)key color:(unsigned char)color setMk1ColorWithMk2ColorCode(color, &_keys[key * 3]); break; case 2: - _keys[key] = color; - break; case 3: - /* - // Initial attempt - does nothing even though it looked promising from when reversing. - _keys[key * 3 + 0] = kCommandLightGuideKeyCommandMK3; - _keys[key * 3 + 1] = key; - _keys[key * 3 + 2] = color; - */ _keys[key] = color; break; } @@ -661,16 +591,6 @@ - (void)lightKeysWithColor:(unsigned char)color case 3: memset(_keys, color, kKompleteKontrolLightGuideKeyMapSize); break; - /* - case 3: - // Initial attempt - does nothing even though it looked promising from when reversing. - for (unsigned int i = 0; i < 128; i++) { - _keys[i * 3 + 0] = kCommandLightGuideKeyCommandMK3; - _keys[i * 3 + 1] = i; - _keys[i * 3 + 2] = color; - } - break; - */ } [self updateLightGuideMap:nil]; diff --git a/KompleteSynthesia/MIDI2HIDController.m b/KompleteSynthesia/MIDI2HIDController.m index 1dbbb56..f8a23da 100644 --- a/KompleteSynthesia/MIDI2HIDController.m +++ b/KompleteSynthesia/MIDI2HIDController.m @@ -259,7 +259,7 @@ - (void)lightNote:(unsigned int)note [hid lightKey:key color:[self lightColorWithState:keyStates[key]]]; } -#pragma mark MIDIControllerDelegate +#pragma mark - MIDIControllerDelegate - (void)receivedMIDIEvent:(unsigned char)cv channel:(unsigned char)channel @@ -301,7 +301,7 @@ - (void)receivedMIDIEvent:(unsigned char)cv }); } -#pragma mark HIDControllerDelegate +#pragma mark - HIDControllerDelegate - (void)deviceRemoved { @@ -404,10 +404,10 @@ - (void)receivedEvent:(const int)event value:(int)value break; case kKompleteKontrolButtonIdKnob1: if (value > 0) { - [log logLine:@"KNOB1 -> sending volume up"]; + [log logLine:@"KNOB1 -> sending VOLUME UP"]; [VirtualEvent triggerAuxKeyEvents:0]; } else if (value < 0) { - [log logLine:@"KNOB1 -> sending volume down"]; + [log logLine:@"KNOB1 -> sending VOLUME DOWN"]; [VirtualEvent triggerAuxKeyEvents:1]; } [_delegate updateVolume:self]; @@ -415,6 +415,4 @@ - (void)receivedEvent:(const int)event value:(int)value } } -#pragma mark HIDControllerDelegate - @end diff --git a/KompleteSynthesia/MIDIController.m b/KompleteSynthesia/MIDIController.m index d09e729..b69aff7 100644 --- a/KompleteSynthesia/MIDIController.m +++ b/KompleteSynthesia/MIDIController.m @@ -101,8 +101,8 @@ - (BOOL)setupWithError:(NSError**)error // MIDIInputPortCreateWithProtocol does not exist on macOS 10.15. We could replace this // logic with `MIDIInputPortCreateWithBlock` which works based on MIDIPackets and not - // MIDIEvents - that in turn makes the parser a more complex and prone to failurea. But - // it would give us 10.15 (catalina) compatiblity. + // MIDIEvents - that in turn makes the parser a lot more complex and prone to failures. + // This would however give us 10.15 (catalina) compatiblity. status = MIDIInputPortCreateWithProtocol(client, (__bridge CFStringRef)kMIDIInputInterfaceLightLoopback, kMIDIProtocol_1_0, &portLight, receiveBlockLightLoopback); if (status != 0) { diff --git a/KompleteSynthesia/PreferencesWindowController.m b/KompleteSynthesia/PreferencesWindowController.m index e1f5421..c1c8e7c 100644 --- a/KompleteSynthesia/PreferencesWindowController.m +++ b/KompleteSynthesia/PreferencesWindowController.m @@ -44,7 +44,7 @@ - (void)windowDidLoad self.mirrorSynthesiaToControllerScreen.enabled = _video != nil; [self.mirrorSynthesiaToControllerScreen setState:_video.mirrorSynthesiaApplicationWindow ? NSControlStateValueOn : NSControlStateValueOff]; - [self.checkForUpdates setState:[UpdateManager CheckForUpdates] ? NSControlStateValueOn : NSControlStateValueOff]; + [self.checkForUpdates setState:[UpdateManager checkForUpdates] ? NSControlStateValueOn : NSControlStateValueOff]; } - (IBAction)selectKeyState:(id)sender @@ -77,18 +77,6 @@ - (IBAction)selectKeyState:(id)sender [popOver showRelativeToRect:colorField.frame ofView:[self.window contentView] preferredEdge:NSMaxXEdge]; } -- (void)keyStatePicked:(const unsigned char)keyState index:(const unsigned char)index -{ - NSLog(@"picked key state %02Xh for map index %d", keyState, index); - - assert(controls.count > index); - ColorField* colorField = controls[index]; - colorField.keyState = keyState; - [colorField setNeedsDisplay:YES]; - - [self.delegate preferencesUpdatedKeyState:keyState forKeyIndex:index]; -} - - (IBAction)fowardingValueChanged:(id)sender { _midi2hid.forwardButtonsToSynthesiaOnly = self.forwardButtonsOnlyToSynthesia.state == NSControlStateValueOn; @@ -131,10 +119,24 @@ - (IBAction)assertSynthesiaConfig:(id)sender - (IBAction)checkForUpdate:(id)sender { [self.progress startAnimation:self]; - [UpdateManager UpdateCheckWithCompletion:^(NSString* status) { + [UpdateManager updateCheckWithCompletion:^(NSString* status) { [self.progress stopAnimation:self]; [self.updateStatusField setStringValue:status]; }]; } +#pragma mark - PaletteViewControllerDelegate + +- (void)keyStatePicked:(const unsigned char)keyState index:(const unsigned char)index +{ + NSLog(@"picked key state %02Xh for map index %d", keyState, index); + + assert(controls.count > index); + ColorField* colorField = controls[index]; + colorField.keyState = keyState; + [colorField setNeedsDisplay:YES]; + + [self.delegate preferencesUpdatedKeyState:keyState forKeyIndex:index]; +} + @end diff --git a/KompleteSynthesia/PreferencesWindowController.xib b/KompleteSynthesia/PreferencesWindowController.xib index 770ceaa..4a940d7 100644 --- a/KompleteSynthesia/PreferencesWindowController.xib +++ b/KompleteSynthesia/PreferencesWindowController.xib @@ -284,7 +284,6 @@ That file belongs to Synthesia and should be located in the folder ~/Library/App - From c8fc3547c069d26934bcce05c2602794e21a3d36 Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Sat, 27 Apr 2024 20:53:03 +0200 Subject: [PATCH 08/12] chore: copyright update --- KompleteSynthesia/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KompleteSynthesia/Info.plist b/KompleteSynthesia/Info.plist index 16065cf..b6704a8 100644 --- a/KompleteSynthesia/Info.plist +++ b/KompleteSynthesia/Info.plist @@ -25,7 +25,7 @@ LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright - Copyright © 2023 Till Toenshoff. All rights reserved. + Copyright © 2023-2024 Till Toenshoff. All rights reserved. NSMainNibFile MainMenu NSPrincipalClass From 60620eec20bee9f4d3f6dc52a37e769489ffc6ef Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Sat, 27 Apr 2024 20:53:29 +0200 Subject: [PATCH 09/12] chore: ib update --- KompleteSynthesia/PreferencesWindowController.xib | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KompleteSynthesia/PreferencesWindowController.xib b/KompleteSynthesia/PreferencesWindowController.xib index 4a940d7..d5aa73d 100644 --- a/KompleteSynthesia/PreferencesWindowController.xib +++ b/KompleteSynthesia/PreferencesWindowController.xib @@ -1,8 +1,8 @@ - + - + From 20bfb8ec81191d5b41a5f8d80fecac910531572b Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Sat, 27 Apr 2024 20:54:05 +0200 Subject: [PATCH 10/12] feat: confirmed S88MK3 product id --- KompleteSynthesia/USBController.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/KompleteSynthesia/USBController.h b/KompleteSynthesia/USBController.h index 1d139b2..fd5cfa9 100644 --- a/KompleteSynthesia/USBController.h +++ b/KompleteSynthesia/USBController.h @@ -23,9 +23,9 @@ typedef NS_ENUM(uint32_t, ProductID) { kPID_S61MK2 = 0x1620, kPID_S88MK2 = 0x1630, // MK3 controllers. - kPID_S49MK3 = 0x2100, // FIXME: NO IDEA - THESE ARE PLACEHOLDERS SO FAR + kPID_S49MK3 = 0x2100, // FIXME: NO IDEA - THIS IS A PLACEHOLDER SO FAR kPID_S61MK3 = 0x2110, // Confirmed, thanks to @Bounga. - kPID_S88MK3 = 0x2120 // FIXME: NO IDEA - THESE ARE PLACEHOLDERS SO FAR + kPID_S88MK3 = 0x2120, // Confirmed, thanks to @ninanoe. }; @class LogViewController; From af12e4373b03c644d262c3a8c262a93e65be3eec Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Sat, 27 Apr 2024 23:11:09 +0200 Subject: [PATCH 11/12] feat: slight state update for signalling active pause --- KompleteSynthesia/FuzzingWindowController.m | 17 +++++++++++++- KompleteSynthesia/FuzzingWindowController.xib | 22 +++++++++---------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/KompleteSynthesia/FuzzingWindowController.m b/KompleteSynthesia/FuzzingWindowController.m index 8ee7a1e..052c0a1 100644 --- a/KompleteSynthesia/FuzzingWindowController.m +++ b/KompleteSynthesia/FuzzingWindowController.m @@ -56,6 +56,9 @@ - (void)windowDidLoad [_delegate preferencesUpdatedKeyState:0x00 forKeyIndex:0]; + _delaySlider.target = self; + _delaySlider.action = @selector(delayChanged:); + commandUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:kCommandUpdateTimerDelay repeats:YES @@ -79,17 +82,28 @@ - (void)controlTextDidEndEditing:(NSNotification*)notification withLength:_hidController.initialCommandLength]; } +- (void)delayChanged:(NSSlider*)sender +{ + [self pause:sender]; + [self pause:sender]; +} + - (void)updateButtonStates { if (paused) { _startButton.enabled = NO; _pauseButton.enabled = YES; + _pauseButton.state = NSControlStateValueMixed; + [_pauseButton setButtonType:NSButtonTypePushOnPushOff]; _stopButton.enabled = YES; } else { _startButton.enabled = fuzzTimer == nil; _stopButton.enabled = fuzzTimer != nil; _pauseButton.enabled = fuzzTimer != nil; + _pauseButton.state = fuzzTimer != nil ? NSControlStateValueOn : NSControlStateValueOff; + [_pauseButton setButtonType:NSButtonTypeMomentaryPushIn]; } + // [_pauseButton highlight:_pauseButton.state == NSControlStateValueMixed]; } - (void)stopTimer @@ -112,10 +126,11 @@ - (IBAction)pause:(id)sender if (fuzzTimer != nil) { paused = YES; [self stopTimer]; - } else { + } else if (paused == YES) { paused = NO; [self startTimer]; } + [self updateButtonStates]; } - (void)startTimer diff --git a/KompleteSynthesia/FuzzingWindowController.xib b/KompleteSynthesia/FuzzingWindowController.xib index 27e525e..2d80d78 100644 --- a/KompleteSynthesia/FuzzingWindowController.xib +++ b/KompleteSynthesia/FuzzingWindowController.xib @@ -96,17 +96,6 @@ - @@ -123,6 +112,17 @@ + From e645b5bea5d87b87db9f38d83be6e42895a5a996 Mon Sep 17 00:00:00 2001 From: Till Toenshoff Date: Sat, 27 Apr 2024 23:12:27 +0200 Subject: [PATCH 12/12] chore: even though it stutters a bit - it helps keeping the delegate callbacks deterministically named --- KompleteSynthesia/HIDController.h | 2 +- KompleteSynthesia/HIDController.m | 6 +++--- KompleteSynthesia/MIDI2HIDController.m | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/KompleteSynthesia/HIDController.h b/KompleteSynthesia/HIDController.h index 602350f..c6418d3 100644 --- a/KompleteSynthesia/HIDController.h +++ b/KompleteSynthesia/HIDController.h @@ -120,7 +120,7 @@ enum { @class USBController; @protocol HIDControllerDelegate -- (void)receivedEvent:(const int)event value:(int)value; +- (void)receivedHIDEvent:(const int)event value:(int)value; - (void)deviceRemoved; @end diff --git a/KompleteSynthesia/HIDController.m b/KompleteSynthesia/HIDController.m index 71988bd..29831c4 100644 --- a/KompleteSynthesia/HIDController.m +++ b/KompleteSynthesia/HIDController.m @@ -329,7 +329,7 @@ - (void)receivedReport:(unsigned char*)report length:(int)length const short int* newValue = (short int*)&report[10]; if (lastVolumeKnobValue != INTMAX_C(16)) { int delta = *newValue - lastVolumeKnobValue; - [_delegate receivedEvent:kKompleteKontrolButtonIdKnob1 value:delta]; + [_delegate receivedHIDEvent:kKompleteKontrolButtonIdKnob1 value:delta]; } lastVolumeKnobValue = *newValue; return; @@ -343,7 +343,7 @@ - (void)receivedReport:(unsigned char*)report length:(int)length [self feedbackWithEvent:keyEvents[i].identifier]; } - [_delegate receivedEvent:keyEvents[i].identifier value:0]; + [_delegate receivedHIDEvent:keyEvents[i].identifier value:0]; return; } @@ -357,7 +357,7 @@ - (void)receivedReport:(unsigned char*)report length:(int)length delta = 1; } if (delta != 0) { - [_delegate receivedEvent:kKompleteKontrolButtonIdJogScroll value:delta]; + [_delegate receivedHIDEvent:kKompleteKontrolButtonIdJogScroll value:delta]; } lastJogWheelValue = report[30]; diff --git a/KompleteSynthesia/MIDI2HIDController.m b/KompleteSynthesia/MIDI2HIDController.m index f8a23da..a37f8af 100644 --- a/KompleteSynthesia/MIDI2HIDController.m +++ b/KompleteSynthesia/MIDI2HIDController.m @@ -315,7 +315,7 @@ - (void)deviceRemoved } } -- (void)receivedEvent:(const int)event value:(int)value +- (void)receivedHIDEvent:(const int)event value:(int)value { // These buttons shall work in all cases as it they do not intended to control Synthesia // but KompleteSynthesia.