2 // Apple 2 SDL Portable Apple Emulator
5 // © 2017 Underground Software
7 // Loosely based on AppleWin by Tom Charlesworth which was based on AppleWin by
8 // Oliver Schmidt which was based on AppleWin by Michael O'Brien. :-) Parts are
9 // also derived from ApplePC. Too bad it was closed source--it could have been
10 // *the* premier Apple II emulator out there.
12 // JLH = James Hammons <jlhamm@acm.org>
15 // --- ---------- -----------------------------------------------------------
16 // JLH 11/12/2005 Initial port to SDL
17 // JLH 11/18/2005 Wired up graphic soft switches
18 // JLH 12/02/2005 Setup timer subsystem for more accurate time keeping
19 // JLH 12/12/2005 Added preliminary state saving support
20 // JLH 09/24/2013 Added //e support
25 // - Port to SDL [DONE]
27 // - Weed out unneeded functions [DONE]
29 // - 128K IIe related stuff [DONE]
30 // - State loading/saving
54 // Debug and misc. defines
56 #define THREADED_65C02
57 #define CPU_THREAD_OVERFLOW_COMPENSATION
59 //#define CPU_CLOCK_CHECKING
60 //#define THREAD_DEBUGGING
61 #define SOFT_SWITCH_DEBUGGING
65 uint8_t ram[0x10000], rom[0x10000]; // RAM & ROM spaces
66 uint8_t ram2[0x10000]; // Auxillary RAM
67 V65C02REGS mainCPU; // v65C02 execution context
68 uint8_t appleType = APPLE_TYPE_IIE;
69 FloppyDrive floppyDrive;
70 bool powerStateChangeRequested = false;
72 // Local variables (actually, they're global since they're not static)
74 uint8_t lastKeyPressed = 0;
76 bool openAppleDown = false;
77 bool closedAppleDown = false;
78 bool store80Mode = false;
80 bool slotCXROM = false;
81 bool slotC3ROM = false;
87 // Language card state (ROM read, no write)
88 uint8_t lcState = 0x02;
90 static bool running = true; // Machine running state flag...
91 static uint32_t startTicks;
92 static bool pauseMode = false;
93 static bool fullscreenDebounce = false;
94 static bool capsLock = false;
95 static bool capsLockDebounce = false;
96 static bool resetKeyDown = false;
98 // Vars to handle the //e's 2-key rollover
99 static SDL_Keycode keysHeld[2];
100 static uint8_t keysHeldAppleCode[2];
101 static uint8_t keyDownCount = 0;
105 static void SaveApple2State(const char * filename);
106 static bool LoadApple2State(const char * filename);
107 static void ResetApple2State(void);
109 // Local timer callback functions
111 static void FrameCallback(void);
112 static void BlinkTimer(void);
114 #ifdef THREADED_65C02
115 // Test of threaded execution of 6502
116 static SDL_Thread * cpuThread = NULL;
117 static SDL_cond * cpuCond = NULL;
118 static SDL_sem * mainSem = NULL;
119 static bool cpuFinished = false;
121 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
123 // Let's try a thread...
125 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
128 int CPUThreadFunc(void * data)
130 // Mutex must be locked for conditional to work...
131 // Also, must be created in the thread that uses it...
132 SDL_mutex * cpuMutex = SDL_CreateMutex();
134 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
135 float overflow = 0.0;
140 // decrement mainSem...
141 #ifdef THREAD_DEBUGGING
142 WriteLog("CPU: SDL_SemWait(mainSem);\n");
144 SDL_SemWait(mainSem);
146 // There are exactly 800 slices of 21.333 cycles per frame, so it works
148 #ifdef THREAD_DEBUGGING
149 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
151 for(int i=0; i<800; i++)
153 uint32_t cycles = 21;
154 overflow += 0.333333334;
162 // If the CTRL+Reset key combo is being held, make sure the RESET
163 // line stays asserted:
165 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
167 Execute65C02(&mainCPU, cycles);
168 WriteSampleToBuffer();
170 // Dunno if this is correct (seems to be close enough)...
171 vbl = (i < 670 ? true : false);
174 #ifdef THREAD_DEBUGGING
175 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
177 SDL_mutexP(cpuMutex);
178 // increment mainSem...
179 #ifdef THREAD_DEBUGGING
180 WriteLog("CPU: SDL_SemPost(mainSem);\n");
182 SDL_SemPost(mainSem);
183 #ifdef THREAD_DEBUGGING
184 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
186 SDL_CondWait(cpuCond, cpuMutex);
188 #ifdef THREAD_DEBUGGING
189 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
191 SDL_mutexV(cpuMutex);
193 while (!cpuFinished);
195 SDL_DestroyMutex(cpuMutex);
203 // Request a change in the power state of the emulated Apple
205 void SetPowerState(void)
207 powerStateChangeRequested = true;
212 // Load a file into RAM/ROM image space
214 bool LoadImg(char * filename, uint8_t * ram, int size)
216 FILE * fp = fopen(filename, "rb");
221 fread(ram, 1, size, fp);
228 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.0";
229 static void SaveApple2State(const char * filename)
231 WriteLog("Main: Saving Apple2 state...\n");
232 FILE * file = fopen(filename, "wb");
236 WriteLog("Could not open file \"%s\" for writing!\n", filename);
241 fwrite(stateHeader, 1, 18, file);
243 // Write out CPU state
244 fwrite(&mainCPU, 1, sizeof(mainCPU), file);
246 // Write out main memory
247 fwrite(ram, 1, 0x10000, file);
248 fwrite(ram2, 1, 0x10000, file);
250 // Write out state variables
251 fputc((uint8_t)keyDown, file);
252 fputc((uint8_t)openAppleDown, file);
253 fputc((uint8_t)closedAppleDown, file);
254 fputc((uint8_t)store80Mode, file);
255 fputc((uint8_t)vbl, file);
256 fputc((uint8_t)slotCXROM, file);
257 fputc((uint8_t)slotC3ROM, file);
258 fputc((uint8_t)ramrd, file);
259 fputc((uint8_t)ramwrt, file);
260 fputc((uint8_t)altzp, file);
261 fputc((uint8_t)ioudis, file);
262 fputc((uint8_t)dhires, file);
263 fputc((uint8_t)flash, file);
264 fputc((uint8_t)textMode, file);
265 fputc((uint8_t)mixedMode, file);
266 fputc((uint8_t)displayPage2, file);
267 fputc((uint8_t)hiRes, file);
268 fputc((uint8_t)alternateCharset, file);
269 fputc((uint8_t)col80Mode, file);
270 fputc(lcState, file);
272 // Write out floppy state
273 floppyDrive.SaveState(file);
278 static bool LoadApple2State(const char * filename)
280 WriteLog("Main: Loading Apple2 state...\n");
281 FILE * file = fopen(filename, "rb");
285 WriteLog("Could not open file \"%s\" for reading!\n", filename);
290 fread(buffer, 1, 18, file);
293 if (memcmp(buffer, stateHeader, 18) != 0)
296 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
301 fread(&mainCPU, 1, sizeof(mainCPU), file);
304 fread(ram, 1, 0x10000, file);
305 fread(ram2, 1, 0x10000, file);
307 // Read in state variables
308 keyDown = (bool)fgetc(file);
309 openAppleDown = (bool)fgetc(file);
310 closedAppleDown = (bool)fgetc(file);
311 store80Mode = (bool)fgetc(file);
312 vbl = (bool)fgetc(file);
313 slotCXROM = (bool)fgetc(file);
314 slotC3ROM = (bool)fgetc(file);
315 ramrd = (bool)fgetc(file);
316 ramwrt = (bool)fgetc(file);
317 altzp = (bool)fgetc(file);
318 ioudis = (bool)fgetc(file);
319 dhires = (bool)fgetc(file);
320 flash = (bool)fgetc(file);
321 textMode = (bool)fgetc(file);
322 mixedMode = (bool)fgetc(file);
323 displayPage2 = (bool)fgetc(file);
324 hiRes = (bool)fgetc(file);
325 alternateCharset = (bool)fgetc(file);
326 col80Mode = (bool)fgetc(file);
327 lcState = fgetc(file);
329 // Read in floppy state
330 floppyDrive.LoadState(file);
334 // Make sure things are in a sane state before execution :-P
335 mainCPU.RdMem = AppleReadMem;
336 mainCPU.WrMem = AppleWriteMem;
343 static void ResetApple2State(void)
346 openAppleDown = false;
347 closedAppleDown = false;
360 // Without this, you can wedge the system :-/
361 memset(ram, 0, 0x10000);
362 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
366 #ifdef CPU_CLOCK_CHECKING
368 uint32_t totalCPU = 0;
369 uint64_t lastClock = 0;
374 int main(int /*argc*/, char * /*argv*/[])
376 InitLog("./apple2.log");
378 srand(time(NULL)); // Initialize RNG
381 memset(ram, 0, 0x10000);
382 memset(rom, 0, 0x10000);
383 memset(ram2, 0, 0x10000);
389 // Set up V65C02 execution context
390 memset(&mainCPU, 0, sizeof(V65C02REGS));
391 mainCPU.RdMem = AppleReadMem;
392 mainCPU.WrMem = AppleWriteMem;
393 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
395 if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
397 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
401 WriteLog("About to initialize video...\n");
405 printf("Could not init screen: aborting!\n");
409 GUI::Init(sdlRenderer);
410 WriteLog("About to initialize audio...\n");
413 if (settings.autoStateSaving)
415 // Load last state from file...
416 if (!LoadApple2State(settings.autoStatePath))
417 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
421 InitializeEventList();
422 // Set frame to fire at 1/60 s interval
423 SetCallbackTime(FrameCallback, 16666.66666667);
424 // Set up blinking at 1/4 s intervals
425 SetCallbackTime(BlinkTimer, 250000);
426 startTicks = SDL_GetTicks();
428 #ifdef THREADED_65C02
429 // Kick off the CPU...
430 cpuCond = SDL_CreateCond();
431 mainSem = SDL_CreateSemaphore(1);
432 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
433 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
434 // SDL_sem * mainMutex = SDL_CreateMutex();
437 WriteLog("Entering main loop...\n");
441 double timeToNextEvent = GetTimeToNextEvent();
442 #ifndef THREADED_65C02
443 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
445 #ifdef CPU_CLOCK_CHECKING
446 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
452 #ifdef THREADED_65C02
453 WriteLog("Main: cpuFinished = true;\n");
456 WriteLog("Main: SDL_SemWait(mainSem);\n");
457 // Only do this if NOT in power off/emulation paused mode!
459 // Should lock until CPU thread is waiting...
460 SDL_SemWait(mainSem);
463 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
464 SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
465 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
466 SDL_WaitThread(cpuThread, NULL);
467 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
468 SDL_DestroyCond(cpuCond);
469 SDL_DestroySemaphore(mainSem);
471 // Autosave state here, if requested...
472 if (settings.autoStateSaving)
473 SaveApple2State(settings.autoStatePath);
475 floppyDrive.SaveImage(0);
476 floppyDrive.SaveImage(1);
492 -----------------------
493 space $A0 $A0 $A0 $A0 No xlation
494 RETURN $8D $8D $8D $8D No xlation
495 0 $B0 $B0 $B0 $B0 Need to screen shift+0 (?)
496 1! $B1 $B1 $A1 $A1 No xlation
497 2" $B2 $B2 $A2 $A2 No xlation
498 3# $B3 $B3 $A3 $A3 No xlation
499 4$ $B4 $B4 $A4 $A4 No xlation
500 5% $B5 $B5 $A5 $A5 No xlation
501 6& $B6 $B6 $A6 $A6 No xlation
502 7' $B7 $B7 $A7 $A7 No xlation
503 8( $B8 $B8 $A8 $A8 No xlation
504 9) $B9 $B9 $A9 $A9 No xlation
505 :* $BA $BA $AA $AA No xlation
506 ;+ $BB $BB $AB $AB No xlation
507 ,< $AC $AC $BC $BC No xlation
508 -= $AD $AD $BD $BD No xlation
509 .> $AE $AE $BE $BE No xlation
510 /? $AF $AF $BF $BF No xlation
523 M $CD $8D $DD $9D -> ODD
524 N^ $CE $8E $DE $9E -> ODD
526 P@ $D0 $90 $C0 $80 Need to xlate CTL+SHFT+P & SHFT+P (?)
539 ESC $9B $9B $9B $9B No xlation
543 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
544 // +CTRL+SHIFT (3). Order of keys is:
545 // Delete, left, tab, down, up, return, right, escape
546 // Space, single quote, comma, minus, period, slash
548 // Semicolon, equals, left bracket, backslash, right bracket, backquote
549 // Letters a-z (lowercase)
551 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
552 // keyboards, so this table should suffice for the shifted keys just fine.
554 uint8_t apple2e_keycode[4][56] = {
556 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
557 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
558 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
559 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
560 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
561 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
562 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
565 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
566 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
567 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
568 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
569 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
570 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
571 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
574 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
575 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
576 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
577 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
578 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
579 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
580 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
582 { // With CTRL+Shift held
583 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
584 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
585 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
586 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
587 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
588 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
589 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
593 static uint32_t frameCount = 0;
594 static void FrameCallback(void)
599 while (SDL_PollEvent(&event))
603 // Problem with using SDL_TEXTINPUT is that it causes key delay. :-/
604 // We get key delay regardless... :-/
607 //Need to do some key translation here, and screen out non-apple keys as well...
608 //(really, could do it all in SDL_KEYDOWN, would just have to get symbols &
609 // everything else done separately. this is slightly easier. :-P)
610 // if (event.key.keysym.sym == SDLK_TAB) // Prelim key screening...
611 if (event.edit.text[0] == '\t') // Prelim key screening...
614 lastKeyPressed = event.edit.text[0];
617 //kludge: should have a caps lock thingy here...
618 //or all uppercase for ][+...
619 // if (lastKeyPressed >= 'a' && lastKeyPressed <='z')
620 // lastKeyPressed &= 0xDF; // Convert to upper case...
625 // We do our own repeat handling thank you very much! :-)
626 if (event.key.repeat != 0)
629 // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
631 if ((event.key.keysym.mod & KMOD_CTRL)
632 && (event.key.keysym.mod & KMOD_SHIFT)
633 && (event.key.keysym.sym == SDLK_q))
636 // We return here, because we don't want to pick up any
637 // spurious keypresses with our exit sequence.
641 // CTRL+RESET key emulation (mapped to CTRL+HOME)
642 if ((event.key.keysym.mod & KMOD_CTRL)
643 && (event.key.keysym.sym == SDLK_HOME))
645 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
650 // There has GOT to be a better way off mapping SDLKs to our
651 // keyindex. But for now, this should suffice.
654 switch (event.key.keysym.sym)
656 case SDLK_BACKSPACE: keyIndex = 0; break;
657 case SDLK_LEFT: keyIndex = 1; break;
658 case SDLK_TAB: keyIndex = 2; break;
659 case SDLK_DOWN: keyIndex = 3; break;
660 case SDLK_UP: keyIndex = 4; break;
661 case SDLK_RETURN: keyIndex = 5; break;
662 case SDLK_RIGHT: keyIndex = 6; break;
663 case SDLK_ESCAPE: keyIndex = 7; break;
664 case SDLK_SPACE: keyIndex = 8; break;
665 case SDLK_QUOTE: keyIndex = 9; break;
666 case SDLK_COMMA: keyIndex = 10; break;
667 case SDLK_MINUS: keyIndex = 11; break;
668 case SDLK_PERIOD: keyIndex = 12; break;
669 case SDLK_SLASH: keyIndex = 13; break;
670 case SDLK_0: keyIndex = 14; break;
671 case SDLK_1: keyIndex = 15; break;
672 case SDLK_2: keyIndex = 16; break;
673 case SDLK_3: keyIndex = 17; break;
674 case SDLK_4: keyIndex = 18; break;
675 case SDLK_5: keyIndex = 19; break;
676 case SDLK_6: keyIndex = 20; break;
677 case SDLK_7: keyIndex = 21; break;
678 case SDLK_8: keyIndex = 22; break;
679 case SDLK_9: keyIndex = 23; break;
680 case SDLK_SEMICOLON: keyIndex = 24; break;
681 case SDLK_EQUALS: keyIndex = 25; break;
682 case SDLK_LEFTBRACKET: keyIndex = 26; break;
683 case SDLK_BACKSLASH: keyIndex = 27; break;
684 case SDLK_RIGHTBRACKET: keyIndex = 28; break;
685 case SDLK_BACKQUOTE: keyIndex = 29; break;
686 case SDLK_a: keyIndex = 30; break;
687 case SDLK_b: keyIndex = 31; break;
688 case SDLK_c: keyIndex = 32; break;
689 case SDLK_d: keyIndex = 33; break;
690 case SDLK_e: keyIndex = 34; break;
691 case SDLK_f: keyIndex = 35; break;
692 case SDLK_g: keyIndex = 36; break;
693 case SDLK_h: keyIndex = 37; break;
694 case SDLK_i: keyIndex = 38; break;
695 case SDLK_j: keyIndex = 39; break;
696 case SDLK_k: keyIndex = 40; break;
697 case SDLK_l: keyIndex = 41; break;
698 case SDLK_m: keyIndex = 42; break;
699 case SDLK_n: keyIndex = 43; break;
700 case SDLK_o: keyIndex = 44; break;
701 case SDLK_p: keyIndex = 45; break;
702 case SDLK_q: keyIndex = 46; break;
703 case SDLK_r: keyIndex = 47; break;
704 case SDLK_s: keyIndex = 48; break;
705 case SDLK_t: keyIndex = 49; break;
706 case SDLK_u: keyIndex = 50; break;
707 case SDLK_v: keyIndex = 51; break;
708 case SDLK_w: keyIndex = 52; break;
709 case SDLK_x: keyIndex = 53; break;
710 case SDLK_y: keyIndex = 54; break;
711 case SDLK_z: keyIndex = 55; break;
714 // Stuff the key in if we have a valid one...
715 if (keyIndex != 0xFF)
717 // Handle Shift, CTRL, & Shift+CTRL combos
720 if (event.key.keysym.mod & KMOD_CTRL)
723 if (event.key.keysym.mod & KMOD_SHIFT)
726 lastKeyPressed = apple2e_keycode[table][keyIndex];
731 // Buffer the key held. Note that the last key is always
732 // stuffed into keysHeld[0].
733 if (keyDownCount >= 2)
735 keysHeld[1] = keysHeld[0];
736 keysHeldAppleCode[1] = keysHeldAppleCode[0];
738 if (keyDownCount > 2)
742 keysHeld[0] = event.key.keysym.sym;
743 keysHeldAppleCode[0] = lastKeyPressed;
747 if (event.key.keysym.sym == SDLK_PAUSE)
749 pauseMode = !pauseMode;
754 SpawnMessage("*** PAUSED ***");
759 SpawnMessage("*** RESUME ***");
763 else if (event.key.keysym.sym == SDLK_LALT)
764 openAppleDown = true;
765 else if (event.key.keysym.sym == SDLK_RALT)
766 closedAppleDown = true;
767 // Toggle the disassembly process
768 else if (event.key.keysym.sym == SDLK_F11)
771 /*else if (event.key.keysym.sym == SDLK_F9)
773 floppyDrive.CreateBlankImage(0);
774 // SpawnMessage("Image cleared...");
776 /*else if (event.key.keysym.sym == SDLK_F10)
778 floppyDrive.SwapImages();
779 // SpawnMessage("Image swapped...");
782 else if (event.key.keysym.sym == SDLK_F2)
784 else if (event.key.keysym.sym == SDLK_F3)
786 else if (event.key.keysym.sym == SDLK_F5)
789 char volStr[19] = "[****************]";
791 for(int i=GetVolume(); i<16; i++)
794 SpawnMessage("Volume: %s", volStr);
796 else if (event.key.keysym.sym == SDLK_F6)
799 char volStr[19] = "[****************]";
801 for(int i=GetVolume(); i<16; i++)
804 SpawnMessage("Volume: %s", volStr);
806 else if (event.key.keysym.sym == SDLK_F12)
808 if (!fullscreenDebounce)
811 fullscreenDebounce = true;
814 else if (event.key.keysym.sym == SDLK_CAPSLOCK)
816 if (!capsLockDebounce)
818 capsLock = !capsLock;
819 capsLockDebounce = true;
826 if (event.key.keysym.sym == SDLK_F12)
827 fullscreenDebounce = false;
828 else if (event.key.keysym.sym == SDLK_CAPSLOCK)
829 capsLockDebounce = false;
830 // Paddle buttons 0 & 1
831 else if (event.key.keysym.sym == SDLK_LALT)
832 openAppleDown = false;
833 else if (event.key.keysym.sym == SDLK_RALT)
834 closedAppleDown = false;
835 else if ((event.key.keysym.mod & KMOD_CTRL)
836 && (event.key.keysym.sym == SDLK_HOME))
837 resetKeyDown = false;
840 // Handle key buffering 'key up' event (2 key rollover)
841 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
845 else if (keyDownCount == 2)
847 if (event.key.keysym.sym == keysHeld[0])
850 keysHeld[0] = keysHeld[1];
851 keysHeldAppleCode[0] = keysHeldAppleCode[1];
853 else if (event.key.keysym.sym == keysHeld[1])
862 case SDL_MOUSEBUTTONDOWN:
863 GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
866 case SDL_MOUSEBUTTONUP:
867 GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
870 case SDL_MOUSEMOTION:
871 GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
874 case SDL_WINDOWEVENT:
875 if (event.window.event == SDL_WINDOWEVENT_LEAVE)
876 GUI::MouseMove(0, 0, 0);
885 // Stuff the Apple keyboard buffer, if any keys are pending
886 // N.B.: May have to simulate the key repeat delay too
887 if (keyDownCount > 0)
889 lastKeyPressed = keysHeldAppleCode[0];
893 // Handle power request from the GUI
894 if (powerStateChangeRequested)
896 if (GUI::powerOnState)
899 // Unlock the CPU thread...
900 SDL_SemPost(mainSem);
905 // Should lock until CPU thread is waiting...
906 SDL_SemWait(mainSem);
910 powerStateChangeRequested = false;
913 // Render the Apple screen + GUI overlay
914 RenderAppleScreen(sdlRenderer);
915 GUI::Render(sdlRenderer);
916 SDL_RenderPresent(sdlRenderer);
917 SetCallbackTime(FrameCallback, 16666.66666667);
919 #ifdef CPU_CLOCK_CHECKING
920 //We know it's stopped, so we can get away with this...
924 uint64_t clock = GetCurrentV65C02Clock();
925 //totalCPU += (uint32_t)(clock - lastClock);
927 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
934 // This is the problem: If you set the interval to 16, it runs faster than
935 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
936 // have it do 16 for one frame, then 17 for two others. Then it should average
937 // out to 1/60s per frame every 3 frames. [And now it does!]
938 frameCount = (frameCount + 1) % 3;
939 uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
941 // Wait for next frame...
942 while (SDL_GetTicks() - startTicks < waitFrameTime)
945 startTicks = SDL_GetTicks();
947 uint64_t cpuCycles = GetCurrentV65C02Clock();
948 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
949 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
950 lastCPUCycles = cpuCycles
953 //let's wait, then signal...
954 //works longer, but then still falls behind... [FIXED, see above]
955 #ifdef THREADED_65C02
957 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
962 static void BlinkTimer(void)
964 // Set up blinking at 1/4 sec intervals
966 SetCallbackTime(BlinkTimer, 250000);
971 Next problem is this: How to have events occur and synchronize with the rest
974 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
975 remainder CPU cycles over...)
977 One way would be to use a fractional accumulator, then subtract 1 every
978 time it overflows. Like so:
984 Execute6808(&soundCPU, time);
985 overflow += 0.289115646;