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
49 #include "floppydrive.h"
50 #include "harddrive.h"
53 #include "mockingboard.h"
59 #include "gui/diskselector.h"
61 // Debug and misc. defines
63 #define THREADED_65C02
64 #define CPU_THREAD_OVERFLOW_COMPENSATION
66 //#define CPU_CLOCK_CHECKING
67 //#define THREAD_DEBUGGING
68 #define SOFT_SWITCH_DEBUGGING
72 uint8_t ram[0x10000], rom[0x10000]; // RAM & ROM spaces
73 uint8_t ram2[0x10000]; // Auxillary RAM
74 V65C02REGS mainCPU; // v65C02 execution context
75 uint8_t appleType = APPLE_TYPE_IIE;
76 bool powerStateChangeRequested = false;
77 uint64_t frameCycleStart;
79 uint64_t frameTicks = 0;
80 uint64_t frameTime[60];
81 uint32_t frameTimePtr = 0;
85 uint8_t lastKeyPressed = 0;
87 bool openAppleDown = false;
88 bool closedAppleDown = false;
89 bool store80Mode = false;
91 bool intCXROM = false;
92 bool slotC3ROM = false;
93 bool intC8ROM = false;
99 // Language card state (ROM read, no write)
100 uint8_t lcState = 0x02;
101 uint8_t blinkTimer = 0;
103 static bool running = true; // Machine running state flag...
104 static uint64_t startTicks;
105 static bool pauseMode = false;
106 static bool fullscreenDebounce = false;
107 static bool resetKeyDown = false;
108 static int8_t hideMouseTimeout = 60;
110 // Vars to handle the //e's 2-key rollover
111 static SDL_Keycode keysHeld[2];
112 static uint8_t keysHeldAppleCode[2];
113 static uint8_t keyDownCount = 0;
114 static uint8_t keyDelay = 0;
118 static void SaveApple2State(const char * filename);
119 static bool LoadApple2State(const char * filename);
120 static void ResetApple2State(void);
121 static void AppleTimer(uint16_t);
123 // Local timer callback functions
125 static void FrameCallback(void);
126 static void BlinkTimer(void);
128 #ifdef THREADED_65C02
129 // Test of threaded execution of 6502
130 static SDL_Thread * cpuThread = NULL;
131 static SDL_cond * cpuCond = NULL;
132 static SDL_sem * mainSem = NULL;
133 static bool cpuFinished = false;
135 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
136 // This is a lie. At the end of each 65 cycle line, there is an elongated
137 // cycle (the so-called 'long' cycle) that throws the calcs out of whack.
138 // So actually, it's supposed to be 1,020,484.32 Hz
140 // Let's try a thread...
142 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
145 static uint32_t sampleCount;
146 static uint64_t sampleClock, lastSampleClock;
147 int CPUThreadFunc(void * data)
149 // Mutex must be locked for conditional to work...
150 // Also, must be created in the thread that uses it...
151 SDL_mutex * cpuMutex = SDL_CreateMutex();
153 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
154 // float overflow = 0.0;
159 uint64_t cpuFrameTickStart = SDL_GetPerformanceCounter();
160 uint64_t oldClock = mainCPU.clock;
162 sampleClock = lastSampleClock = mainCPU.clock;
163 // decrement mainSem...
164 #ifdef THREAD_DEBUGGING
165 WriteLog("CPU: SDL_SemWait(mainSem);\n");
167 SDL_SemWait(mainSem);
169 // There are exactly 800 slices of 21.333 cycles per frame, so it works
171 // [Actually, seems it's 786 slices of 21.666 cycles per frame]
173 // Set our frame cycle counter to the correct # of cycles at the start
175 frameCycleStart = mainCPU.clock - mainCPU.overflow;
176 #ifdef THREAD_DEBUGGING
177 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
179 for(int i=0; i<262; i++)
181 // If the CTRL+Reset key combo is being held, make sure the RESET
182 // line stays asserted:
184 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
186 Execute65C02(&mainCPU, 65);
188 // According to "Understanding The Apple IIe", VBL asserted after
189 // the last byte of the screen is read and let go on the first read
190 // of the first byte of the screen. We now know that the screen
191 // starts on line #6 and ends on line #197 (of the vertical
192 // counter--actual VBLANK proper happens on lines 230 thru 233).
193 vbl = ((i >= 6) && (i <= 197) ? true : false);
196 //WriteLog("*** Frame ran for %d cycles (%.3lf µs, %d samples).\n", mainCPU.clock - oldClock, ((double)(SDL_GetPerformanceCounter() - cpuFrameTickStart) * 1000000.0) / (double)SDL_GetPerformanceFrequency(), sampleCount);
197 // frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
199 Other timings from UTA2E:
200 Power-up reset 32.6 msec / 512 horizontal scans
201 Flash cycle 1.87 Hz / Vertical freq./32
202 Delay before auto repeat 534-801 msec / 32-48 vertical scans
203 Auto repeat frequency 15 Hz / Vertical freq./4
204 Vertical frequency 59.94 Hz (actually, 59.92 Hz [59.92274339401])
205 Horizontal frequency 15,734 Hz (actually, 15700 Hz)
206 1 NTSC frame = 17030 cycles (N.B.: this works out to 1021800 cycles per sec.)
207 NTSC clock frequency ("composite" freq.) is 1.02048432 MHz, which is 14.31818 x (65 / (65 x 14 + 2)) MHz.
209 70 blank lines for top margin, 192 lines for screen, (35 & 35?)
210 VA-C,V0-5 is upcounter starting at 011111010 ($FA) to 111111111 ($1FF)
211 Horizontal counter is upcounter resets to 0000000, then jumps to 1000000 &
212 counts up to 1111111 (bit 7 is Horizontal Preset Enable, which resets the counter when it goes low, the rest are H0-5)
214 pg. 3-24 says one cycle before VBL the counters will be at
215 010111111/1111111 (that doesn't look right to me...)
230 SUMS are calculated like so:
235 ------------------------------
236 SUM-A6 SUM-A5 SUM-A4 SUM-A3
238 In TEXT mode, A10 == (80STORE' * PAGE2)', A11 == 80STORE' * PAGE2
241 In HIRES mode, A13 == (PAGE2 * 80STORE')', A14 == PAGE2 * 80STORE'
242 A10 == VA, A11 == VB, A12 == VC, A15 == 0
244 N.B.: VA-C are lower bits than V5-0
246 HC, from 00, 0 to 23 is the HBL interval, with horizontal retrace occuring between cycles 8 and 11.
247 VC, from line 0-5 and 198-261 is the VBL interval, with vertical retrace occuring between lines 230-233
251 #ifdef THREAD_DEBUGGING
252 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
254 SDL_mutexP(cpuMutex);
255 // increment mainSem...
256 #ifdef THREAD_DEBUGGING
257 WriteLog("CPU: SDL_SemPost(mainSem);\n");
259 SDL_SemPost(mainSem);
260 #ifdef THREAD_DEBUGGING
261 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
263 SDL_CondWait(cpuCond, cpuMutex);
265 #ifdef THREAD_DEBUGGING
266 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
268 SDL_mutexV(cpuMutex);
270 while (!cpuFinished);
272 SDL_DestroyMutex(cpuMutex);
280 // Request a change in the power state of the emulated Apple
282 void SetPowerState(void)
284 powerStateChangeRequested = true;
289 // Load a file into RAM/ROM image space
291 bool LoadImg(char * filename, uint8_t * ram, int size)
293 FILE * fp = fopen(filename, "rb");
298 fread(ram, 1, size, fp);
305 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.2";
306 static void SaveApple2State(const char * filename)
308 WriteLog("Main: Saving Apple2 state...\n");
309 FILE * file = fopen(filename, "wb");
313 WriteLog("Could not open file \"%s\" for writing!\n", filename);
318 fwrite(stateHeader, 1, 18, file);
320 // Write out CPU state
321 fwrite(&mainCPU, 1, sizeof(mainCPU), file);
323 // Write out main memory
324 fwrite(ram, 1, 0x10000, file);
325 fwrite(ram2, 1, 0x10000, file);
327 // Write out state variables
328 fputc((uint8_t)keyDown, file);
329 fputc((uint8_t)openAppleDown, file);
330 fputc((uint8_t)closedAppleDown, file);
331 fputc((uint8_t)store80Mode, file);
332 fputc((uint8_t)vbl, file);
333 fputc((uint8_t)intCXROM, file);
334 fputc((uint8_t)slotC3ROM, file);
335 fputc((uint8_t)intC8ROM, file);
336 fputc((uint8_t)ramrd, file);
337 fputc((uint8_t)ramwrt, file);
338 fputc((uint8_t)altzp, file);
339 fputc((uint8_t)ioudis, file);
340 fputc((uint8_t)dhires, file);
341 fputc((uint8_t)flash, file);
342 fputc((uint8_t)textMode, file);
343 fputc((uint8_t)mixedMode, file);
344 fputc((uint8_t)displayPage2, file);
345 fputc((uint8_t)hiRes, file);
346 fputc((uint8_t)alternateCharset, file);
347 fputc((uint8_t)col80Mode, file);
348 fputc(lcState, file);
350 // Write out floppy state
351 floppyDrive[0].SaveState(file);
353 // Write out Mockingboard state
359 static bool LoadApple2State(const char * filename)
361 WriteLog("Main: Loading Apple2 state...\n");
362 FILE * file = fopen(filename, "rb");
366 WriteLog("Could not open file \"%s\" for reading!\n", filename);
371 fread(buffer, 1, 18, file);
374 if (memcmp(buffer, stateHeader, 18) != 0)
377 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
382 fread(&mainCPU, 1, sizeof(mainCPU), file);
385 fread(ram, 1, 0x10000, file);
386 fread(ram2, 1, 0x10000, file);
388 // Read in state variables
389 keyDown = (bool)fgetc(file);
390 openAppleDown = (bool)fgetc(file);
391 closedAppleDown = (bool)fgetc(file);
392 store80Mode = (bool)fgetc(file);
393 vbl = (bool)fgetc(file);
394 intCXROM = (bool)fgetc(file);
395 slotC3ROM = (bool)fgetc(file);
396 intC8ROM = (bool)fgetc(file);
397 ramrd = (bool)fgetc(file);
398 ramwrt = (bool)fgetc(file);
399 altzp = (bool)fgetc(file);
400 ioudis = (bool)fgetc(file);
401 dhires = (bool)fgetc(file);
402 flash = (bool)fgetc(file);
403 textMode = (bool)fgetc(file);
404 mixedMode = (bool)fgetc(file);
405 displayPage2 = (bool)fgetc(file);
406 hiRes = (bool)fgetc(file);
407 alternateCharset = (bool)fgetc(file);
408 col80Mode = (bool)fgetc(file);
409 lcState = fgetc(file);
411 // Read in floppy state
412 floppyDrive[0].LoadState(file);
414 // Read in Mockingboard state
418 // Make sure things are in a sane state before execution :-P
419 mainCPU.RdMem = AppleReadMem;
420 mainCPU.WrMem = AppleWriteMem;
421 mainCPU.Timer = AppleTimer;
428 static void ResetApple2State(void)
431 openAppleDown = false;
432 closedAppleDown = false;
447 // Without this, you can wedge the system :-/
448 memset(ram, 0, 0x10000);
449 memset(ram2, 0, 0x10000);
450 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
454 static double cyclesForSample = 0;
455 static void AppleTimer(uint16_t cycles)
457 // Handle PHI2 clocked stuff here...
459 floppyDrive[0].RunSequencer(cycles);
463 // 21.26009 cycles per sample @ 48000 (running @ 1,020,484.32 Hz)
464 // 16.688154500083 ms = 1 frame
465 cyclesForSample += (double)cycles;
467 if (cyclesForSample >= 21.26009)
470 sampleClock = mainCPU.clock;
471 WriteLog(" cyclesForSample = %lf (%d samples, cycles=%d)\n", cyclesForSample, sampleClock - lastSampleClock, cycles);
473 lastSampleClock = sampleClock;
475 WriteSampleToBuffer();
476 cyclesForSample -= 21.26009;
482 #ifdef CPU_CLOCK_CHECKING
484 uint32_t totalCPU = 0;
485 uint64_t lastClock = 0;
490 int main(int /*argc*/, char * /*argv*/[])
492 InitLog("./apple2.log");
494 srand(time(NULL)); // Initialize RNG
497 // Make some timing/address tables
499 for(uint32_t line=0; line<262; line++)
501 WriteLog("LINE %03i: ", line);
503 for(uint32_t col=0; col<65; col++)
505 // Convert these to H/V counters
506 uint32_t hcount = col - 1;
508 // HC sees zero twice:
509 if (hcount == 0xFFFFFFFF)
512 uint32_t vcount = line + 0xFA;
514 // Now do the address calculations
515 uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
516 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
517 uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
519 // Add in particulars for the gfx mode we're in...
522 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
523 | (!store80Mode && displayPage2 ? 0x800 : 0);
526 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
527 | (!store80Mode && displayPage2 ? 0x4000 : 0)
528 | ((vcount & 0x07) << 10);
530 WriteLog("$%04X ", address);
539 memset(ram, 0, 0x10000);
540 memset(rom, 0, 0x10000);
541 memset(ram2, 0, 0x10000);
547 // Install devices in slots
548 InstallFloppy(SLOT6);
549 InstallMockingboard(SLOT4);
550 InstallHardDrive(SLOT7);
552 // Set up V65C02 execution context
553 memset(&mainCPU, 0, sizeof(V65C02REGS));
554 mainCPU.RdMem = AppleReadMem;
555 mainCPU.WrMem = AppleWriteMem;
556 mainCPU.Timer = AppleTimer;
557 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
559 if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
561 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
565 WriteLog("About to initialize video...\n");
569 printf("Could not init screen: aborting!\n");
573 GUI::Init(sdlRenderer);
575 WriteLog("About to initialize audio...\n");
578 if (settings.autoStateSaving)
580 // Load last state from file...
581 if (!LoadApple2State(settings.autoStatePath))
582 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
586 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.
588 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.
590 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.
592 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).
594 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.
600 InitializeEventList();
601 // Set frame to fire at 1/60 s interval
602 SetCallbackTime(FrameCallback, 16666.66666667);
603 // Set up blinking at 1/4 s intervals
604 // SetCallbackTime(BlinkTimer, 250000);
605 startTicks = SDL_GetTicks();
607 #ifdef THREADED_65C02
608 // Kick off the CPU...
609 cpuCond = SDL_CreateCond();
610 mainSem = SDL_CreateSemaphore(1);
611 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
612 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
613 // SDL_sem * mainMutex = SDL_CreateMutex();
616 WriteLog("Entering main loop...\n");
620 #ifdef CPU_CLOCK_CHECKING
621 double timeToNextEvent = GetTimeToNextEvent();
623 #ifndef THREADED_65C02
624 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
626 #ifdef CPU_CLOCK_CHECKING
627 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
633 #ifdef THREADED_65C02
634 WriteLog("Main: cpuFinished = true;\n");
637 WriteLog("Main: SDL_SemWait(mainSem);\n");
638 // Only do this if NOT in power off/emulation paused mode!
640 // Should lock until CPU thread is waiting...
641 SDL_SemWait(mainSem);
644 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
645 SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
646 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
647 SDL_WaitThread(cpuThread, NULL);
648 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
649 SDL_DestroyCond(cpuCond);
650 SDL_DestroySemaphore(mainSem);
652 // Autosave state here, if requested...
653 if (settings.autoStateSaving)
654 SaveApple2State(settings.autoStatePath);
656 floppyDrive[0].SaveImage(0);
657 floppyDrive[0].SaveImage(1);
660 #include "dis65c02.h"
661 static char disbuf[80];
665 pc += Decode65C02(&mainCPU, disbuf, pc);
666 WriteLog("%s\n", disbuf);
680 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
681 // +CTRL+SHIFT (3). Order of keys is:
682 // Delete, left, tab, down, up, return, right, escape
683 // Space, single quote, comma, minus, period, slash
685 // Semicolon, equals, left bracket, backslash, right bracket, backquote
686 // Letters a-z (lowercase)
688 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
689 // keyboards, so this table should suffice for the shifted keys just
692 uint8_t apple2e_keycode[4][56] = {
694 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
695 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
696 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
697 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
698 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
699 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
700 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
703 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
704 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
705 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
706 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
707 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
708 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
709 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
712 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
713 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
714 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
715 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
716 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
717 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
718 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
720 { // With CTRL+Shift held
721 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
722 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
723 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
724 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
725 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
726 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
727 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
731 static uint32_t frameCount = 0;
732 static void FrameCallback(void)
737 frameTimePtr = (frameTimePtr + 1) % 60;
738 frameTime[frameTimePtr] = startTicks;
740 while (SDL_PollEvent(&event))
745 // We do our own repeat handling thank you very much! :-)
746 if (event.key.repeat != 0)
749 // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
751 if ((event.key.keysym.mod & KMOD_CTRL)
752 && (event.key.keysym.mod & KMOD_SHIFT)
753 && (event.key.keysym.sym == SDLK_q))
756 // We return here, because we don't want to pick up any
757 // spurious keypresses with our exit sequence.
761 // CTRL+RESET key emulation (mapped to CTRL+HOME)
762 if ((event.key.keysym.mod & KMOD_CTRL)
763 && (event.key.keysym.sym == SDLK_HOME))
765 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
767 // Need to reset the MMU switches as well on RESET
774 // There has GOT to be a better way off mapping SDLKs to our
775 // keyindex. But for now, this should suffice.
778 switch (event.key.keysym.sym)
780 case SDLK_BACKSPACE: keyIndex = 0; break;
781 case SDLK_LEFT: keyIndex = 1; break;
782 case SDLK_TAB: keyIndex = 2; break;
783 case SDLK_DOWN: keyIndex = 3; break;
784 case SDLK_UP: keyIndex = 4; break;
785 case SDLK_RETURN: keyIndex = 5; break;
786 case SDLK_RIGHT: keyIndex = 6; break;
787 case SDLK_ESCAPE: keyIndex = 7; break;
788 case SDLK_SPACE: keyIndex = 8; break;
789 case SDLK_QUOTE: keyIndex = 9; break;
790 case SDLK_COMMA: keyIndex = 10; break;
791 case SDLK_MINUS: keyIndex = 11; break;
792 case SDLK_PERIOD: keyIndex = 12; break;
793 case SDLK_SLASH: keyIndex = 13; break;
794 case SDLK_0: keyIndex = 14; break;
795 case SDLK_1: keyIndex = 15; break;
796 case SDLK_2: keyIndex = 16; break;
797 case SDLK_3: keyIndex = 17; break;
798 case SDLK_4: keyIndex = 18; break;
799 case SDLK_5: keyIndex = 19; break;
800 case SDLK_6: keyIndex = 20; break;
801 case SDLK_7: keyIndex = 21; break;
802 case SDLK_8: keyIndex = 22; break;
803 case SDLK_9: keyIndex = 23; break;
804 case SDLK_SEMICOLON: keyIndex = 24; break;
805 case SDLK_EQUALS: keyIndex = 25; break;
806 case SDLK_LEFTBRACKET: keyIndex = 26; break;
807 case SDLK_BACKSLASH: keyIndex = 27; break;
808 case SDLK_RIGHTBRACKET: keyIndex = 28; break;
809 case SDLK_BACKQUOTE: keyIndex = 29; break;
810 case SDLK_a: keyIndex = 30; break;
811 case SDLK_b: keyIndex = 31; break;
812 case SDLK_c: keyIndex = 32; break;
813 case SDLK_d: keyIndex = 33; break;
814 case SDLK_e: keyIndex = 34; break;
815 case SDLK_f: keyIndex = 35; break;
816 case SDLK_g: keyIndex = 36; break;
817 case SDLK_h: keyIndex = 37; break;
818 case SDLK_i: keyIndex = 38; break;
819 case SDLK_j: keyIndex = 39; break;
820 case SDLK_k: keyIndex = 40; break;
821 case SDLK_l: keyIndex = 41; break;
822 case SDLK_m: keyIndex = 42; break;
823 case SDLK_n: keyIndex = 43; break;
824 case SDLK_o: keyIndex = 44; break;
825 case SDLK_p: keyIndex = 45; break;
826 case SDLK_q: keyIndex = 46; break;
827 case SDLK_r: keyIndex = 47; break;
828 case SDLK_s: keyIndex = 48; break;
829 case SDLK_t: keyIndex = 49; break;
830 case SDLK_u: keyIndex = 50; break;
831 case SDLK_v: keyIndex = 51; break;
832 case SDLK_w: keyIndex = 52; break;
833 case SDLK_x: keyIndex = 53; break;
834 case SDLK_y: keyIndex = 54; break;
835 case SDLK_z: keyIndex = 55; break;
838 // Stuff the key in if we have a valid one...
839 if (keyIndex != 0xFF)
841 // Handle Shift, CTRL, & Shift+CTRL combos
844 if (event.key.keysym.mod & KMOD_CTRL)
847 if (event.key.keysym.mod & KMOD_SHIFT)
850 lastKeyPressed = apple2e_keycode[table][keyIndex];
854 if ((SDL_GetModState() & KMOD_CAPS)
855 && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A))
856 lastKeyPressed -= 0x20;
858 // Handle key repeat if the key hasn't been held
862 // Buffer the key held. Note that the last key is always
863 // stuffed into keysHeld[0].
864 if (keyDownCount >= 2)
866 keysHeld[1] = keysHeld[0];
867 keysHeldAppleCode[1] = keysHeldAppleCode[0];
869 if (keyDownCount > 2)
873 keysHeld[0] = event.key.keysym.sym;
874 keysHeldAppleCode[0] = lastKeyPressed;
878 if (event.key.keysym.sym == SDLK_PAUSE)
880 pauseMode = !pauseMode;
885 SpawnMessage("*** PAUSED ***");
890 SpawnMessage("*** RESUME ***");
894 else if (event.key.keysym.sym == SDLK_LALT)
895 openAppleDown = true;
896 else if (event.key.keysym.sym == SDLK_RALT)
897 closedAppleDown = true;
898 else if (event.key.keysym.sym == SDLK_F2)
900 else if (event.key.keysym.sym == SDLK_F3)
902 else if (event.key.keysym.sym == SDLK_F4)
904 else if (event.key.keysym.sym == SDLK_F5)
907 char volStr[19] = "[****************]";
909 for(int i=GetVolume(); i<16; i++)
912 SpawnMessage("Volume: %s", volStr);
914 else if (event.key.keysym.sym == SDLK_F6)
917 char volStr[19] = "[****************]";
919 for(int i=GetVolume(); i<16; i++)
922 SpawnMessage("Volume: %s", volStr);
924 else if (event.key.keysym.sym == SDLK_F7)
926 // 4th root of 2 is ~1.18920711500272 (~1.5 dB)
927 // This attenuates by ~3 dB
928 VAY_3_8910::maxVolume /= 1.4142135f;
929 SpawnMessage("MB Volume: %d", (int)VAY_3_8910::maxVolume);
931 else if (event.key.keysym.sym == SDLK_F8)
933 VAY_3_8910::maxVolume *= 1.4142135f;
934 SpawnMessage("MB Volume: %d", (int)VAY_3_8910::maxVolume);
936 else if (event.key.keysym.sym == SDLK_F9)
938 floppyDrive[0].CreateBlankImage(1);
939 // SpawnMessage("Image cleared...");
941 /*else if (event.key.keysym.sym == SDLK_F10)
943 floppyDrive[0].SwapImages();
944 // SpawnMessage("Image swapped...");
946 // Toggle the disassembly process
947 else if (event.key.keysym.sym == SDLK_F11)
950 SpawnMessage("Trace: %s", (dumpDis ? "ON" : "off"));
952 else if (event.key.keysym.sym == SDLK_F12)
954 if (!fullscreenDebounce)
957 fullscreenDebounce = true;
964 if (event.key.keysym.sym == SDLK_F12)
965 fullscreenDebounce = false;
966 // Paddle buttons 0 & 1
967 else if (event.key.keysym.sym == SDLK_LALT)
968 openAppleDown = false;
969 else if (event.key.keysym.sym == SDLK_RALT)
970 closedAppleDown = false;
971 else if ((event.key.keysym.mod & KMOD_CTRL)
972 && (event.key.keysym.sym == SDLK_HOME))
973 resetKeyDown = false;
976 // Handle key buffering 'key up' event (2 key rollover)
977 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
980 keyDelay = 0; // Reset key delay
982 else if (keyDownCount == 2)
984 if (event.key.keysym.sym == keysHeld[0])
987 keysHeld[0] = keysHeld[1];
988 keysHeldAppleCode[0] = keysHeldAppleCode[1];
990 else if (event.key.keysym.sym == keysHeld[1])
999 case SDL_MOUSEBUTTONDOWN:
1000 GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
1003 case SDL_MOUSEBUTTONUP:
1004 GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
1007 case SDL_MOUSEMOTION:
1008 GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
1010 // Handle mouse showing when the mouse is hidden...
1011 if (hideMouseTimeout == -1)
1014 hideMouseTimeout = 60;
1017 case SDL_WINDOWEVENT:
1018 if (event.window.event == SDL_WINDOWEVENT_LEAVE)
1019 GUI::MouseMove(0, 0, 0);
1028 // Hide the mouse if it's been 1s since the last time it was moved
1029 // N.B.: Should disable mouse hiding if it's over the GUI...
1030 if ((hideMouseTimeout > 0) && !(GUI::sidebarState == SBS_SHOWN || DiskSelector::showWindow == true))
1032 else if (hideMouseTimeout == 0)
1038 // Stuff the Apple keyboard buffer, if any keys are pending
1039 // N.B.: May have to simulate the key repeat delay too [yup, sure do]
1040 // According to "Understanding the Apple IIe", the initial delay is
1041 // between 32 & 48 jiffies and the repeat is every 4 jiffies.
1042 if (keyDownCount > 0)
1049 lastKeyPressed = keysHeldAppleCode[0];
1054 // Handle power request from the GUI
1055 if (powerStateChangeRequested)
1057 if (GUI::powerOnState)
1060 // Unlock the CPU thread...
1061 SDL_SemPost(mainSem);
1066 // Should lock until CPU thread is waiting...
1067 SDL_SemWait(mainSem);
1071 powerStateChangeRequested = false;
1074 blinkTimer = (blinkTimer + 1) & 0x1F;
1076 if (blinkTimer == 0)
1079 // Render the Apple screen + GUI overlay
1080 RenderAppleScreen(sdlRenderer);
1081 GUI::Render(sdlRenderer);
1082 SDL_RenderPresent(sdlRenderer);
1083 SetCallbackTime(FrameCallback, 16666.66666667);
1085 #ifdef CPU_CLOCK_CHECKING
1086 //We know it's stopped, so we can get away with this...
1090 uint64_t clock = GetCurrentV65C02Clock();
1091 //totalCPU += (uint32_t)(clock - lastClock);
1093 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1100 // This is the problem: If you set the interval to 16, it runs faster than
1101 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1102 // have it do 16 for one frame, then 17 for two others. Then it should average
1103 // out to 1/60s per frame every 3 frames. [And now it does!]
1104 // Maybe we need a higher resolution timer, as the SDL_GetTicks() (in ms) seems
1105 // to jitter all over the place...
1106 frameCount = (frameCount + 1) % 3;
1107 // uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1108 // Get number of ticks burned in this frame, for displaying elsewhere
1110 frameTicks = SDL_GetTicks() - startTicks;
1112 frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
1115 // Wait for next frame...
1116 // while (SDL_GetTicks() - startTicks < waitFrameTime)
1120 startTicks = SDL_GetTicks();
1122 startTicks = SDL_GetPerformanceCounter();
1125 uint64_t cpuCycles = GetCurrentV65C02Clock();
1126 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1127 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1128 lastCPUCycles = cpuCycles
1131 //let's wait, then signal...
1132 //works longer, but then still falls behind... [FIXED, see above]
1133 #ifdef THREADED_65C02
1135 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1140 static void BlinkTimer(void)
1142 // Set up blinking at 1/4 sec intervals
1144 SetCallbackTime(BlinkTimer, 250000);
1149 Next problem is this: How to have events occur and synchronize with the rest
1152 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1153 remainder CPU cycles over...)
1155 One way would be to use a fractional accumulator, then subtract 1 every
1156 time it overflows. Like so:
1158 double overflow = 0;
1162 Execute6808(&soundCPU, time);
1163 overflow += 0.289115646;