2 // Apple 2 SDL Portable Apple Emulator
5 // © 2017 Underground Software
7 // Parts loosely inspired by AppleWin by Tom Charlesworth which was based on
8 // AppleWin by Oliver Schmidt which was based on AppleWin by Michael O'Brien.
9 // :-) Some parts (mainly TV rendering) are derived from ApplePC. Too bad it
10 // was closed source--it could have been *the* premier Apple II emulator out
13 // JLH = James Hammons <jlhamm@acm.org>
16 // --- ---------- -----------------------------------------------------------
17 // JLH 11/12/2005 Initial port to SDL
18 // JLH 11/18/2005 Wired up graphic soft switches
19 // JLH 12/02/2005 Setup timer subsystem for more accurate time keeping
20 // JLH 12/12/2005 Added preliminary state saving support
21 // JLH 09/24/2013 Added //e support
26 // - Port to SDL [DONE]
28 // - Weed out unneeded functions [DONE]
30 // - 128K IIe related stuff [DONE]
31 // - State loading/saving
35 // - Having a directory in the ${disks} directory causes a segfault in floppy
57 #include "gui/diskselector.h"
59 // Debug and misc. defines
61 #define THREADED_65C02
62 #define CPU_THREAD_OVERFLOW_COMPENSATION
64 //#define CPU_CLOCK_CHECKING
65 //#define THREAD_DEBUGGING
66 #define SOFT_SWITCH_DEBUGGING
70 uint8_t ram[0x10000], rom[0x10000]; // RAM & ROM spaces
71 uint8_t ram2[0x10000]; // Auxillary RAM
72 V65C02REGS mainCPU; // v65C02 execution context
73 uint8_t appleType = APPLE_TYPE_IIE;
74 FloppyDrive floppyDrive;
75 bool powerStateChangeRequested = false;
76 uint64_t frameCycleStart;
80 uint8_t lastKeyPressed = 0;
82 bool openAppleDown = false;
83 bool closedAppleDown = false;
84 bool store80Mode = false;
86 bool slotCXROM = false;
87 bool slotC3ROM = false;
93 // Language card state (ROM read, no write)
94 uint8_t lcState = 0x02;
96 static bool running = true; // Machine running state flag...
97 static uint32_t startTicks;
98 static bool pauseMode = false;
99 static bool fullscreenDebounce = false;
100 static bool capsLock = false;
101 static bool capsLockDebounce = false;
102 static bool resetKeyDown = false;
103 static int8_t hideMouseTimeout = 60;
105 // Vars to handle the //e's 2-key rollover
106 static SDL_Keycode keysHeld[2];
107 static uint8_t keysHeldAppleCode[2];
108 static uint8_t keyDownCount = 0;
109 static uint8_t keyDelay = 0;
113 static void SaveApple2State(const char * filename);
114 static bool LoadApple2State(const char * filename);
115 static void ResetApple2State(void);
117 // Local timer callback functions
119 static void FrameCallback(void);
120 static void BlinkTimer(void);
122 #ifdef THREADED_65C02
123 // Test of threaded execution of 6502
124 static SDL_Thread * cpuThread = NULL;
125 static SDL_cond * cpuCond = NULL;
126 static SDL_sem * mainSem = NULL;
127 static bool cpuFinished = false;
129 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
130 // This is a lie. At the end of each 65 cycle line, there is an elongated
131 // cycle (the so-called 'long' cycle) that throws the calcs out of whack.
133 // Let's try a thread...
135 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
138 int CPUThreadFunc(void * data)
140 // Mutex must be locked for conditional to work...
141 // Also, must be created in the thread that uses it...
142 SDL_mutex * cpuMutex = SDL_CreateMutex();
144 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
145 float overflow = 0.0;
150 // decrement mainSem...
151 #ifdef THREAD_DEBUGGING
152 WriteLog("CPU: SDL_SemWait(mainSem);\n");
154 SDL_SemWait(mainSem);
156 // There are exactly 800 slices of 21.333 cycles per frame, so it works
158 // [Actually, seems it's 786 slices of 21.666 cycles per frame]
160 // Set our frame cycle counter to the correct # of cycles at the start
162 frameCycleStart = mainCPU.clock - mainCPU.overflow;
163 #ifdef THREAD_DEBUGGING
164 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
166 // for(int i=0; i<800; i++)
167 for(int i=0; i<786; i++)
169 uint32_t cycles = 21;
170 // overflow += 0.333333334;
171 overflow += 0.666666667;
179 // If the CTRL+Reset key combo is being held, make sure the RESET
180 // line stays asserted:
182 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
184 Execute65C02(&mainCPU, cycles);
185 WriteSampleToBuffer();
187 // According to "Understanding The Apple IIe", VBL asserted after
188 // the last byte of the screen is read and let go on the first read
189 // of the first byte of the screen. We now know that the screen
190 // starts on line #6 and ends on line #197 (of the vertical
191 // counter--actual VBLANK happens on lines 230 thru 233).
192 vbl = ((i > 17) && (i < 592) ? true : false);
195 Other timings from UTA2E:
196 Power-up reset 32.6 msec / 512 horizontal scans
197 Flash cycle 1.87 Hz / Vertical freq./32
198 Delay before auto repeat 534-801 msec / 32-48 vertical scans
199 Auto repeat frequency 15 Hz / Vertical freq./4
200 Vertical frequency 59.94 Hz
201 Horizontal frequency 15,734 Hz
202 1 NTSC frame = 17030 cycles
204 70 blank lines for top margin, 192 lines for screen, (35 & 35?)
205 VA-C,V0-5 is upcounter starting at 011111010 ($FA) to 111111111 ($1FF)
206 Horizontal counter is upcounter resets to 0000000, then jumps to 1000000 &
207 counts up to 1111111 (bit 7 is Horizontal Preset Enable, which resets the counter when it goes low, the rest are H0-5)
209 pg. 3-24 says one cycle before VBL the counters will be at
210 010111111/1111111 (that doesn't look right to me...)
225 SUMS are calculated like so:
230 ------------------------------
231 SUM-A6 SUM-A5 SUM-A4 SUM-A3
233 In TEXT mode, A10 == (80STORE' * PAGE2)', A11 == 80STORE' * PAGE2
236 In HIRES mode, A13 == (PAGE2 * 80STORE')', A14 == PAGE2 * 80STORE'
237 A10 == VA, A11 == VB, A12 == VC, A15 == 0
239 N.B.: VA-C are lower bits than V5-0
241 HC, from 00, 0 to 23 is the HBL interval, with horizontal retrace occuring between cycles 8 and 11.
242 VC, from line 0-5 and 198-261 is the VBL interval, with vertical retrace occuring between lines 230-233
246 #ifdef THREAD_DEBUGGING
247 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
249 SDL_mutexP(cpuMutex);
250 // increment mainSem...
251 #ifdef THREAD_DEBUGGING
252 WriteLog("CPU: SDL_SemPost(mainSem);\n");
254 SDL_SemPost(mainSem);
255 #ifdef THREAD_DEBUGGING
256 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
258 SDL_CondWait(cpuCond, cpuMutex);
260 #ifdef THREAD_DEBUGGING
261 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
263 SDL_mutexV(cpuMutex);
265 while (!cpuFinished);
267 SDL_DestroyMutex(cpuMutex);
275 // Request a change in the power state of the emulated Apple
277 void SetPowerState(void)
279 powerStateChangeRequested = true;
284 // Load a file into RAM/ROM image space
286 bool LoadImg(char * filename, uint8_t * ram, int size)
288 FILE * fp = fopen(filename, "rb");
293 fread(ram, 1, size, fp);
300 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.0";
301 static void SaveApple2State(const char * filename)
303 WriteLog("Main: Saving Apple2 state...\n");
304 FILE * file = fopen(filename, "wb");
308 WriteLog("Could not open file \"%s\" for writing!\n", filename);
313 fwrite(stateHeader, 1, 18, file);
315 // Write out CPU state
316 fwrite(&mainCPU, 1, sizeof(mainCPU), file);
318 // Write out main memory
319 fwrite(ram, 1, 0x10000, file);
320 fwrite(ram2, 1, 0x10000, file);
322 // Write out state variables
323 fputc((uint8_t)keyDown, file);
324 fputc((uint8_t)openAppleDown, file);
325 fputc((uint8_t)closedAppleDown, file);
326 fputc((uint8_t)store80Mode, file);
327 fputc((uint8_t)vbl, file);
328 fputc((uint8_t)slotCXROM, file);
329 fputc((uint8_t)slotC3ROM, file);
330 fputc((uint8_t)ramrd, file);
331 fputc((uint8_t)ramwrt, file);
332 fputc((uint8_t)altzp, file);
333 fputc((uint8_t)ioudis, file);
334 fputc((uint8_t)dhires, file);
335 fputc((uint8_t)flash, file);
336 fputc((uint8_t)textMode, file);
337 fputc((uint8_t)mixedMode, file);
338 fputc((uint8_t)displayPage2, file);
339 fputc((uint8_t)hiRes, file);
340 fputc((uint8_t)alternateCharset, file);
341 fputc((uint8_t)col80Mode, file);
342 fputc(lcState, file);
344 // Write out floppy state
345 floppyDrive.SaveState(file);
350 static bool LoadApple2State(const char * filename)
352 WriteLog("Main: Loading Apple2 state...\n");
353 FILE * file = fopen(filename, "rb");
357 WriteLog("Could not open file \"%s\" for reading!\n", filename);
362 fread(buffer, 1, 18, file);
365 if (memcmp(buffer, stateHeader, 18) != 0)
368 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
373 fread(&mainCPU, 1, sizeof(mainCPU), file);
376 fread(ram, 1, 0x10000, file);
377 fread(ram2, 1, 0x10000, file);
379 // Read in state variables
380 keyDown = (bool)fgetc(file);
381 openAppleDown = (bool)fgetc(file);
382 closedAppleDown = (bool)fgetc(file);
383 store80Mode = (bool)fgetc(file);
384 vbl = (bool)fgetc(file);
385 slotCXROM = (bool)fgetc(file);
386 slotC3ROM = (bool)fgetc(file);
387 ramrd = (bool)fgetc(file);
388 ramwrt = (bool)fgetc(file);
389 altzp = (bool)fgetc(file);
390 ioudis = (bool)fgetc(file);
391 dhires = (bool)fgetc(file);
392 flash = (bool)fgetc(file);
393 textMode = (bool)fgetc(file);
394 mixedMode = (bool)fgetc(file);
395 displayPage2 = (bool)fgetc(file);
396 hiRes = (bool)fgetc(file);
397 alternateCharset = (bool)fgetc(file);
398 col80Mode = (bool)fgetc(file);
399 lcState = fgetc(file);
401 // Read in floppy state
402 floppyDrive.LoadState(file);
406 // Make sure things are in a sane state before execution :-P
407 mainCPU.RdMem = AppleReadMem;
408 mainCPU.WrMem = AppleWriteMem;
415 static void ResetApple2State(void)
418 openAppleDown = false;
419 closedAppleDown = false;
432 // Without this, you can wedge the system :-/
433 memset(ram, 0, 0x10000);
434 memset(ram2, 0, 0x10000);
435 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
439 #ifdef CPU_CLOCK_CHECKING
441 uint32_t totalCPU = 0;
442 uint64_t lastClock = 0;
447 int main(int /*argc*/, char * /*argv*/[])
449 InitLog("./apple2.log");
451 srand(time(NULL)); // Initialize RNG
454 // Make some timing/address tables
456 for(uint32_t line=0; line<262; line++)
458 WriteLog("LINE %03i: ", line);
460 for(uint32_t col=0; col<65; col++)
462 // Convert these to H/V counters
463 uint32_t hcount = col - 1;
465 // HC sees zero twice:
466 if (hcount == 0xFFFFFFFF)
469 uint32_t vcount = line + 0xFA;
471 // Now do the address calculations
472 uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
473 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
474 uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
476 // Add in particulars for the gfx mode we're in...
479 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
480 | (!store80Mode && displayPage2 ? 0x800 : 0);
483 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
484 | (!store80Mode && displayPage2 ? 0x4000 : 0)
485 | ((vcount & 0x07) << 10);
487 WriteLog("$%04X ", address);
496 memset(ram, 0, 0x10000);
497 memset(rom, 0, 0x10000);
498 memset(ram2, 0, 0x10000);
504 // Set up V65C02 execution context
505 memset(&mainCPU, 0, sizeof(V65C02REGS));
506 mainCPU.RdMem = AppleReadMem;
507 mainCPU.WrMem = AppleWriteMem;
508 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
510 if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
512 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
516 WriteLog("About to initialize video...\n");
520 printf("Could not init screen: aborting!\n");
524 GUI::Init(sdlRenderer);
525 WriteLog("About to initialize audio...\n");
528 if (settings.autoStateSaving)
530 // Load last state from file...
531 if (!LoadApple2State(settings.autoStatePath))
532 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
536 InitializeEventList();
537 // Set frame to fire at 1/60 s interval
538 SetCallbackTime(FrameCallback, 16666.66666667);
539 // Set up blinking at 1/4 s intervals
540 SetCallbackTime(BlinkTimer, 250000);
541 startTicks = SDL_GetTicks();
543 #ifdef THREADED_65C02
544 // Kick off the CPU...
545 cpuCond = SDL_CreateCond();
546 mainSem = SDL_CreateSemaphore(1);
547 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
548 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
549 // SDL_sem * mainMutex = SDL_CreateMutex();
552 WriteLog("Entering main loop...\n");
556 double timeToNextEvent = GetTimeToNextEvent();
557 #ifndef THREADED_65C02
558 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
560 #ifdef CPU_CLOCK_CHECKING
561 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
567 #ifdef THREADED_65C02
568 WriteLog("Main: cpuFinished = true;\n");
571 WriteLog("Main: SDL_SemWait(mainSem);\n");
572 // Only do this if NOT in power off/emulation paused mode!
574 // Should lock until CPU thread is waiting...
575 SDL_SemWait(mainSem);
578 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
579 SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
580 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
581 SDL_WaitThread(cpuThread, NULL);
582 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
583 SDL_DestroyCond(cpuCond);
584 SDL_DestroySemaphore(mainSem);
586 // Autosave state here, if requested...
587 if (settings.autoStateSaving)
588 SaveApple2State(settings.autoStatePath);
590 floppyDrive.SaveImage(0);
591 floppyDrive.SaveImage(1);
603 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
604 // +CTRL+SHIFT (3). Order of keys is:
605 // Delete, left, tab, down, up, return, right, escape
606 // Space, single quote, comma, minus, period, slash
608 // Semicolon, equals, left bracket, backslash, right bracket, backquote
609 // Letters a-z (lowercase)
611 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
612 // keyboards, so this table should suffice for the shifted keys just fine.
614 uint8_t apple2e_keycode[4][56] = {
616 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
617 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
618 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
619 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
620 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
621 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
622 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
625 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
626 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
627 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
628 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
629 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
630 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
631 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
634 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
635 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
636 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
637 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
638 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
639 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
640 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
642 { // With CTRL+Shift held
643 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
644 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
645 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
646 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
647 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
648 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
649 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
653 static uint32_t frameCount = 0;
654 static void FrameCallback(void)
659 while (SDL_PollEvent(&event))
664 // We do our own repeat handling thank you very much! :-)
665 if (event.key.repeat != 0)
668 // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
670 if ((event.key.keysym.mod & KMOD_CTRL)
671 && (event.key.keysym.mod & KMOD_SHIFT)
672 && (event.key.keysym.sym == SDLK_q))
675 // We return here, because we don't want to pick up any
676 // spurious keypresses with our exit sequence.
680 // CTRL+RESET key emulation (mapped to CTRL+HOME)
681 if ((event.key.keysym.mod & KMOD_CTRL)
682 && (event.key.keysym.sym == SDLK_HOME))
684 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
689 // There has GOT to be a better way off mapping SDLKs to our
690 // keyindex. But for now, this should suffice.
693 switch (event.key.keysym.sym)
695 case SDLK_BACKSPACE: keyIndex = 0; break;
696 case SDLK_LEFT: keyIndex = 1; break;
697 case SDLK_TAB: keyIndex = 2; break;
698 case SDLK_DOWN: keyIndex = 3; break;
699 case SDLK_UP: keyIndex = 4; break;
700 case SDLK_RETURN: keyIndex = 5; break;
701 case SDLK_RIGHT: keyIndex = 6; break;
702 case SDLK_ESCAPE: keyIndex = 7; break;
703 case SDLK_SPACE: keyIndex = 8; break;
704 case SDLK_QUOTE: keyIndex = 9; break;
705 case SDLK_COMMA: keyIndex = 10; break;
706 case SDLK_MINUS: keyIndex = 11; break;
707 case SDLK_PERIOD: keyIndex = 12; break;
708 case SDLK_SLASH: keyIndex = 13; break;
709 case SDLK_0: keyIndex = 14; break;
710 case SDLK_1: keyIndex = 15; break;
711 case SDLK_2: keyIndex = 16; break;
712 case SDLK_3: keyIndex = 17; break;
713 case SDLK_4: keyIndex = 18; break;
714 case SDLK_5: keyIndex = 19; break;
715 case SDLK_6: keyIndex = 20; break;
716 case SDLK_7: keyIndex = 21; break;
717 case SDLK_8: keyIndex = 22; break;
718 case SDLK_9: keyIndex = 23; break;
719 case SDLK_SEMICOLON: keyIndex = 24; break;
720 case SDLK_EQUALS: keyIndex = 25; break;
721 case SDLK_LEFTBRACKET: keyIndex = 26; break;
722 case SDLK_BACKSLASH: keyIndex = 27; break;
723 case SDLK_RIGHTBRACKET: keyIndex = 28; break;
724 case SDLK_BACKQUOTE: keyIndex = 29; break;
725 case SDLK_a: keyIndex = 30; break;
726 case SDLK_b: keyIndex = 31; break;
727 case SDLK_c: keyIndex = 32; break;
728 case SDLK_d: keyIndex = 33; break;
729 case SDLK_e: keyIndex = 34; break;
730 case SDLK_f: keyIndex = 35; break;
731 case SDLK_g: keyIndex = 36; break;
732 case SDLK_h: keyIndex = 37; break;
733 case SDLK_i: keyIndex = 38; break;
734 case SDLK_j: keyIndex = 39; break;
735 case SDLK_k: keyIndex = 40; break;
736 case SDLK_l: keyIndex = 41; break;
737 case SDLK_m: keyIndex = 42; break;
738 case SDLK_n: keyIndex = 43; break;
739 case SDLK_o: keyIndex = 44; break;
740 case SDLK_p: keyIndex = 45; break;
741 case SDLK_q: keyIndex = 46; break;
742 case SDLK_r: keyIndex = 47; break;
743 case SDLK_s: keyIndex = 48; break;
744 case SDLK_t: keyIndex = 49; break;
745 case SDLK_u: keyIndex = 50; break;
746 case SDLK_v: keyIndex = 51; break;
747 case SDLK_w: keyIndex = 52; break;
748 case SDLK_x: keyIndex = 53; break;
749 case SDLK_y: keyIndex = 54; break;
750 case SDLK_z: keyIndex = 55; break;
753 // Stuff the key in if we have a valid one...
754 if (keyIndex != 0xFF)
756 // Handle Shift, CTRL, & Shift+CTRL combos
759 if (event.key.keysym.mod & KMOD_CTRL)
762 if (event.key.keysym.mod & KMOD_SHIFT)
765 lastKeyPressed = apple2e_keycode[table][keyIndex];
770 && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A))
771 lastKeyPressed -= 0x20;
773 // Handle key repeat if the key hasn't been held
774 // if (keyDelay == 0)
779 // Buffer the key held. Note that the last key is always
780 // stuffed into keysHeld[0].
781 if (keyDownCount >= 2)
783 keysHeld[1] = keysHeld[0];
784 keysHeldAppleCode[1] = keysHeldAppleCode[0];
786 if (keyDownCount > 2)
790 keysHeld[0] = event.key.keysym.sym;
791 keysHeldAppleCode[0] = lastKeyPressed;
795 if (event.key.keysym.sym == SDLK_PAUSE)
797 pauseMode = !pauseMode;
802 SpawnMessage("*** PAUSED ***");
807 SpawnMessage("*** RESUME ***");
811 else if (event.key.keysym.sym == SDLK_LALT)
812 openAppleDown = true;
813 else if (event.key.keysym.sym == SDLK_RALT)
814 closedAppleDown = true;
815 // Toggle the disassembly process
816 else if (event.key.keysym.sym == SDLK_F11)
819 /*else if (event.key.keysym.sym == SDLK_F9)
821 floppyDrive.CreateBlankImage(0);
822 // SpawnMessage("Image cleared...");
824 /*else if (event.key.keysym.sym == SDLK_F10)
826 floppyDrive.SwapImages();
827 // SpawnMessage("Image swapped...");
830 else if (event.key.keysym.sym == SDLK_F2)
832 else if (event.key.keysym.sym == SDLK_F3)
834 else if (event.key.keysym.sym == SDLK_F5)
837 char volStr[19] = "[****************]";
839 for(int i=GetVolume(); i<16; i++)
842 SpawnMessage("Volume: %s", volStr);
844 else if (event.key.keysym.sym == SDLK_F6)
847 char volStr[19] = "[****************]";
849 for(int i=GetVolume(); i<16; i++)
852 SpawnMessage("Volume: %s", volStr);
854 else if (event.key.keysym.sym == SDLK_F12)
856 if (!fullscreenDebounce)
859 fullscreenDebounce = true;
862 else if (event.key.keysym.sym == SDLK_CAPSLOCK)
864 if (!capsLockDebounce)
866 capsLock = !capsLock;
867 capsLockDebounce = true;
874 if (event.key.keysym.sym == SDLK_F12)
875 fullscreenDebounce = false;
876 else if (event.key.keysym.sym == SDLK_CAPSLOCK)
877 capsLockDebounce = false;
878 // Paddle buttons 0 & 1
879 else if (event.key.keysym.sym == SDLK_LALT)
880 openAppleDown = false;
881 else if (event.key.keysym.sym == SDLK_RALT)
882 closedAppleDown = false;
883 else if ((event.key.keysym.mod & KMOD_CTRL)
884 && (event.key.keysym.sym == SDLK_HOME))
885 resetKeyDown = false;
888 // Handle key buffering 'key up' event (2 key rollover)
889 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
892 keyDelay = 0; // Reset key delay
894 else if (keyDownCount == 2)
896 if (event.key.keysym.sym == keysHeld[0])
899 keysHeld[0] = keysHeld[1];
900 keysHeldAppleCode[0] = keysHeldAppleCode[1];
902 else if (event.key.keysym.sym == keysHeld[1])
911 case SDL_MOUSEBUTTONDOWN:
912 GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
915 case SDL_MOUSEBUTTONUP:
916 GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
919 case SDL_MOUSEMOTION:
920 GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
922 // Handle mouse showing when the mouse is hidden...
923 if (hideMouseTimeout == -1)
926 hideMouseTimeout = 60;
929 case SDL_WINDOWEVENT:
930 if (event.window.event == SDL_WINDOWEVENT_LEAVE)
931 GUI::MouseMove(0, 0, 0);
940 // Hide the mouse if it's been 1s since the last time it was moved
941 // N.B.: Should disable mouse hiding if it's over the GUI...
942 if ((hideMouseTimeout > 0) && !(GUI::sidebarState == SBS_SHOWN || DiskSelector::showWindow == true))
944 else if (hideMouseTimeout == 0)
950 // Stuff the Apple keyboard buffer, if any keys are pending
951 // N.B.: May have to simulate the key repeat delay too [yup, sure do]
952 // According to "Understanding the Apple IIe", the initial delay is
953 // between 32 & 48 jiffies and the repeat is every 4 jiffies.
954 if (keyDownCount > 0)
961 lastKeyPressed = keysHeldAppleCode[0];
966 // Handle power request from the GUI
967 if (powerStateChangeRequested)
969 if (GUI::powerOnState)
972 // Unlock the CPU thread...
973 SDL_SemPost(mainSem);
978 // Should lock until CPU thread is waiting...
979 SDL_SemWait(mainSem);
983 powerStateChangeRequested = false;
986 // Render the Apple screen + GUI overlay
987 RenderAppleScreen(sdlRenderer);
988 GUI::Render(sdlRenderer);
989 SDL_RenderPresent(sdlRenderer);
990 SetCallbackTime(FrameCallback, 16666.66666667);
992 #ifdef CPU_CLOCK_CHECKING
993 //We know it's stopped, so we can get away with this...
997 uint64_t clock = GetCurrentV65C02Clock();
998 //totalCPU += (uint32_t)(clock - lastClock);
1000 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1007 // This is the problem: If you set the interval to 16, it runs faster than
1008 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1009 // have it do 16 for one frame, then 17 for two others. Then it should average
1010 // out to 1/60s per frame every 3 frames. [And now it does!]
1011 frameCount = (frameCount + 1) % 3;
1012 uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1014 // Wait for next frame...
1015 while (SDL_GetTicks() - startTicks < waitFrameTime)
1018 startTicks = SDL_GetTicks();
1020 uint64_t cpuCycles = GetCurrentV65C02Clock();
1021 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1022 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1023 lastCPUCycles = cpuCycles
1026 //let's wait, then signal...
1027 //works longer, but then still falls behind... [FIXED, see above]
1028 #ifdef THREADED_65C02
1030 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1035 static void BlinkTimer(void)
1037 // Set up blinking at 1/4 sec intervals
1039 SetCallbackTime(BlinkTimer, 250000);
1044 Next problem is this: How to have events occur and synchronize with the rest
1047 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1048 remainder CPU cycles over...)
1050 One way would be to use a fractional accumulator, then subtract 1 every
1051 time it overflows. Like so:
1053 double overflow = 0;
1057 Execute6808(&soundCPU, time);
1058 overflow += 0.289115646;