diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index d9cfdef..44f5683 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -29,6 +29,7 @@ @"GBTurbo": @(kVK_Space), @"GBRewind": @(kVK_Tab), + @"GBSlow-Motion": @(kVK_Shift), @"GBFilter": @"NearestNeighbor", @"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE), diff --git a/Cocoa/GBButtons.h b/Cocoa/GBButtons.h index 0bd9fdc..314a930 100644 --- a/Cocoa/GBButtons.h +++ b/Cocoa/GBButtons.h @@ -12,6 +12,7 @@ typedef enum : NSUInteger { GBStart, GBTurbo, GBRewind, + GBUnderclock, GBButtonCount } GBButton; diff --git a/Cocoa/GBButtons.m b/Cocoa/GBButtons.m index db0f364..044e933 100644 --- a/Cocoa/GBButtons.m +++ b/Cocoa/GBButtons.m @@ -1,4 +1,4 @@ #import #import "GBButtons.h" -NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind"}; +NSString const *GBButtonNames[] = {@"Right", @"Left", @"Up", @"Down", @"A", @"B", @"Select", @"Start", @"Turbo", @"Rewind", @"Slow-Motion"}; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index c2dfe2d..655ab0a 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -16,6 +16,7 @@ NSPopUpButton *_colorCorrectionPopupButton; NSPopUpButton *_rewindPopupButton; NSButton *_aspectRatioCheckbox; + NSEventModifierFlags previousModifiers; } + (NSArray *)filterList @@ -145,6 +146,18 @@ [self makeFirstResponder:self.controlsTableView]; } +- (void) flagsChanged:(NSEvent *)event +{ + if (event.modifierFlags > previousModifiers) { + [self keyDown:event]; + } + else { + [self keyUp:event]; + } + + previousModifiers = event.modifierFlags; +} + - (IBAction)graphicFilterChanged:(NSPopUpButton *)sender { [[NSUserDefaults standardUserDefaults] setObject:[[self class] filterList][[sender indexOfSelectedItem]] diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 71aee65..5e1e31a 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -12,6 +12,9 @@ NSTrackingArea *tracking_area; BOOL _mouseHidingEnabled; bool enableAnalog; + bool underclockKeyDown; + double clockMultiplier; + NSEventModifierFlags previousModifiers; } - (void) awakeFromNib @@ -51,6 +54,7 @@ owner:self userInfo:nil]; [self addTrackingArea:tracking_area]; + clockMultiplier = 1.0; } - (void) filterChanged @@ -153,6 +157,14 @@ - (void) flip { + if (underclockKeyDown && clockMultiplier > 0.5) { + clockMultiplier -= 0.1; + GB_set_clock_multiplier(_gb, clockMultiplier); + } + if (!underclockKeyDown && clockMultiplier < 1.0) { + clockMultiplier += 0.1; + GB_set_clock_multiplier(_gb, clockMultiplier); + } current_buffer = (current_buffer + 1) % self.numberOfBuffers; [self setNeedsDisplay:YES]; } @@ -180,6 +192,10 @@ self.isRewinding = true; GB_set_turbo_mode(_gb, false, false); break; + + case GBUnderclock: + underclockKeyDown = true; + break; default: GB_set_key_state(_gb, (GB_key_t)i, true); @@ -210,6 +226,10 @@ case GBRewind: self.isRewinding = false; break; + + case GBUnderclock: + underclockKeyDown = false; + break; default: GB_set_key_state(_gb, (GB_key_t)i, false); @@ -242,6 +262,10 @@ GB_set_turbo_mode(_gb, false, false); } break; + + case GBUnderclock: + underclockKeyDown = state; + break; default: if (i < GB_KEY_A) { @@ -324,4 +348,16 @@ return _mouseHidingEnabled; } +- (void) flagsChanged:(NSEvent *)event +{ + if (event.modifierFlags > previousModifiers) { + [self keyDown:event]; + } + else { + [self keyUp:event]; + } + + previousModifiers = event.modifierFlags; +} + @end diff --git a/Cocoa/NSString+StringForKey.m b/Cocoa/NSString+StringForKey.m index 6126c58..f5a9aa3 100644 --- a/Cocoa/NSString+StringForKey.m +++ b/Cocoa/NSString+StringForKey.m @@ -13,21 +13,32 @@ { /* These cases are not handled by stringForVirtualKey */ switch (keyCode) { - case 115: return @"↖"; - case 119: return @"↘"; - case 116: return @"⇞"; - case 121: return @"⇟"; - case 51: return @"⌫"; - case 117: return @"⌦"; - case 76: return @"⌤"; - + + case kVK_Home: return @"↖"; + case kVK_End: return @"↘"; + case kVK_PageUp: return @"⇞"; + case kVK_PageDown: return @"⇟"; + case kVK_Delete: return @"⌫"; + case kVK_ForwardDelete: return @"⌦"; + case kVK_ANSI_KeypadEnter: return @"⌤"; + case kVK_CapsLock: return @"⇪"; + case kVK_Shift: return @"Left ⇧"; + case kVK_Control: return @"Left ⌃"; + case kVK_Option: return @"Left ⌥"; + case kVK_Command: return @"Left ⌘"; + case kVK_RightShift: return @"Right ⇧"; + case kVK_RightControl: return @"Right ⌃"; + case kVK_RightOption: return @"Right ⌥"; + case kVK_RightCommand: return @"Right ⌘"; + case kVK_Function: return @"fn"; + /* Label Keypad buttons accordingly */ default: - if ((keyCode < 82 || keyCode > 92)) { + if ((keyCode < kVK_ANSI_Keypad0 || keyCode > kVK_ANSI_Keypad9)) { return [NSPrefPaneUtils stringForVirtualKey:keyCode modifiers:0]; } - case 65: case 67: case 69: case 75: case 78: case 81: + case kVK_ANSI_KeypadDecimal: case kVK_ANSI_KeypadMultiply: case kVK_ANSI_KeypadPlus: case kVK_ANSI_KeypadDivide: case kVK_ANSI_KeypadMinus: case kVK_ANSI_KeypadEquals: return [@"Keypad " stringByAppendingString:[NSPrefPaneUtils stringForVirtualKey:keyCode modifiers:0]]; } } diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index c599225..b8bd246 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -17,14 +17,14 @@ - + - + - + @@ -33,7 +33,7 @@ - + @@ -67,7 +67,7 @@ - + @@ -76,7 +76,7 @@ - + @@ -97,7 +97,7 @@ - + @@ -117,7 +117,7 @@ - + @@ -141,7 +141,7 @@ - + @@ -161,7 +161,7 @@ - + @@ -170,7 +170,7 @@ - + @@ -201,14 +201,14 @@ - + - + - + diff --git a/Core/apu.c b/Core/apu.c index 69bc5ea..a6496da 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -359,7 +359,7 @@ void GB_apu_run(GB_gameboy_t *gb) if (gb->apu_output.sample_rate) { gb->apu_output.cycles_since_render += cycles; - double cycles_per_sample = CPU_FREQUENCY / (double)gb->apu_output.sample_rate; // TODO: this should be cached! + double cycles_per_sample = GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; if (gb->apu_output.sample_cycles > cycles_per_sample) { gb->apu_output.sample_cycles -= cycles_per_sample; @@ -837,7 +837,7 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned int sample_rate) gb->apu_output.sample_rate = sample_rate; gb->apu_output.buffer_position = 0; if (sample_rate) { - gb->apu_output.highpass_rate = pow(0.999958, CPU_FREQUENCY / (double)sample_rate); + gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate); } } diff --git a/Core/gb.c b/Core/gb.c index c2b6390..43c2980 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -100,6 +100,7 @@ void GB_init(GB_gameboy_t *gb) gb->async_input_callback = default_async_input_callback; #endif gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + gb->clock_multiplier = 1.0; GB_reset(gb); } @@ -116,7 +117,8 @@ void GB_init_cgb(GB_gameboy_t *gb) gb->async_input_callback = default_async_input_callback; #endif gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type - + gb->clock_multiplier = 1.0; + GB_reset(gb); } @@ -307,7 +309,7 @@ uint64_t GB_run_frame(GB_gameboy_t *gb) } gb->turbo = old_turbo; gb->turbo_dont_skip = old_dont_skip; - return gb->cycles_since_last_sync * FRAME_LENGTH * LCDC_PERIOD; + return gb->cycles_since_last_sync * 1000000000LL / GB_get_clock_rate(gb); } void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) @@ -580,3 +582,13 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t * return NULL; } } + +void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier) +{ + gb->clock_multiplier = multiplier; +} + +uint32_t GB_get_clock_rate(GB_gameboy_t *gb) +{ + return CPU_FREQUENCY * gb->clock_multiplier; +} diff --git a/Core/gb.h b/Core/gb.h index 8f41e78..3dea996 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -162,7 +162,6 @@ typedef enum { #define CPU_FREQUENCY 0x400000 #define DIV_CYCLES (0x100) #define INTERNAL_DIV_CYCLES (0x40000) -#define FRAME_LENGTH (1000000000LL * LCDC_PERIOD / CPU_FREQUENCY) // in nanoseconds #if !defined(MIN) #define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) @@ -495,6 +494,7 @@ struct GB_gameboy_internal_s { uint8_t boot_rom[0x900]; bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run() + double clock_multiplier; ); }; @@ -583,4 +583,9 @@ void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data); void GB_disconnect_serial(GB_gameboy_t *gb); +#ifdef GB_INTERNAL +uint32_t GB_get_clock_rate(GB_gameboy_t *gb); +#endif +void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier); + #endif /* GB_h */ diff --git a/Core/timing.c b/Core/timing.c index cbcfb82..c03803f 100644 --- a/Core/timing.c +++ b/Core/timing.c @@ -40,7 +40,7 @@ bool GB_timing_sync_turbo(GB_gameboy_t *gb) { if (!gb->turbo_dont_skip) { int64_t nanoseconds = get_nanoseconds(); - if (nanoseconds <= gb->last_sync + FRAME_LENGTH) { + if (nanoseconds <= gb->last_sync + (1000000000LL * LCDC_PERIOD / GB_get_clock_rate(gb))) { return true; } gb->last_sync = nanoseconds; @@ -57,7 +57,7 @@ void GB_timing_sync(GB_gameboy_t *gb) /* Prevent syncing if not enough time has passed.*/ if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return; - uint64_t target_nanoseconds = gb->cycles_since_last_sync * FRAME_LENGTH / LCDC_PERIOD; + uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / GB_get_clock_rate(gb); int64_t nanoseconds = get_nanoseconds(); if (labs((signed long)(nanoseconds - gb->last_sync)) < target_nanoseconds ) { nsleep(target_nanoseconds + gb->last_sync - nanoseconds);