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"
61 #include "gui/diskselector.h"
63 // Debug and misc. defines
65 #define THREADED_65C02
66 #define CPU_THREAD_OVERFLOW_COMPENSATION
68 //#define CPU_CLOCK_CHECKING
69 //#define THREAD_DEBUGGING
70 #define SOFT_SWITCH_DEBUGGING
74 uint8_t ram[0x10000], rom[0x10000]; // RAM & ROM spaces
75 uint8_t ram2[0x10000]; // Auxillary RAM
76 V65C02REGS mainCPU; // v65C02 execution context
77 uint8_t appleType = APPLE_TYPE_IIE;
78 bool powerStateChangeRequested = false;
79 uint64_t frameCycleStart;
81 uint64_t frameTicks = 0;
82 uint64_t frameTime[60];
83 uint32_t frameTimePtr = 0;
87 uint8_t lastKeyPressed = 0;
89 bool openAppleDown = false;
90 bool closedAppleDown = false;
91 bool store80Mode = false;
93 bool intCXROM = false;
94 bool slotC3ROM = false;
95 bool intC8ROM = false;
101 // Language card state (ROM read, no write)
102 uint8_t lcState = 0x02;
103 uint8_t blinkTimer = 0;
105 static bool running = true; // Machine running state flag...
106 static uint64_t startTicks;
107 static bool pauseMode = false;
108 static bool fullscreenDebounce = false;
109 static bool resetKeyDown = false;
110 static int8_t hideMouseTimeout = 60;
112 // Vars to handle the //e's 2-key rollover
113 static SDL_Keycode keysHeld[2];
114 static uint8_t keysHeldAppleCode[2];
115 static uint8_t keyDownCount = 0;
116 static uint8_t keyDelay = 0;
120 static void SaveApple2State(const char * filename);
121 static bool LoadApple2State(const char * filename);
122 static void ResetApple2State(void);
123 static void AppleTimer(uint16_t);
125 // Local timer callback functions
127 static void FrameCallback(void);
128 static void BlinkTimer(void);
130 #ifdef THREADED_65C02
131 // Test of threaded execution of 6502
132 static SDL_Thread * cpuThread = NULL;
133 static SDL_cond * cpuCond = NULL;
134 static SDL_sem * mainSem = NULL;
135 static bool cpuFinished = false;
137 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
138 // This is a lie. At the end of each 65 cycle line, there is an elongated
139 // cycle (the so-called 'long' cycle) that throws the calcs out of whack.
140 // So actually, it's supposed to be 1,020,484.32 Hz
142 // Let's try a thread...
144 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
147 static uint32_t sampleCount;
148 static uint64_t sampleClock, lastSampleClock;
149 int CPUThreadFunc(void * data)
151 // Mutex must be locked for conditional to work...
152 // Also, must be created in the thread that uses it...
153 SDL_mutex * cpuMutex = SDL_CreateMutex();
155 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
156 // float overflow = 0.0;
161 uint64_t cpuFrameTickStart = SDL_GetPerformanceCounter();
162 uint64_t oldClock = mainCPU.clock;
164 sampleClock = lastSampleClock = mainCPU.clock;
165 // decrement mainSem...
166 #ifdef THREAD_DEBUGGING
167 WriteLog("CPU: SDL_SemWait(mainSem);\n");
169 SDL_SemWait(mainSem);
171 // There are exactly 800 slices of 21.333 cycles per frame, so it works
173 // [Actually, seems it's 786 slices of 21.666 cycles per frame]
175 // Set our frame cycle counter to the correct # of cycles at the start
177 frameCycleStart = mainCPU.clock - mainCPU.overflow;
178 #ifdef THREAD_DEBUGGING
179 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
181 for(int i=0; i<262; i++)
183 // If the CTRL+Reset key combo is being held, make sure the RESET
184 // line stays asserted:
186 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
188 Execute65C02(&mainCPU, 65);
190 // According to "Understanding The Apple IIe", VBL asserted after
191 // the last byte of the screen is read and let go on the first read
192 // of the first byte of the screen. We now know that the screen
193 // starts on line #6 and ends on line #197 (of the vertical
194 // counter--actual VBLANK proper happens on lines 230 thru 233).
195 vbl = ((i >= 6) && (i <= 197) ? true : false);
198 //WriteLog("*** Frame ran for %d cycles (%.3lf µs, %d samples).\n", mainCPU.clock - oldClock, ((double)(SDL_GetPerformanceCounter() - cpuFrameTickStart) * 1000000.0) / (double)SDL_GetPerformanceFrequency(), sampleCount);
199 // frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
201 Other timings from UTA2E:
202 Power-up reset 32.6 msec / 512 horizontal scans
203 Flash cycle 1.87 Hz / Vertical freq./32
204 Delay before auto repeat 534-801 msec / 32-48 vertical scans
205 Auto repeat frequency 15 Hz / Vertical freq./4
206 Vertical frequency 59.94 Hz (actually, 59.92 Hz [59.92274339401])
207 Horizontal frequency 15,734 Hz (actually, 15700 Hz)
208 1 NTSC frame = 17030 cycles (N.B.: this works out to 1021800 cycles per sec.)
209 NTSC clock frequency ("composite" freq.) is 1.02048432 MHz, which is 14.31818 x (65 / (65 x 14 + 2)) MHz.
211 70 blank lines for top margin, 192 lines for screen, (35 & 35?)
212 VA-C,V0-5 is upcounter starting at 011111010 ($FA) to 111111111 ($1FF)
213 Horizontal counter is upcounter resets to 0000000, then jumps to 1000000 &
214 counts up to 1111111 (bit 7 is Horizontal Preset Enable, which resets the counter when it goes low, the rest are H0-5)
216 pg. 3-24 says one cycle before VBL the counters will be at
217 010111111/1111111 (that doesn't look right to me...)
232 SUMS are calculated like so:
237 ------------------------------
238 SUM-A6 SUM-A5 SUM-A4 SUM-A3
240 In TEXT mode, A10 == (80STORE' * PAGE2)', A11 == 80STORE' * PAGE2
243 In HIRES mode, A13 == (PAGE2 * 80STORE')', A14 == PAGE2 * 80STORE'
244 A10 == VA, A11 == VB, A12 == VC, A15 == 0
246 N.B.: VA-C are lower bits than V5-0
248 HC, from 00, 0 to 23 is the HBL interval, with horizontal retrace occuring between cycles 8 and 11.
249 VC, from line 0-5 and 198-261 is the VBL interval, with vertical retrace occuring between lines 230-233
253 #ifdef THREAD_DEBUGGING
254 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
256 SDL_mutexP(cpuMutex);
257 // increment mainSem...
258 #ifdef THREAD_DEBUGGING
259 WriteLog("CPU: SDL_SemPost(mainSem);\n");
261 SDL_SemPost(mainSem);
262 #ifdef THREAD_DEBUGGING
263 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
265 SDL_CondWait(cpuCond, cpuMutex);
267 #ifdef THREAD_DEBUGGING
268 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
270 SDL_mutexV(cpuMutex);
272 while (!cpuFinished);
274 SDL_DestroyMutex(cpuMutex);
282 // Request a change in the power state of the emulated Apple
284 void SetPowerState(void)
286 powerStateChangeRequested = true;
291 // Load a file into RAM/ROM image space
293 bool LoadImg(char * filename, uint8_t * ram, int size)
295 FILE * fp = fopen(filename, "rb");
300 fread(ram, 1, size, fp);
307 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.2";
308 static void SaveApple2State(const char * filename)
310 WriteLog("Main: Saving Apple2 state...\n");
311 FILE * file = fopen(filename, "wb");
315 WriteLog("Could not open file \"%s\" for writing!\n", filename);
320 fwrite(stateHeader, 1, 18, file);
322 // Write out CPU state
323 fwrite(&mainCPU, 1, sizeof(mainCPU), file);
325 // Write out main memory
326 fwrite(ram, 1, 0x10000, file);
327 fwrite(ram2, 1, 0x10000, file);
329 // Write out state variables
330 fputc((uint8_t)keyDown, file);
331 fputc((uint8_t)openAppleDown, file);
332 fputc((uint8_t)closedAppleDown, file);
333 fputc((uint8_t)store80Mode, file);
334 fputc((uint8_t)vbl, file);
335 fputc((uint8_t)intCXROM, file);
336 fputc((uint8_t)slotC3ROM, file);
337 fputc((uint8_t)intC8ROM, file);
338 fputc((uint8_t)ramrd, file);
339 fputc((uint8_t)ramwrt, file);
340 fputc((uint8_t)altzp, file);
341 fputc((uint8_t)ioudis, file);
342 fputc((uint8_t)dhires, file);
343 fputc((uint8_t)flash, file);
344 fputc((uint8_t)textMode, file);
345 fputc((uint8_t)mixedMode, file);
346 fputc((uint8_t)displayPage2, file);
347 fputc((uint8_t)hiRes, file);
348 fputc((uint8_t)alternateCharset, file);
349 fputc((uint8_t)col80Mode, file);
350 fputc(lcState, file);
352 // Write out floppy state
353 floppyDrive[0].SaveState(file);
355 // Write out Mockingboard state
361 static bool LoadApple2State(const char * filename)
363 WriteLog("Main: Loading Apple2 state...\n");
364 FILE * file = fopen(filename, "rb");
368 WriteLog("Could not open file \"%s\" for reading!\n", filename);
373 fread(buffer, 1, 18, file);
376 if (memcmp(buffer, stateHeader, 18) != 0)
379 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
384 fread(&mainCPU, 1, sizeof(mainCPU), file);
387 fread(ram, 1, 0x10000, file);
388 fread(ram2, 1, 0x10000, file);
390 // Read in state variables
391 keyDown = (bool)fgetc(file);
392 openAppleDown = (bool)fgetc(file);
393 closedAppleDown = (bool)fgetc(file);
394 store80Mode = (bool)fgetc(file);
395 vbl = (bool)fgetc(file);
396 intCXROM = (bool)fgetc(file);
397 slotC3ROM = (bool)fgetc(file);
398 intC8ROM = (bool)fgetc(file);
399 ramrd = (bool)fgetc(file);
400 ramwrt = (bool)fgetc(file);
401 altzp = (bool)fgetc(file);
402 ioudis = (bool)fgetc(file);
403 dhires = (bool)fgetc(file);
404 flash = (bool)fgetc(file);
405 textMode = (bool)fgetc(file);
406 mixedMode = (bool)fgetc(file);
407 displayPage2 = (bool)fgetc(file);
408 hiRes = (bool)fgetc(file);
409 alternateCharset = (bool)fgetc(file);
410 col80Mode = (bool)fgetc(file);
411 lcState = fgetc(file);
413 // Read in floppy state
414 floppyDrive[0].LoadState(file);
416 // Read in Mockingboard state
420 // Make sure things are in a sane state before execution :-P
421 mainCPU.RdMem = AppleReadMem;
422 mainCPU.WrMem = AppleWriteMem;
423 mainCPU.Timer = AppleTimer;
430 static void ResetApple2State(void)
433 openAppleDown = false;
434 closedAppleDown = false;
449 // Without this, you can wedge the system :-/
450 memset(ram, 0, 0x10000);
451 memset(ram2, 0, 0x10000);
452 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
456 static double cyclesForSample = 0;
457 static void AppleTimer(uint16_t cycles)
459 // Handle PHI2 clocked stuff here...
461 floppyDrive[0].RunSequencer(cycles);
465 // 21.26009 cycles per sample @ 48000 (running @ 1,020,484.32 Hz)
466 // 16.688154500083 ms = 1 frame
467 cyclesForSample += (double)cycles;
469 if (cyclesForSample >= 21.26009)
472 sampleClock = mainCPU.clock;
473 WriteLog(" cyclesForSample = %lf (%d samples, cycles=%d)\n", cyclesForSample, sampleClock - lastSampleClock, cycles);
475 lastSampleClock = sampleClock;
477 WriteSampleToBuffer();
478 cyclesForSample -= 21.26009;
484 #ifdef CPU_CLOCK_CHECKING
486 uint32_t totalCPU = 0;
487 uint64_t lastClock = 0;
492 int main(int /*argc*/, char * /*argv*/[])
494 InitLog("./apple2.log");
496 srand(time(NULL)); // Initialize RNG
499 // Make some timing/address tables
501 for(uint32_t line=0; line<262; line++)
503 WriteLog("LINE %03i: ", line);
505 for(uint32_t col=0; col<65; col++)
507 // Convert these to H/V counters
508 uint32_t hcount = col - 1;
510 // HC sees zero twice:
511 if (hcount == 0xFFFFFFFF)
514 uint32_t vcount = line + 0xFA;
516 // Now do the address calculations
517 uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
518 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
519 uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
521 // Add in particulars for the gfx mode we're in...
524 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
525 | (!store80Mode && displayPage2 ? 0x800 : 0);
528 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
529 | (!store80Mode && displayPage2 ? 0x4000 : 0)
530 | ((vcount & 0x07) << 10);
532 WriteLog("$%04X ", address);
541 memset(ram, 0, 0x10000);
542 memset(rom, 0, 0x10000);
543 memset(ram2, 0, 0x10000);
549 // Install devices in slots
550 InstallFloppy(SLOT6);
551 InstallMockingboard(SLOT4);
552 InstallHardDrive(SLOT7);
554 // Set up V65C02 execution context
555 memset(&mainCPU, 0, sizeof(V65C02REGS));
556 mainCPU.RdMem = AppleReadMem;
557 mainCPU.WrMem = AppleWriteMem;
558 mainCPU.Timer = AppleTimer;
559 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
562 if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
564 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
568 memcpy(rom + 0xC000, apple2eEnhROM, 0x4000);
571 WriteLog("About to initialize video...\n");
575 printf("Could not init screen: aborting!\n");
579 GUI::Init(sdlRenderer);
581 WriteLog("About to initialize audio...\n");
584 if (settings.autoStateSaving)
586 // Load last state from file...
587 if (!LoadApple2State(settings.autoStatePath))
588 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
592 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.
594 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.
596 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.
598 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).
600 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.
606 InitializeEventList();
607 // Set frame to fire at 1/60 s interval
608 SetCallbackTime(FrameCallback, 16666.66666667);
609 // Set up blinking at 1/4 s intervals
610 // SetCallbackTime(BlinkTimer, 250000);
611 startTicks = SDL_GetTicks();
613 #ifdef THREADED_65C02
614 // Kick off the CPU...
615 cpuCond = SDL_CreateCond();
616 mainSem = SDL_CreateSemaphore(1);
617 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
618 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
619 // SDL_sem * mainMutex = SDL_CreateMutex();
622 WriteLog("Entering main loop...\n");
626 #ifdef CPU_CLOCK_CHECKING
627 double timeToNextEvent = GetTimeToNextEvent();
629 #ifndef THREADED_65C02
630 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
632 #ifdef CPU_CLOCK_CHECKING
633 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
639 #ifdef THREADED_65C02
640 WriteLog("Main: cpuFinished = true;\n");
643 WriteLog("Main: SDL_SemWait(mainSem);\n");
644 // Only do this if NOT in power off/emulation paused mode!
646 // Should lock until CPU thread is waiting...
647 SDL_SemWait(mainSem);
650 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
651 SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
652 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
653 SDL_WaitThread(cpuThread, NULL);
654 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
655 SDL_DestroyCond(cpuCond);
656 SDL_DestroySemaphore(mainSem);
658 // Autosave state here, if requested...
659 if (settings.autoStateSaving)
660 SaveApple2State(settings.autoStatePath);
662 floppyDrive[0].SaveImage(0);
663 floppyDrive[0].SaveImage(1);
666 #include "dis65c02.h"
667 static char disbuf[80];
671 pc += Decode65C02(&mainCPU, disbuf, pc);
672 WriteLog("%s\n", disbuf);
686 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
687 // +CTRL+SHIFT (3). Order of keys is:
688 // Delete, left, tab, down, up, return, right, escape
689 // Space, single quote, comma, minus, period, slash
691 // Semicolon, equals, left bracket, backslash, right bracket, backquote
692 // Letters a-z (lowercase)
694 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
695 // keyboards, so this table should suffice for the shifted keys just
698 uint8_t apple2e_keycode[4][56] = {
700 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
701 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
702 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
703 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
704 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
705 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
706 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
709 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
710 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
711 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
712 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
713 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
714 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
715 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
718 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
719 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
720 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
721 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
722 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
723 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
724 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
726 { // With CTRL+Shift held
727 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
728 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
729 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
730 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
731 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
732 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
733 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
737 static uint32_t frameCount = 0;
738 static void FrameCallback(void)
743 frameTimePtr = (frameTimePtr + 1) % 60;
744 frameTime[frameTimePtr] = startTicks;
746 while (SDL_PollEvent(&event))
751 // We do our own repeat handling thank you very much! :-)
752 if (event.key.repeat != 0)
755 // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
757 if ((event.key.keysym.mod & KMOD_CTRL)
758 && (event.key.keysym.mod & KMOD_SHIFT)
759 && (event.key.keysym.sym == SDLK_q))
762 // We return here, because we don't want to pick up any
763 // spurious keypresses with our exit sequence.
767 // CTRL+RESET key emulation (mapped to CTRL+HOME)
768 if ((event.key.keysym.mod & KMOD_CTRL)
769 && (event.key.keysym.sym == SDLK_HOME))
771 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
773 // Need to reset the MMU switches as well on RESET
780 // There has GOT to be a better way off mapping SDLKs to our
781 // keyindex. But for now, this should suffice.
784 switch (event.key.keysym.sym)
786 case SDLK_BACKSPACE: keyIndex = 0; break;
787 case SDLK_LEFT: keyIndex = 1; break;
788 case SDLK_TAB: keyIndex = 2; break;
789 case SDLK_DOWN: keyIndex = 3; break;
790 case SDLK_UP: keyIndex = 4; break;
791 case SDLK_RETURN: keyIndex = 5; break;
792 case SDLK_RIGHT: keyIndex = 6; break;
793 case SDLK_ESCAPE: keyIndex = 7; break;
794 case SDLK_SPACE: keyIndex = 8; break;
795 case SDLK_QUOTE: keyIndex = 9; break;
796 case SDLK_COMMA: keyIndex = 10; break;
797 case SDLK_MINUS: keyIndex = 11; break;
798 case SDLK_PERIOD: keyIndex = 12; break;
799 case SDLK_SLASH: keyIndex = 13; break;
800 case SDLK_0: keyIndex = 14; break;
801 case SDLK_1: keyIndex = 15; break;
802 case SDLK_2: keyIndex = 16; break;
803 case SDLK_3: keyIndex = 17; break;
804 case SDLK_4: keyIndex = 18; break;
805 case SDLK_5: keyIndex = 19; break;
806 case SDLK_6: keyIndex = 20; break;
807 case SDLK_7: keyIndex = 21; break;
808 case SDLK_8: keyIndex = 22; break;
809 case SDLK_9: keyIndex = 23; break;
810 case SDLK_SEMICOLON: keyIndex = 24; break;
811 case SDLK_EQUALS: keyIndex = 25; break;
812 case SDLK_LEFTBRACKET: keyIndex = 26; break;
813 case SDLK_BACKSLASH: keyIndex = 27; break;
814 case SDLK_RIGHTBRACKET: keyIndex = 28; break;
815 case SDLK_BACKQUOTE: keyIndex = 29; break;
816 case SDLK_a: keyIndex = 30; break;
817 case SDLK_b: keyIndex = 31; break;
818 case SDLK_c: keyIndex = 32; break;
819 case SDLK_d: keyIndex = 33; break;
820 case SDLK_e: keyIndex = 34; break;
821 case SDLK_f: keyIndex = 35; break;
822 case SDLK_g: keyIndex = 36; break;
823 case SDLK_h: keyIndex = 37; break;
824 case SDLK_i: keyIndex = 38; break;
825 case SDLK_j: keyIndex = 39; break;
826 case SDLK_k: keyIndex = 40; break;
827 case SDLK_l: keyIndex = 41; break;
828 case SDLK_m: keyIndex = 42; break;
829 case SDLK_n: keyIndex = 43; break;
830 case SDLK_o: keyIndex = 44; break;
831 case SDLK_p: keyIndex = 45; break;
832 case SDLK_q: keyIndex = 46; break;
833 case SDLK_r: keyIndex = 47; break;
834 case SDLK_s: keyIndex = 48; break;
835 case SDLK_t: keyIndex = 49; break;
836 case SDLK_u: keyIndex = 50; break;
837 case SDLK_v: keyIndex = 51; break;
838 case SDLK_w: keyIndex = 52; break;
839 case SDLK_x: keyIndex = 53; break;
840 case SDLK_y: keyIndex = 54; break;
841 case SDLK_z: keyIndex = 55; break;
844 // Stuff the key in if we have a valid one...
845 if (keyIndex != 0xFF)
847 // Handle Shift, CTRL, & Shift+CTRL combos
850 if (event.key.keysym.mod & KMOD_CTRL)
853 if (event.key.keysym.mod & KMOD_SHIFT)
856 lastKeyPressed = apple2e_keycode[table][keyIndex];
860 if ((SDL_GetModState() & KMOD_CAPS)
861 && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A))
862 lastKeyPressed -= 0x20;
864 // Handle key repeat if the key hasn't been held
868 // Buffer the key held. Note that the last key is always
869 // stuffed into keysHeld[0].
870 if (keyDownCount >= 2)
872 keysHeld[1] = keysHeld[0];
873 keysHeldAppleCode[1] = keysHeldAppleCode[0];
875 if (keyDownCount > 2)
879 keysHeld[0] = event.key.keysym.sym;
880 keysHeldAppleCode[0] = lastKeyPressed;
884 if (event.key.keysym.sym == SDLK_PAUSE)
886 pauseMode = !pauseMode;
891 SpawnMessage("*** PAUSED ***");
896 SpawnMessage("*** RESUME ***");
900 else if (event.key.keysym.sym == SDLK_LALT)
901 openAppleDown = true;
902 else if (event.key.keysym.sym == SDLK_RALT)
903 closedAppleDown = true;
904 else if (event.key.keysym.sym == SDLK_F2)
906 else if (event.key.keysym.sym == SDLK_F3)
908 else if (event.key.keysym.sym == SDLK_F4)
910 else if (event.key.keysym.sym == SDLK_F5)
913 char volStr[19] = "[****************]";
915 for(int i=GetVolume(); i<16; i++)
918 SpawnMessage("Volume: %s", volStr);
920 else if (event.key.keysym.sym == SDLK_F6)
923 char volStr[19] = "[****************]";
925 for(int i=GetVolume(); i<16; i++)
928 SpawnMessage("Volume: %s", volStr);
930 else if (event.key.keysym.sym == SDLK_F7)
932 // 4th root of 2 is ~1.18920711500272 (~1.5 dB)
933 // This attenuates by ~3 dB
934 VAY_3_8910::maxVolume /= 1.4142135f;
935 SpawnMessage("MB Volume: %d", (int)VAY_3_8910::maxVolume);
937 else if (event.key.keysym.sym == SDLK_F8)
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_F9)
944 floppyDrive[0].CreateBlankImage(1);
945 // SpawnMessage("Image cleared...");
947 /*else if (event.key.keysym.sym == SDLK_F10)
949 floppyDrive[0].SwapImages();
950 // SpawnMessage("Image swapped...");
952 // Toggle the disassembly process
953 else if (event.key.keysym.sym == SDLK_F11)
956 SpawnMessage("Trace: %s", (dumpDis ? "ON" : "off"));
958 else if (event.key.keysym.sym == SDLK_F12)
960 if (!fullscreenDebounce)
963 fullscreenDebounce = true;
970 if (event.key.keysym.sym == SDLK_F12)
971 fullscreenDebounce = false;
972 // Paddle buttons 0 & 1
973 else if (event.key.keysym.sym == SDLK_LALT)
974 openAppleDown = false;
975 else if (event.key.keysym.sym == SDLK_RALT)
976 closedAppleDown = false;
977 else if ((event.key.keysym.mod & KMOD_CTRL)
978 && (event.key.keysym.sym == SDLK_HOME))
979 resetKeyDown = false;
982 // Handle key buffering 'key up' event (2 key rollover)
983 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
986 keyDelay = 0; // Reset key delay
988 else if (keyDownCount == 2)
990 if (event.key.keysym.sym == keysHeld[0])
993 keysHeld[0] = keysHeld[1];
994 keysHeldAppleCode[0] = keysHeldAppleCode[1];
996 else if (event.key.keysym.sym == keysHeld[1])
1005 case SDL_MOUSEBUTTONDOWN:
1006 GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
1009 case SDL_MOUSEBUTTONUP:
1010 GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
1013 case SDL_MOUSEMOTION:
1014 GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
1016 // Handle mouse showing when the mouse is hidden...
1017 if (hideMouseTimeout == -1)
1020 hideMouseTimeout = 60;
1023 case SDL_WINDOWEVENT:
1024 if (event.window.event == SDL_WINDOWEVENT_LEAVE)
1025 GUI::MouseMove(0, 0, 0);
1034 // Hide the mouse if it's been 1s since the last time it was moved
1035 // N.B.: Should disable mouse hiding if it's over the GUI...
1036 if ((hideMouseTimeout > 0) && !(GUI::sidebarState == SBS_SHOWN || DiskSelector::showWindow == true))
1038 else if (hideMouseTimeout == 0)
1044 // Stuff the Apple keyboard buffer, if any keys are pending
1045 // N.B.: May have to simulate the key repeat delay too [yup, sure do]
1046 // According to "Understanding the Apple IIe", the initial delay is
1047 // between 32 & 48 jiffies and the repeat is every 4 jiffies.
1048 if (keyDownCount > 0)
1055 lastKeyPressed = keysHeldAppleCode[0];
1060 // Handle power request from the GUI
1061 if (powerStateChangeRequested)
1063 if (GUI::powerOnState)
1066 // Unlock the CPU thread...
1067 SDL_SemPost(mainSem);
1072 // Should lock until CPU thread is waiting...
1073 SDL_SemWait(mainSem);
1077 powerStateChangeRequested = false;
1080 blinkTimer = (blinkTimer + 1) & 0x1F;
1082 if (blinkTimer == 0)
1085 // Render the Apple screen + GUI overlay
1086 RenderAppleScreen(sdlRenderer);
1087 GUI::Render(sdlRenderer);
1088 SDL_RenderPresent(sdlRenderer);
1089 SetCallbackTime(FrameCallback, 16666.66666667);
1091 #ifdef CPU_CLOCK_CHECKING
1092 //We know it's stopped, so we can get away with this...
1096 uint64_t clock = GetCurrentV65C02Clock();
1097 //totalCPU += (uint32_t)(clock - lastClock);
1099 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1106 // This is the problem: If you set the interval to 16, it runs faster than
1107 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1108 // have it do 16 for one frame, then 17 for two others. Then it should average
1109 // out to 1/60s per frame every 3 frames. [And now it does!]
1110 // Maybe we need a higher resolution timer, as the SDL_GetTicks() (in ms) seems
1111 // to jitter all over the place...
1112 frameCount = (frameCount + 1) % 3;
1113 // uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1114 // Get number of ticks burned in this frame, for displaying elsewhere
1116 frameTicks = SDL_GetTicks() - startTicks;
1118 frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
1121 // Wait for next frame...
1122 // while (SDL_GetTicks() - startTicks < waitFrameTime)
1126 startTicks = SDL_GetTicks();
1128 startTicks = SDL_GetPerformanceCounter();
1131 uint64_t cpuCycles = GetCurrentV65C02Clock();
1132 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1133 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1134 lastCPUCycles = cpuCycles
1137 //let's wait, then signal...
1138 //works longer, but then still falls behind... [FIXED, see above]
1139 #ifdef THREADED_65C02
1141 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1146 static void BlinkTimer(void)
1148 // Set up blinking at 1/4 sec intervals
1150 SetCallbackTime(BlinkTimer, 250000);
1155 Next problem is this: How to have events occur and synchronize with the rest
1158 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1159 remainder CPU cycles over...)
1161 One way would be to use a fractional accumulator, then subtract 1 every
1162 time it overflows. Like so:
1164 double overflow = 0;
1168 Execute6808(&soundCPU, time);
1169 overflow += 0.289115646;