2 // Apple 2 SDL Portable Apple Emulator
5 // © 2018 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]
27 // - Weed out unneeded functions [DONE]
29 // - 128K IIe related stuff [DONE]
30 // - State loading/saving [DONE]
35 // - Having a directory in the ${disks} directory causes a segfault in floppy
48 #include "firmware/apple2-fw.h"
49 #include "firmware/apple2e-enh.h"
50 #include "firmware/firmware.h"
51 #include "floppydrive.h"
52 #include "harddrive.h"
55 #include "mockingboard.h"
60 #include "gui/diskselector.h"
61 #include "gui/config.h"
64 // Debug and misc. defines
66 #define THREADED_65C02
67 #define CPU_THREAD_OVERFLOW_COMPENSATION
69 //#define CPU_CLOCK_CHECKING
70 //#define THREAD_DEBUGGING
71 #define SOFT_SWITCH_DEBUGGING
75 uint8_t ram[0x10000], rom[0x10000]; // RAM & ROM spaces
76 uint8_t ram2[0x10000]; // Auxillary RAM
77 V65C02REGS mainCPU; // v65C02 execution context
78 uint8_t appleType = APPLE_TYPE_IIE;
79 bool powerStateChangeRequested = false;
80 uint64_t frameCycleStart;
82 uint64_t frameTicks = 0;
83 uint64_t frameTime[60];
84 uint32_t frameTimePtr = 0;
88 uint8_t lastKeyPressed = 0;
90 bool openAppleDown = false;
91 bool closedAppleDown = false;
92 bool store80Mode = false;
94 bool intCXROM = false;
95 bool slotC3ROM = false;
96 bool intC8ROM = false;
102 // Language card state (ROM read, no write)
103 uint8_t lcState = 0x02;
104 uint8_t blinkTimer = 0;
106 static bool running = true; // Machine running state flag...
107 static uint64_t startTicks;
108 static bool pauseMode = false;
109 static bool fullscreenDebounce = false;
110 static bool resetKeyDown = false;
111 static int8_t hideMouseTimeout = 60;
113 // Vars to handle the //e's 2-key rollover
114 static SDL_Keycode keysHeld[2];
115 static uint8_t keysHeldAppleCode[2];
116 static uint8_t keyDownCount = 0;
117 static uint8_t keyDelay = 0;
121 static void SaveApple2State(const char * filename);
122 static bool LoadApple2State(const char * filename);
123 static void ResetApple2State(void);
124 static void AppleTimer(uint16_t);
126 // Local timer callback functions
128 static void FrameCallback(void);
129 static void BlinkTimer(void);
131 #ifdef THREADED_65C02
132 // Test of threaded execution of 6502
133 static SDL_Thread * cpuThread = NULL;
134 static SDL_cond * cpuCond = NULL;
135 static SDL_sem * mainSem = NULL;
136 static bool cpuFinished = false;
138 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
139 // This is a lie. At the end of each 65 cycle line, there is an elongated
140 // cycle (the so-called 'long' cycle) that throws the calcs out of whack.
141 // So actually, it's supposed to be 1,020,484.32 Hz
143 // Let's try a thread...
145 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
148 static uint32_t sampleCount;
149 static uint64_t sampleClock, lastSampleClock;
150 int CPUThreadFunc(void * data)
152 // Mutex must be locked for conditional to work...
153 // Also, must be created in the thread that uses it...
154 SDL_mutex * cpuMutex = SDL_CreateMutex();
156 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
157 // float overflow = 0.0;
162 uint64_t cpuFrameTickStart = SDL_GetPerformanceCounter();
163 uint64_t oldClock = mainCPU.clock;
165 sampleClock = lastSampleClock = mainCPU.clock;
166 // decrement mainSem...
167 #ifdef THREAD_DEBUGGING
168 WriteLog("CPU: SDL_SemWait(mainSem);\n");
170 SDL_SemWait(mainSem);
172 // There are exactly 800 slices of 21.333 cycles per frame, so it works
174 // [Actually, seems it's 786 slices of 21.666 cycles per frame]
176 // Set our frame cycle counter to the correct # of cycles at the start
178 frameCycleStart = mainCPU.clock - mainCPU.overflow;
179 #ifdef THREAD_DEBUGGING
180 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
182 for(int i=0; i<262; i++)
184 // If the CTRL+Reset key combo is being held, make sure the RESET
185 // line stays asserted:
187 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
189 Execute65C02(&mainCPU, 65);
191 // According to "Understanding The Apple IIe", VBL asserted after
192 // the last byte of the screen is read and let go on the first read
193 // of the first byte of the screen. We now know that the screen
194 // starts on line #6 and ends on line #197 (of the vertical
195 // counter--actual VBLANK proper happens on lines 230 thru 233).
196 vbl = ((i >= 6) && (i <= 197) ? true : false);
199 //WriteLog("*** Frame ran for %d cycles (%.3lf µs, %d samples).\n", mainCPU.clock - oldClock, ((double)(SDL_GetPerformanceCounter() - cpuFrameTickStart) * 1000000.0) / (double)SDL_GetPerformanceFrequency(), sampleCount);
200 // frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
202 Other timings from UTA2E:
203 Power-up reset 32.6 msec / 512 horizontal scans
204 Flash cycle 1.87 Hz / Vertical freq./32
205 Delay before auto repeat 534-801 msec / 32-48 vertical scans
206 Auto repeat frequency 15 Hz / Vertical freq./4
207 Vertical frequency 59.94 Hz (actually, 59.92 Hz [59.92274339401])
208 Horizontal frequency 15,734 Hz (actually, 15700 Hz)
209 1 NTSC frame = 17030 cycles (N.B.: this works out to 1021800 cycles per sec.)
210 NTSC clock frequency ("composite" freq.) is 1.02048432 MHz, which is 14.31818 x (65 / (65 x 14 + 2)) MHz.
212 70 blank lines for top margin, 192 lines for screen, (35 & 35?)
213 VA-C,V0-5 is upcounter starting at 011111010 ($FA) to 111111111 ($1FF)
214 Horizontal counter is upcounter resets to 0000000, then jumps to 1000000 &
215 counts up to 1111111 (bit 7 is Horizontal Preset Enable, which resets the counter when it goes low, the rest are H0-5)
217 pg. 3-24 says one cycle before VBL the counters will be at
218 010111111/1111111 (that doesn't look right to me...)
233 SUMS are calculated like so:
238 ------------------------------
239 SUM-A6 SUM-A5 SUM-A4 SUM-A3
241 In TEXT mode, A10 == (80STORE' * PAGE2)', A11 == 80STORE' * PAGE2
244 In HIRES mode, A13 == (PAGE2 * 80STORE')', A14 == PAGE2 * 80STORE'
245 A10 == VA, A11 == VB, A12 == VC, A15 == 0
247 N.B.: VA-C are lower bits than V5-0
249 HC, from 00, 0 to 23 is the HBL interval, with horizontal retrace occuring between cycles 8 and 11.
250 VC, from line 0-5 and 198-261 is the VBL interval, with vertical retrace occuring between lines 230-233
254 #ifdef THREAD_DEBUGGING
255 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
257 SDL_mutexP(cpuMutex);
258 // increment mainSem...
259 #ifdef THREAD_DEBUGGING
260 WriteLog("CPU: SDL_SemPost(mainSem);\n");
262 SDL_SemPost(mainSem);
263 #ifdef THREAD_DEBUGGING
264 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
266 SDL_CondWait(cpuCond, cpuMutex);
268 #ifdef THREAD_DEBUGGING
269 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
271 SDL_mutexV(cpuMutex);
273 while (!cpuFinished);
275 SDL_DestroyMutex(cpuMutex);
283 // Request a change in the power state of the emulated Apple
285 void SetPowerState(void)
287 powerStateChangeRequested = true;
292 // Load a file into RAM/ROM image space
294 bool LoadImg(char * filename, uint8_t * ram, int size)
296 FILE * fp = fopen(filename, "rb");
301 fread(ram, 1, size, fp);
308 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.2";
309 static void SaveApple2State(const char * filename)
311 WriteLog("Main: Saving Apple2 state...\n");
312 FILE * file = fopen(filename, "wb");
316 WriteLog("Could not open file \"%s\" for writing!\n", filename);
321 fwrite(stateHeader, 1, 18, file);
323 // Write out CPU state
324 fwrite(&mainCPU, 1, sizeof(mainCPU), file);
326 // Write out main memory
327 fwrite(ram, 1, 0x10000, file);
328 fwrite(ram2, 1, 0x10000, file);
330 // Write out state variables
331 fputc((uint8_t)keyDown, file);
332 fputc((uint8_t)openAppleDown, file);
333 fputc((uint8_t)closedAppleDown, file);
334 fputc((uint8_t)store80Mode, file);
335 fputc((uint8_t)vbl, file);
336 fputc((uint8_t)intCXROM, file);
337 fputc((uint8_t)slotC3ROM, file);
338 fputc((uint8_t)intC8ROM, file);
339 fputc((uint8_t)ramrd, file);
340 fputc((uint8_t)ramwrt, file);
341 fputc((uint8_t)altzp, file);
342 fputc((uint8_t)ioudis, file);
343 fputc((uint8_t)dhires, file);
344 fputc((uint8_t)flash, file);
345 fputc((uint8_t)textMode, file);
346 fputc((uint8_t)mixedMode, file);
347 fputc((uint8_t)displayPage2, file);
348 fputc((uint8_t)hiRes, file);
349 fputc((uint8_t)alternateCharset, file);
350 fputc((uint8_t)col80Mode, file);
351 fputc(lcState, file);
353 // Write out floppy state
354 floppyDrive[0].SaveState(file);
356 // Write out Mockingboard state
362 static bool LoadApple2State(const char * filename)
364 WriteLog("Main: Loading Apple2 state...\n");
365 FILE * file = fopen(filename, "rb");
369 WriteLog("Could not open file \"%s\" for reading!\n", filename);
374 fread(buffer, 1, 18, file);
377 if (memcmp(buffer, stateHeader, 18) != 0)
380 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
385 fread(&mainCPU, 1, sizeof(mainCPU), file);
388 fread(ram, 1, 0x10000, file);
389 fread(ram2, 1, 0x10000, file);
391 // Read in state variables
392 keyDown = (bool)fgetc(file);
393 openAppleDown = (bool)fgetc(file);
394 closedAppleDown = (bool)fgetc(file);
395 store80Mode = (bool)fgetc(file);
396 vbl = (bool)fgetc(file);
397 intCXROM = (bool)fgetc(file);
398 slotC3ROM = (bool)fgetc(file);
399 intC8ROM = (bool)fgetc(file);
400 ramrd = (bool)fgetc(file);
401 ramwrt = (bool)fgetc(file);
402 altzp = (bool)fgetc(file);
403 ioudis = (bool)fgetc(file);
404 dhires = (bool)fgetc(file);
405 flash = (bool)fgetc(file);
406 textMode = (bool)fgetc(file);
407 mixedMode = (bool)fgetc(file);
408 displayPage2 = (bool)fgetc(file);
409 hiRes = (bool)fgetc(file);
410 alternateCharset = (bool)fgetc(file);
411 col80Mode = (bool)fgetc(file);
412 lcState = fgetc(file);
414 // Read in floppy state
415 floppyDrive[0].LoadState(file);
417 // Read in Mockingboard state
421 // Make sure things are in a sane state before execution :-P
422 mainCPU.RdMem = AppleReadMem;
423 mainCPU.WrMem = AppleWriteMem;
424 mainCPU.Timer = AppleTimer;
431 static void ResetApple2State(void)
434 openAppleDown = false;
435 closedAppleDown = false;
450 // Without this, you can wedge the system :-/
451 memset(ram, 0, 0x10000);
452 memset(ram2, 0, 0x10000);
453 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
457 static double cyclesForSample = 0;
458 static void AppleTimer(uint16_t cycles)
460 // Handle PHI2 clocked stuff here...
462 floppyDrive[0].RunSequencer(cycles);
466 // 21.26009 cycles per sample @ 48000 (running @ 1,020,484.32 Hz)
467 // 16.688154500083 ms = 1 frame
468 cyclesForSample += (double)cycles;
470 if (cyclesForSample >= 21.26009)
473 sampleClock = mainCPU.clock;
474 WriteLog(" cyclesForSample = %lf (%d samples, cycles=%d)\n", cyclesForSample, sampleClock - lastSampleClock, cycles);
476 lastSampleClock = sampleClock;
478 WriteSampleToBuffer();
479 cyclesForSample -= 21.26009;
485 #ifdef CPU_CLOCK_CHECKING
487 uint32_t totalCPU = 0;
488 uint64_t lastClock = 0;
493 int main(int /*argc*/, char * /*argv*/[])
495 InitLog("./apple2.log");
497 srand(time(NULL)); // Initialize RNG
500 // Make some timing/address tables
502 for(uint32_t line=0; line<262; line++)
504 WriteLog("LINE %03i: ", line);
506 for(uint32_t col=0; col<65; col++)
508 // Convert these to H/V counters
509 uint32_t hcount = col - 1;
511 // HC sees zero twice:
512 if (hcount == 0xFFFFFFFF)
515 uint32_t vcount = line + 0xFA;
517 // Now do the address calculations
518 uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
519 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
520 uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
522 // Add in particulars for the gfx mode we're in...
525 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
526 | (!store80Mode && displayPage2 ? 0x800 : 0);
529 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
530 | (!store80Mode && displayPage2 ? 0x4000 : 0)
531 | ((vcount & 0x07) << 10);
533 WriteLog("$%04X ", address);
542 memset(ram, 0, 0x10000);
543 memset(rom, 0, 0x10000);
544 memset(ram2, 0, 0x10000);
550 // Install devices in slots
551 InstallFloppy(SLOT6);
552 InstallMockingboard(SLOT4);
553 InstallHardDrive(SLOT7);
555 // Set up V65C02 execution context
556 memset(&mainCPU, 0, sizeof(V65C02REGS));
557 mainCPU.RdMem = AppleReadMem;
558 mainCPU.WrMem = AppleWriteMem;
559 mainCPU.Timer = AppleTimer;
560 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
563 if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
565 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
569 memcpy(rom + 0xC000, apple2eEnhROM, 0x4000);
572 WriteLog("About to initialize video...\n");
576 printf("Could not init screen: aborting!\n");
580 GUI::Init(sdlRenderer);
582 WriteLog("About to initialize audio...\n");
585 if (settings.autoStateSaving)
587 // Load last state from file...
588 if (!LoadApple2State(settings.autoStatePath))
589 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
593 So, how to run this then? Right now, we have two separate threads, one for the CPU and one for audio. The screen refresh is tied to the CPU thread.
595 To do this properly, however, we need to execute approximately 1,020,484.32 cycles per second, and we need to tie the AY/regular sound to this rate. Video should happen still approximately 60 times a second, even though the real thing has a frame rate of 59.92 Hz.
597 Right now, the speed of the CPU is tied to the host system's refresh rate (which seems to be somewhere around 59.9 Hz). Under this regime, the sound thread is starved much of the time, which means there's a timing mismatch somewhere between the sound thread and the CPU thread and the video (main) thread.
599 Other considerations: Even though we know the exact amount of cycles for one frame (17030 cycles to be exact), because the video frame rate is slightly under 60 (~59.92) the amount of time those cycles take can't be tied to the host system refresh rate, as they won't be the same (it will have about 8,000 or so more cycles in one second than it should have using 60 frames per second as the base frequency). However, we do know that the system clock is 14.318180 MHz, and we do know that 1 out of every 65 cycles will take 2 extra ticks of the system clock (cycles are normally 14 ticks of the system clock). So, by virtue of this, we know how long one frame is in seconds exactly (which would be (((65 * 14) + 2) * 262) / 14318180 = 16.688154500083 milliseconds).
601 So we need to decouple the CPU thread from the host video thread, and have the CPU frame run at its rate so that it will complete its running in its alloted time. We also need to have a little bit of cushion for the sound thread, so that its buffer doesn't starve. Assuming we get the timing correct, it will pull ahead and fall behind and all average out in the end.
607 InitializeEventList();
608 // Set frame to fire at 1/60 s interval
609 SetCallbackTime(FrameCallback, 16666.66666667);
610 // Set up blinking at 1/4 s intervals
611 // SetCallbackTime(BlinkTimer, 250000);
612 startTicks = SDL_GetTicks();
614 #ifdef THREADED_65C02
615 // Kick off the CPU...
616 cpuCond = SDL_CreateCond();
617 mainSem = SDL_CreateSemaphore(1);
618 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
619 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
620 // SDL_sem * mainMutex = SDL_CreateMutex();
623 WriteLog("Entering main loop...\n");
627 #ifdef CPU_CLOCK_CHECKING
628 double timeToNextEvent = GetTimeToNextEvent();
630 #ifndef THREADED_65C02
631 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
633 #ifdef CPU_CLOCK_CHECKING
634 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
640 #ifdef THREADED_65C02
641 WriteLog("Main: cpuFinished = true;\n");
644 WriteLog("Main: SDL_SemWait(mainSem);\n");
645 // Only do this if NOT in power off/emulation paused mode!
647 // Should lock until CPU thread is waiting...
648 SDL_SemWait(mainSem);
651 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
652 SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
653 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
654 SDL_WaitThread(cpuThread, NULL);
655 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
656 SDL_DestroyCond(cpuCond);
657 SDL_DestroySemaphore(mainSem);
659 // Autosave state here, if requested...
660 if (settings.autoStateSaving)
661 SaveApple2State(settings.autoStatePath);
663 floppyDrive[0].SaveImage(0);
664 floppyDrive[0].SaveImage(1);
667 #include "dis65c02.h"
668 static char disbuf[80];
672 pc += Decode65C02(&mainCPU, disbuf, pc);
673 WriteLog("%s\n", disbuf);
687 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
688 // +CTRL+SHIFT (3). Order of keys is:
689 // Delete, left, tab, down, up, return, right, escape
690 // Space, single quote, comma, minus, period, slash
692 // Semicolon, equals, left bracket, backslash, right bracket, backquote
693 // Letters a-z (lowercase)
695 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
696 // keyboards, so this table should suffice for the shifted keys just
699 uint8_t apple2e_keycode[4][56] = {
701 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
702 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
703 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
704 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
705 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
706 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
707 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
710 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
711 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
712 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
713 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
714 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
715 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
716 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
719 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
720 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
721 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
722 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
723 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
724 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
725 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
727 { // With CTRL+Shift held
728 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
729 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
730 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
731 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
732 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
733 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
734 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
738 static uint32_t frameCount = 0;
739 static void FrameCallback(void)
744 frameTimePtr = (frameTimePtr + 1) % 60;
745 frameTime[frameTimePtr] = startTicks;
747 while (SDL_PollEvent(&event))
752 // We do our own repeat handling thank you very much! :-)
753 if (event.key.repeat != 0)
756 // This breaks IMEs and the like, but we'll do simple for now
757 if (GUI::KeyDown(event.key.keysym.sym))
760 // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
762 if ((event.key.keysym.mod & KMOD_CTRL)
763 && (event.key.keysym.mod & KMOD_SHIFT)
764 && (event.key.keysym.sym == SDLK_q))
767 // We return here, because we don't want to pick up any
768 // spurious keypresses with our exit sequence.
772 // CTRL+RESET key emulation (mapped to CTRL+HOME)
773 if ((event.key.keysym.mod & KMOD_CTRL)
774 && (event.key.keysym.sym == SDLK_HOME))
776 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
778 // Need to reset the MMU switches as well on RESET
785 // There has GOT to be a better way off mapping SDLKs to our
786 // keyindex. But for now, this should suffice.
789 switch (event.key.keysym.sym)
791 case SDLK_BACKSPACE: keyIndex = 0; break;
792 case SDLK_LEFT: keyIndex = 1; break;
793 case SDLK_TAB: keyIndex = 2; break;
794 case SDLK_DOWN: keyIndex = 3; break;
795 case SDLK_UP: keyIndex = 4; break;
796 case SDLK_RETURN: keyIndex = 5; break;
797 case SDLK_RIGHT: keyIndex = 6; break;
798 case SDLK_ESCAPE: keyIndex = 7; break;
799 case SDLK_SPACE: keyIndex = 8; break;
800 case SDLK_QUOTE: keyIndex = 9; break;
801 case SDLK_COMMA: keyIndex = 10; break;
802 case SDLK_MINUS: keyIndex = 11; break;
803 case SDLK_PERIOD: keyIndex = 12; break;
804 case SDLK_SLASH: keyIndex = 13; break;
805 case SDLK_0: keyIndex = 14; break;
806 case SDLK_1: keyIndex = 15; break;
807 case SDLK_2: keyIndex = 16; break;
808 case SDLK_3: keyIndex = 17; break;
809 case SDLK_4: keyIndex = 18; break;
810 case SDLK_5: keyIndex = 19; break;
811 case SDLK_6: keyIndex = 20; break;
812 case SDLK_7: keyIndex = 21; break;
813 case SDLK_8: keyIndex = 22; break;
814 case SDLK_9: keyIndex = 23; break;
815 case SDLK_SEMICOLON: keyIndex = 24; break;
816 case SDLK_EQUALS: keyIndex = 25; break;
817 case SDLK_LEFTBRACKET: keyIndex = 26; break;
818 case SDLK_BACKSLASH: keyIndex = 27; break;
819 case SDLK_RIGHTBRACKET: keyIndex = 28; break;
820 case SDLK_BACKQUOTE: keyIndex = 29; break;
821 case SDLK_a: keyIndex = 30; break;
822 case SDLK_b: keyIndex = 31; break;
823 case SDLK_c: keyIndex = 32; break;
824 case SDLK_d: keyIndex = 33; break;
825 case SDLK_e: keyIndex = 34; break;
826 case SDLK_f: keyIndex = 35; break;
827 case SDLK_g: keyIndex = 36; break;
828 case SDLK_h: keyIndex = 37; break;
829 case SDLK_i: keyIndex = 38; break;
830 case SDLK_j: keyIndex = 39; break;
831 case SDLK_k: keyIndex = 40; break;
832 case SDLK_l: keyIndex = 41; break;
833 case SDLK_m: keyIndex = 42; break;
834 case SDLK_n: keyIndex = 43; break;
835 case SDLK_o: keyIndex = 44; break;
836 case SDLK_p: keyIndex = 45; break;
837 case SDLK_q: keyIndex = 46; break;
838 case SDLK_r: keyIndex = 47; break;
839 case SDLK_s: keyIndex = 48; break;
840 case SDLK_t: keyIndex = 49; break;
841 case SDLK_u: keyIndex = 50; break;
842 case SDLK_v: keyIndex = 51; break;
843 case SDLK_w: keyIndex = 52; break;
844 case SDLK_x: keyIndex = 53; break;
845 case SDLK_y: keyIndex = 54; break;
846 case SDLK_z: keyIndex = 55; break;
849 // Stuff the key in if we have a valid one...
850 if (keyIndex != 0xFF)
852 // Handle Shift, CTRL, & Shift+CTRL combos
855 if (event.key.keysym.mod & KMOD_CTRL)
858 if (event.key.keysym.mod & KMOD_SHIFT)
861 lastKeyPressed = apple2e_keycode[table][keyIndex];
865 if ((SDL_GetModState() & KMOD_CAPS)
866 && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A))
867 lastKeyPressed -= 0x20;
869 // Handle key repeat if the key hasn't been held
873 // Buffer the key held. Note that the last key is always
874 // stuffed into keysHeld[0].
875 if (keyDownCount >= 2)
877 keysHeld[1] = keysHeld[0];
878 keysHeldAppleCode[1] = keysHeldAppleCode[0];
880 if (keyDownCount > 2)
884 keysHeld[0] = event.key.keysym.sym;
885 keysHeldAppleCode[0] = lastKeyPressed;
889 if (event.key.keysym.sym == SDLK_PAUSE)
891 pauseMode = !pauseMode;
896 SpawnMessage("*** PAUSED ***");
901 SpawnMessage("*** RESUME ***");
905 else if (event.key.keysym.sym == SDLK_LALT)
906 openAppleDown = true;
907 else if (event.key.keysym.sym == SDLK_RALT)
908 closedAppleDown = true;
909 else if (event.key.keysym.sym == SDLK_F2)
911 else if (event.key.keysym.sym == SDLK_F3)
913 else if (event.key.keysym.sym == SDLK_F4)
915 else if (event.key.keysym.sym == SDLK_F5)
918 char volStr[19] = "[****************]";
920 for(int i=GetVolume(); i<16; i++)
923 SpawnMessage("Volume: %s", volStr);
925 else if (event.key.keysym.sym == SDLK_F6)
928 char volStr[19] = "[****************]";
930 for(int i=GetVolume(); i<16; i++)
933 SpawnMessage("Volume: %s", volStr);
935 else if (event.key.keysym.sym == SDLK_F7)
937 // 4th root of 2 is ~1.18920711500272 (~1.5 dB)
938 // This attenuates by ~3 dB
939 VAY_3_8910::maxVolume /= 1.4142135f;
940 SpawnMessage("MB Volume: %d", (int)VAY_3_8910::maxVolume);
942 else if (event.key.keysym.sym == SDLK_F8)
944 VAY_3_8910::maxVolume *= 1.4142135f;
945 SpawnMessage("MB Volume: %d", (int)VAY_3_8910::maxVolume);
947 else if (event.key.keysym.sym == SDLK_F9)
949 floppyDrive[0].CreateBlankImage(1);
950 // SpawnMessage("Image cleared...");
952 /*else if (event.key.keysym.sym == SDLK_F10)
954 floppyDrive[0].SwapImages();
955 // SpawnMessage("Image swapped...");
957 // Toggle the disassembly process
958 else if (event.key.keysym.sym == SDLK_F11)
961 SpawnMessage("Trace: %s", (dumpDis ? "ON" : "off"));
963 else if (event.key.keysym.sym == SDLK_F12)
965 if (!fullscreenDebounce)
968 fullscreenDebounce = true;
975 if (event.key.keysym.sym == SDLK_F12)
976 fullscreenDebounce = false;
977 // Paddle buttons 0 & 1
978 else if (event.key.keysym.sym == SDLK_LALT)
979 openAppleDown = false;
980 else if (event.key.keysym.sym == SDLK_RALT)
981 closedAppleDown = false;
982 else if ((event.key.keysym.mod & KMOD_CTRL)
983 && (event.key.keysym.sym == SDLK_HOME))
984 resetKeyDown = false;
987 // Handle key buffering 'key up' event (2 key rollover)
988 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
991 keyDelay = 0; // Reset key delay
993 else if (keyDownCount == 2)
995 if (event.key.keysym.sym == keysHeld[0])
998 keysHeld[0] = keysHeld[1];
999 keysHeldAppleCode[0] = keysHeldAppleCode[1];
1001 else if (event.key.keysym.sym == keysHeld[1])
1010 case SDL_MOUSEBUTTONDOWN:
1011 GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
1014 case SDL_MOUSEBUTTONUP:
1015 GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
1018 case SDL_MOUSEMOTION:
1019 GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
1021 // Handle mouse showing when the mouse is hidden...
1022 if (hideMouseTimeout == -1)
1025 hideMouseTimeout = 60;
1028 case SDL_WINDOWEVENT:
1029 if (event.window.event == SDL_WINDOWEVENT_LEAVE)
1030 GUI::MouseMove(0, 0, 0);
1039 // Hide the mouse if it's been 1s since the last time it was moved
1040 // N.B.: Should disable mouse hiding if it's over the GUI...
1041 if ((hideMouseTimeout > 0) && !(GUI::sidebarState == SBS_SHOWN || DiskSelector::showWindow == true || Config::showWindow == true))
1043 else if (hideMouseTimeout == 0)
1049 // Stuff the Apple keyboard buffer, if any keys are pending
1050 // N.B.: May have to simulate the key repeat delay too [yup, sure do]
1051 // According to "Understanding the Apple IIe", the initial delay is
1052 // between 32 & 48 jiffies and the repeat is every 4 jiffies.
1053 if (keyDownCount > 0)
1060 lastKeyPressed = keysHeldAppleCode[0];
1065 // Handle power request from the GUI
1066 if (powerStateChangeRequested)
1068 if (GUI::powerOnState)
1071 // Unlock the CPU thread...
1072 SDL_SemPost(mainSem);
1077 // Should lock until CPU thread is waiting...
1078 SDL_SemWait(mainSem);
1082 powerStateChangeRequested = false;
1085 blinkTimer = (blinkTimer + 1) & 0x1F;
1087 if (blinkTimer == 0)
1090 // Render the Apple screen + GUI overlay
1091 RenderAppleScreen(sdlRenderer);
1092 GUI::Render(sdlRenderer);
1093 SDL_RenderPresent(sdlRenderer);
1094 SetCallbackTime(FrameCallback, 16666.66666667);
1096 #ifdef CPU_CLOCK_CHECKING
1097 //We know it's stopped, so we can get away with this...
1101 uint64_t clock = GetCurrentV65C02Clock();
1102 //totalCPU += (uint32_t)(clock - lastClock);
1104 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1111 // This is the problem: If you set the interval to 16, it runs faster than
1112 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1113 // have it do 16 for one frame, then 17 for two others. Then it should average
1114 // out to 1/60s per frame every 3 frames. [And now it does!]
1115 // Maybe we need a higher resolution timer, as the SDL_GetTicks() (in ms) seems
1116 // to jitter all over the place...
1117 frameCount = (frameCount + 1) % 3;
1118 // uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1119 // Get number of ticks burned in this frame, for displaying elsewhere
1121 frameTicks = SDL_GetTicks() - startTicks;
1123 frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
1126 // Wait for next frame...
1127 // while (SDL_GetTicks() - startTicks < waitFrameTime)
1131 startTicks = SDL_GetTicks();
1133 startTicks = SDL_GetPerformanceCounter();
1136 uint64_t cpuCycles = GetCurrentV65C02Clock();
1137 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1138 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1139 lastCPUCycles = cpuCycles
1142 //let's wait, then signal...
1143 //works longer, but then still falls behind... [FIXED, see above]
1144 #ifdef THREADED_65C02
1146 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1151 static void BlinkTimer(void)
1153 // Set up blinking at 1/4 sec intervals
1155 SetCallbackTime(BlinkTimer, 250000);
1160 Next problem is this: How to have events occur and synchronize with the rest
1163 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1164 remainder CPU cycles over...)
1166 One way would be to use a fractional accumulator, then subtract 1 every
1167 time it overflows. Like so:
1169 double overflow = 0;
1173 Execute6808(&soundCPU, time);
1174 overflow += 0.289115646;