From e0af961ad33cb786d7e3f7bc2af594d042f36876 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 14 Jan 2023 18:28:08 +0200 Subject: [PATCH] CoreHaptics support for rumble and button feedback --- AppleCommon/GBAudioClient.m | 2 +- Makefile | 2 +- iOS/GBBackgroundView.m | 6 +++ iOS/GBHapticManager.h | 7 +++ iOS/GBHapticManager.m | 93 +++++++++++++++++++++++++++++++++++++ iOS/GBViewController.m | 5 +- 6 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 iOS/GBHapticManager.h create mode 100644 iOS/GBHapticManager.m diff --git a/AppleCommon/GBAudioClient.m b/AppleCommon/GBAudioClient.m index 88a7855..8f57e1f 100644 --- a/AppleCommon/GBAudioClient.m +++ b/AppleCommon/GBAudioClient.m @@ -55,7 +55,7 @@ static OSStatus render( // Set our tone rendering function on the unit AURenderCallbackStruct input; input.inputProc = (void*)render; - input.inputProcRefCon = (__bridge void * _Nullable)(self); + input.inputProcRefCon = (__bridge void *)(self); err = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, diff --git a/Makefile b/Makefile index 8b6038f..3b3b3ba 100644 --- a/Makefile +++ b/Makefile @@ -186,7 +186,7 @@ endif CFLAGS += -arch arm64 -miphoneos-version-min=11.0 -isysroot $(SYSROOT) -IAppleCommon LDFLAGS += -arch arm64 OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -LDFLAGS += -lobjc -framework UIKit -framework Foundation -framework CoreGraphics -framework Metal -framework MetalKit -framework AudioToolbox -framework AVFoundation -miphoneos-version-min=11.0 -isysroot $(SYSROOT) +LDFLAGS += -lobjc -framework UIKit -framework Foundation -framework CoreGraphics -framework Metal -framework MetalKit -framework AudioToolbox -framework AVFoundation -weak_framework CoreHaptics -miphoneos-version-min=11.0 -isysroot $(SYSROOT) CODESIGN := codesign -fs - else ifeq ($(PLATFORM),Darwin) diff --git a/iOS/GBBackgroundView.m b/iOS/GBBackgroundView.m index 81d9cfa..4af17aa 100644 --- a/iOS/GBBackgroundView.m +++ b/iOS/GBBackgroundView.m @@ -1,5 +1,6 @@ #import "GBBackgroundView.h" #import "GBViewMetal.h" +#import "GBHapticManager.h" double CGPointSquaredDistance(CGPoint a, CGPoint b) { @@ -35,6 +36,7 @@ static void positionView(UIImageView *view, CGPoint position) UIImageView *_bButtonView; UIImageView *_startButtonView; UIImageView *_selectButtonView; + GB_key_mask_t _lastMask; } - (instancetype)init @@ -130,6 +132,10 @@ static void positionView(UIImageView *view, CGPoint position) } } GB_set_key_mask(_gbView.gb, mask); + if (mask & ~_lastMask) { + [[GBHapticManager sharedManager] doTapHaptic]; + } + _lastMask = mask; } - (BOOL)isMultipleTouchEnabled diff --git a/iOS/GBHapticManager.h b/iOS/GBHapticManager.h new file mode 100644 index 0000000..e2c543c --- /dev/null +++ b/iOS/GBHapticManager.h @@ -0,0 +1,7 @@ +#import + +@interface GBHapticManager : NSObject ++ (instancetype)sharedManager; +- (void)doTapHaptic; +- (void)setRumbleStrength:(double)rumble; +@end diff --git a/iOS/GBHapticManager.m b/iOS/GBHapticManager.m new file mode 100644 index 0000000..1a2a7ff --- /dev/null +++ b/iOS/GBHapticManager.m @@ -0,0 +1,93 @@ +#import "GBHapticManager.h" +#import + +@implementation GBHapticManager +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpartial-availability" + CHHapticEngine *_engine; + id _rumblePlayer; +#pragma clang diagnostic pop + double _rumble; +} + ++ (instancetype)sharedManager +{ + static dispatch_once_t onceToken; + static GBHapticManager *manager; + dispatch_once(&onceToken, ^{ + manager = [[self alloc] init]; + }); + return manager; +} + +- (instancetype)init +{ + self = [super init]; + if (!self) return nil; + if (@available(iOS 13.0, *)) { + _engine = [[CHHapticEngine alloc] initAndReturnError:nil]; + _engine.playsHapticsOnly = true; + _engine.autoShutdownEnabled = true; + } + else { + return nil; + } + if (!_engine) return nil; + return self; +} + +#pragma clang diagnostic ignored "-Wpartial-availability" + +- (CHHapticEvent *) eventWithType:(CHHapticEventType)type + sharpness:(double)sharpness + intensity:(double)intensity + duration:(NSTimeInterval)duration +{ + return [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticTransient + parameters:@[[[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticSharpness + value:sharpness], + [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity + value:intensity]] + relativeTime:CHHapticTimeImmediate + duration:duration]; +} + +- (void)doTapHaptic +{ + if (_rumble) return; + + CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:@[[self eventWithType:CHHapticEventTypeHapticTransient + sharpness:0.25 + intensity:0.75 + duration:1.0]] + parameters:nil + error:nil]; + id player = [_engine createPlayerWithPattern:pattern error:nil]; + + [player startAtTime:0 error:nil]; +} + +- (void)setRumbleStrength:(double)rumble +{ + if (rumble == 0) { + [_rumblePlayer stopAtTime:0 error:nil]; + _rumblePlayer = nil; + _rumble = 0; + return; + } + _rumble = rumble; + CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:@[[self eventWithType:CHHapticEventTypeHapticContinuous + sharpness:0.75 + intensity:rumble + duration:1.0]] + parameters:nil + error:nil]; + id newPlayer = [_engine createPlayerWithPattern:pattern error:nil]; + + [newPlayer startAtTime:0 error:nil]; + [_rumblePlayer stopAtTime:0 error:nil]; + _rumblePlayer = newPlayer; +} + +@end diff --git a/iOS/GBViewController.m b/iOS/GBViewController.m index 19c0677..497a1ff 100644 --- a/iOS/GBViewController.m +++ b/iOS/GBViewController.m @@ -6,6 +6,7 @@ #import "GBROMManager.h" #import "GBLoadROMTableViewController.h" #import "GBBackgroundView.h" +#import "GBHapticManager.h" #include @implementation GBViewController @@ -116,7 +117,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) [[NSNotificationCenter defaultCenter] addObserverForName:@"GBROMChanged" object:nil queue:nil - usingBlock:^(NSNotification * _Nonnull note) { + usingBlock:^(NSNotification *note) { [self loadROM]; [self start]; }]; @@ -305,7 +306,7 @@ static void rumbleCallback(GB_gameboy_t *gb, double amp) - (void)rumbleChanged:(double)amp { - // TODO + [[GBHapticManager sharedManager] setRumbleStrength:amp]; } - (void)updateRumbleMode