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 "floppydisk.h"
52 #include "mockingboard.h"
58 #include "gui/diskselector.h"
60 // Debug and misc. defines
62 #define THREADED_65C02
63 #define CPU_THREAD_OVERFLOW_COMPENSATION
65 //#define CPU_CLOCK_CHECKING
66 //#define THREAD_DEBUGGING
67 #define SOFT_SWITCH_DEBUGGING
71 uint8_t ram[0x10000], rom[0x10000]; // RAM & ROM spaces
72 uint8_t ram2[0x10000]; // Auxillary RAM
73 V65C02REGS mainCPU; // v65C02 execution context
74 uint8_t appleType = APPLE_TYPE_IIE;
75 bool powerStateChangeRequested = false;
76 uint64_t frameCycleStart;
78 uint64_t frameTicks = 0;
79 uint64_t frameTime[60];
80 uint32_t frameTimePtr = 0;
84 uint8_t lastKeyPressed = 0;
86 bool openAppleDown = false;
87 bool closedAppleDown = false;
88 bool store80Mode = false;
90 bool intCXROM = false;
91 bool slotC3ROM = false;
92 bool intC8ROM = false;
98 // Language card state (ROM read, no write)
99 uint8_t lcState = 0x02;
100 uint8_t blinkTimer = 0;
102 static bool running = true; // Machine running state flag...
103 static uint64_t startTicks;
104 static bool pauseMode = false;
105 static bool fullscreenDebounce = false;
106 static bool resetKeyDown = false;
107 static int8_t hideMouseTimeout = 60;
109 // Vars to handle the //e's 2-key rollover
110 static SDL_Keycode keysHeld[2];
111 static uint8_t keysHeldAppleCode[2];
112 static uint8_t keyDownCount = 0;
113 static uint8_t keyDelay = 0;
117 static void SaveApple2State(const char * filename);
118 static bool LoadApple2State(const char * filename);
119 static void ResetApple2State(void);
120 static void AppleTimer(uint16_t);
122 // Local timer callback functions
124 static void FrameCallback(void);
125 static void BlinkTimer(void);
127 #ifdef THREADED_65C02
128 // Test of threaded execution of 6502
129 static SDL_Thread * cpuThread = NULL;
130 static SDL_cond * cpuCond = NULL;
131 static SDL_sem * mainSem = NULL;
132 static bool cpuFinished = false;
134 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
135 // This is a lie. At the end of each 65 cycle line, there is an elongated
136 // cycle (the so-called 'long' cycle) that throws the calcs out of whack.
137 // So actually, it's supposed to be 1,020,484.32 Hz
139 // Let's try a thread...
141 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
144 static uint32_t sampleCount;
145 static uint64_t sampleClock, lastSampleClock;
146 int CPUThreadFunc(void * data)
148 // Mutex must be locked for conditional to work...
149 // Also, must be created in the thread that uses it...
150 SDL_mutex * cpuMutex = SDL_CreateMutex();
152 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
153 // float overflow = 0.0;
158 uint64_t cpuFrameTickStart = SDL_GetPerformanceCounter();
159 uint64_t oldClock = mainCPU.clock;
161 sampleClock = lastSampleClock = mainCPU.clock;
162 // decrement mainSem...
163 #ifdef THREAD_DEBUGGING
164 WriteLog("CPU: SDL_SemWait(mainSem);\n");
166 SDL_SemWait(mainSem);
168 // There are exactly 800 slices of 21.333 cycles per frame, so it works
170 // [Actually, seems it's 786 slices of 21.666 cycles per frame]
172 // Set our frame cycle counter to the correct # of cycles at the start
174 frameCycleStart = mainCPU.clock - mainCPU.overflow;
175 #ifdef THREAD_DEBUGGING
176 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
178 for(int i=0; i<262; i++)
180 // If the CTRL+Reset key combo is being held, make sure the RESET
181 // line stays asserted:
183 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
185 Execute65C02(&mainCPU, 65);
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 proper happens on lines 230 thru 233).
192 vbl = ((i >= 6) && (i <= 197) ? true : false);
195 //WriteLog("*** Frame ran for %d cycles (%.3lf µs, %d samples).\n", mainCPU.clock - oldClock, ((double)(SDL_GetPerformanceCounter() - cpuFrameTickStart) * 1000000.0) / (double)SDL_GetPerformanceFrequency(), sampleCount);
196 // frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
198 Other timings from UTA2E:
199 Power-up reset 32.6 msec / 512 horizontal scans
200 Flash cycle 1.87 Hz / Vertical freq./32
201 Delay before auto repeat 534-801 msec / 32-48 vertical scans
202 Auto repeat frequency 15 Hz / Vertical freq./4
203 Vertical frequency 59.94 Hz (actually, 59.92 Hz [59.92274339401])
204 Horizontal frequency 15,734 Hz (actually, 15700 Hz)
205 1 NTSC frame = 17030 cycles (N.B.: this works out to 1021800 cycles per sec.)
206 NTSC clock frequency ("composite" freq.) is 1.02048432 MHz, which is 14.31818 x (65 / (65 x 14 + 2)) MHz.
208 70 blank lines for top margin, 192 lines for screen, (35 & 35?)
209 VA-C,V0-5 is upcounter starting at 011111010 ($FA) to 111111111 ($1FF)
210 Horizontal counter is upcounter resets to 0000000, then jumps to 1000000 &
211 counts up to 1111111 (bit 7 is Horizontal Preset Enable, which resets the counter when it goes low, the rest are H0-5)
213 pg. 3-24 says one cycle before VBL the counters will be at
214 010111111/1111111 (that doesn't look right to me...)
229 SUMS are calculated like so:
234 ------------------------------
235 SUM-A6 SUM-A5 SUM-A4 SUM-A3
237 In TEXT mode, A10 == (80STORE' * PAGE2)', A11 == 80STORE' * PAGE2
240 In HIRES mode, A13 == (PAGE2 * 80STORE')', A14 == PAGE2 * 80STORE'
241 A10 == VA, A11 == VB, A12 == VC, A15 == 0
243 N.B.: VA-C are lower bits than V5-0
245 HC, from 00, 0 to 23 is the HBL interval, with horizontal retrace occuring between cycles 8 and 11.
246 VC, from line 0-5 and 198-261 is the VBL interval, with vertical retrace occuring between lines 230-233
250 #ifdef THREAD_DEBUGGING
251 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
253 SDL_mutexP(cpuMutex);
254 // increment mainSem...
255 #ifdef THREAD_DEBUGGING
256 WriteLog("CPU: SDL_SemPost(mainSem);\n");
258 SDL_SemPost(mainSem);
259 #ifdef THREAD_DEBUGGING
260 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
262 SDL_CondWait(cpuCond, cpuMutex);
264 #ifdef THREAD_DEBUGGING
265 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
267 SDL_mutexV(cpuMutex);
269 while (!cpuFinished);
271 SDL_DestroyMutex(cpuMutex);
279 // Request a change in the power state of the emulated Apple
281 void SetPowerState(void)
283 powerStateChangeRequested = true;
288 // Load a file into RAM/ROM image space
290 bool LoadImg(char * filename, uint8_t * ram, int size)
292 FILE * fp = fopen(filename, "rb");
297 fread(ram, 1, size, fp);
304 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.2";
305 static void SaveApple2State(const char * filename)
307 WriteLog("Main: Saving Apple2 state...\n");
308 FILE * file = fopen(filename, "wb");
312 WriteLog("Could not open file \"%s\" for writing!\n", filename);
317 fwrite(stateHeader, 1, 18, file);
319 // Write out CPU state
320 fwrite(&mainCPU, 1, sizeof(mainCPU), file);
322 // Write out main memory
323 fwrite(ram, 1, 0x10000, file);
324 fwrite(ram2, 1, 0x10000, file);
326 // Write out state variables
327 fputc((uint8_t)keyDown, file);
328 fputc((uint8_t)openAppleDown, file);
329 fputc((uint8_t)closedAppleDown, file);
330 fputc((uint8_t)store80Mode, file);
331 fputc((uint8_t)vbl, file);
332 fputc((uint8_t)intCXROM, file);
333 fputc((uint8_t)slotC3ROM, file);
334 fputc((uint8_t)intC8ROM, file);
335 fputc((uint8_t)ramrd, file);
336 fputc((uint8_t)ramwrt, file);
337 fputc((uint8_t)altzp, file);
338 fputc((uint8_t)ioudis, file);
339 fputc((uint8_t)dhires, file);
340 fputc((uint8_t)flash, file);
341 fputc((uint8_t)textMode, file);
342 fputc((uint8_t)mixedMode, file);
343 fputc((uint8_t)displayPage2, file);
344 fputc((uint8_t)hiRes, file);
345 fputc((uint8_t)alternateCharset, file);
346 fputc((uint8_t)col80Mode, file);
347 fputc(lcState, file);
349 // Write out floppy state
350 floppyDrive[0].SaveState(file);
352 // Write out Mockingboard state
358 static bool LoadApple2State(const char * filename)
360 WriteLog("Main: Loading Apple2 state...\n");
361 FILE * file = fopen(filename, "rb");
365 WriteLog("Could not open file \"%s\" for reading!\n", filename);
370 fread(buffer, 1, 18, file);
373 if (memcmp(buffer, stateHeader, 18) != 0)
376 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
381 fread(&mainCPU, 1, sizeof(mainCPU), file);
384 fread(ram, 1, 0x10000, file);
385 fread(ram2, 1, 0x10000, file);
387 // Read in state variables
388 keyDown = (bool)fgetc(file);
389 openAppleDown = (bool)fgetc(file);
390 closedAppleDown = (bool)fgetc(file);
391 store80Mode = (bool)fgetc(file);
392 vbl = (bool)fgetc(file);
393 intCXROM = (bool)fgetc(file);
394 slotC3ROM = (bool)fgetc(file);
395 intC8ROM = (bool)fgetc(file);
396 ramrd = (bool)fgetc(file);
397 ramwrt = (bool)fgetc(file);
398 altzp = (bool)fgetc(file);
399 ioudis = (bool)fgetc(file);
400 dhires = (bool)fgetc(file);
401 flash = (bool)fgetc(file);
402 textMode = (bool)fgetc(file);
403 mixedMode = (bool)fgetc(file);
404 displayPage2 = (bool)fgetc(file);
405 hiRes = (bool)fgetc(file);
406 alternateCharset = (bool)fgetc(file);
407 col80Mode = (bool)fgetc(file);
408 lcState = fgetc(file);
410 // Read in floppy state
411 floppyDrive[0].LoadState(file);
413 // Read in Mockingboard state
417 // Make sure things are in a sane state before execution :-P
418 mainCPU.RdMem = AppleReadMem;
419 mainCPU.WrMem = AppleWriteMem;
420 mainCPU.Timer = AppleTimer;
427 static void ResetApple2State(void)
430 openAppleDown = false;
431 closedAppleDown = false;
446 // Without this, you can wedge the system :-/
447 memset(ram, 0, 0x10000);
448 memset(ram2, 0, 0x10000);
449 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
453 static double cyclesForSample = 0;
454 static void AppleTimer(uint16_t cycles)
456 // Handle PHI2 clocked stuff here...
458 floppyDrive[0].RunSequencer(cycles);
462 // 21.26009 cycles per sample @ 48000 (running @ 1,020,484.32 Hz)
463 // 16.688154500083 ms = 1 frame
464 cyclesForSample += (double)cycles;
466 if (cyclesForSample >= 21.26009)
469 sampleClock = mainCPU.clock;
470 WriteLog(" cyclesForSample = %lf (%d samples, cycles=%d)\n", cyclesForSample, sampleClock - lastSampleClock, cycles);
472 lastSampleClock = sampleClock;
474 WriteSampleToBuffer();
475 cyclesForSample -= 21.26009;
481 #ifdef CPU_CLOCK_CHECKING
483 uint32_t totalCPU = 0;
484 uint64_t lastClock = 0;
489 int main(int /*argc*/, char * /*argv*/[])
491 InitLog("./apple2.log");
493 srand(time(NULL)); // Initialize RNG
496 // Make some timing/address tables
498 for(uint32_t line=0; line<262; line++)
500 WriteLog("LINE %03i: ", line);
502 for(uint32_t col=0; col<65; col++)
504 // Convert these to H/V counters
505 uint32_t hcount = col - 1;
507 // HC sees zero twice:
508 if (hcount == 0xFFFFFFFF)
511 uint32_t vcount = line + 0xFA;
513 // Now do the address calculations
514 uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
515 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
516 uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
518 // Add in particulars for the gfx mode we're in...
521 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
522 | (!store80Mode && displayPage2 ? 0x800 : 0);
525 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
526 | (!store80Mode && displayPage2 ? 0x4000 : 0)
527 | ((vcount & 0x07) << 10);
529 WriteLog("$%04X ", address);
538 memset(ram, 0, 0x10000);
539 memset(rom, 0, 0x10000);
540 memset(ram2, 0, 0x10000);
546 // Install devices in slots
547 InstallFloppy(SLOT6);
548 InstallMockingboard(SLOT4);
550 // Set up V65C02 execution context
551 memset(&mainCPU, 0, sizeof(V65C02REGS));
552 mainCPU.RdMem = AppleReadMem;
553 mainCPU.WrMem = AppleWriteMem;
554 mainCPU.Timer = AppleTimer;
555 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
557 if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
559 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
563 WriteLog("About to initialize video...\n");
567 printf("Could not init screen: aborting!\n");
571 GUI::Init(sdlRenderer);
573 WriteLog("About to initialize audio...\n");
576 if (settings.autoStateSaving)
578 // Load last state from file...
579 if (!LoadApple2State(settings.autoStatePath))
580 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
584 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.
586 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.
588 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.
590 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).
592 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.
598 InitializeEventList();
599 // Set frame to fire at 1/60 s interval
600 SetCallbackTime(FrameCallback, 16666.66666667);
601 // Set up blinking at 1/4 s intervals
602 // SetCallbackTime(BlinkTimer, 250000);
603 startTicks = SDL_GetTicks();
605 #ifdef THREADED_65C02
606 // Kick off the CPU...
607 cpuCond = SDL_CreateCond();
608 mainSem = SDL_CreateSemaphore(1);
609 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
610 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
611 // SDL_sem * mainMutex = SDL_CreateMutex();
614 WriteLog("Entering main loop...\n");
618 #ifdef CPU_CLOCK_CHECKING
619 double timeToNextEvent = GetTimeToNextEvent();
621 #ifndef THREADED_65C02
622 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
624 #ifdef CPU_CLOCK_CHECKING
625 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
631 #ifdef THREADED_65C02
632 WriteLog("Main: cpuFinished = true;\n");
635 WriteLog("Main: SDL_SemWait(mainSem);\n");
636 // Only do this if NOT in power off/emulation paused mode!
638 // Should lock until CPU thread is waiting...
639 SDL_SemWait(mainSem);
642 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
643 SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
644 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
645 SDL_WaitThread(cpuThread, NULL);
646 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
647 SDL_DestroyCond(cpuCond);
648 SDL_DestroySemaphore(mainSem);
650 // Autosave state here, if requested...
651 if (settings.autoStateSaving)
652 SaveApple2State(settings.autoStatePath);
654 floppyDrive[0].SaveImage(0);
655 floppyDrive[0].SaveImage(1);
658 #include "dis65c02.h"
659 static char disbuf[80];
663 pc += Decode65C02(&mainCPU, disbuf, pc);
664 WriteLog("%s\n", disbuf);
678 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
679 // +CTRL+SHIFT (3). Order of keys is:
680 // Delete, left, tab, down, up, return, right, escape
681 // Space, single quote, comma, minus, period, slash
683 // Semicolon, equals, left bracket, backslash, right bracket, backquote
684 // Letters a-z (lowercase)
686 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
687 // keyboards, so this table should suffice for the shifted keys just
690 uint8_t apple2e_keycode[4][56] = {
692 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
693 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
694 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
695 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
696 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
697 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
698 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
701 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
702 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
703 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
704 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
705 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
706 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
707 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
710 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
711 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
712 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
713 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
714 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
715 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
716 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
718 { // With CTRL+Shift held
719 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
720 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
721 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
722 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
723 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
724 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
725 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
729 static uint32_t frameCount = 0;
730 static void FrameCallback(void)
735 frameTimePtr = (frameTimePtr + 1) % 60;
736 frameTime[frameTimePtr] = startTicks;
738 while (SDL_PollEvent(&event))
743 // We do our own repeat handling thank you very much! :-)
744 if (event.key.repeat != 0)
747 // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
749 if ((event.key.keysym.mod & KMOD_CTRL)
750 && (event.key.keysym.mod & KMOD_SHIFT)
751 && (event.key.keysym.sym == SDLK_q))
754 // We return here, because we don't want to pick up any
755 // spurious keypresses with our exit sequence.
759 // CTRL+RESET key emulation (mapped to CTRL+HOME)
760 if ((event.key.keysym.mod & KMOD_CTRL)
761 && (event.key.keysym.sym == SDLK_HOME))
763 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
765 // Need to reset the MMU switches as well on RESET
772 // There has GOT to be a better way off mapping SDLKs to our
773 // keyindex. But for now, this should suffice.
776 switch (event.key.keysym.sym)
778 case SDLK_BACKSPACE: keyIndex = 0; break;
779 case SDLK_LEFT: keyIndex = 1; break;
780 case SDLK_TAB: keyIndex = 2; break;
781 case SDLK_DOWN: keyIndex = 3; break;
782 case SDLK_UP: keyIndex = 4; break;
783 case SDLK_RETURN: keyIndex = 5; break;
784 case SDLK_RIGHT: keyIndex = 6; break;
785 case SDLK_ESCAPE: keyIndex = 7; break;
786 case SDLK_SPACE: keyIndex = 8; break;
787 case SDLK_QUOTE: keyIndex = 9; break;
788 case SDLK_COMMA: keyIndex = 10; break;
789 case SDLK_MINUS: keyIndex = 11; break;
790 case SDLK_PERIOD: keyIndex = 12; break;
791 case SDLK_SLASH: keyIndex = 13; break;
792 case SDLK_0: keyIndex = 14; break;
793 case SDLK_1: keyIndex = 15; break;
794 case SDLK_2: keyIndex = 16; break;
795 case SDLK_3: keyIndex = 17; break;
796 case SDLK_4: keyIndex = 18; break;
797 case SDLK_5: keyIndex = 19; break;
798 case SDLK_6: keyIndex = 20; break;
799 case SDLK_7: keyIndex = 21; break;
800 case SDLK_8: keyIndex = 22; break;
801 case SDLK_9: keyIndex = 23; break;
802 case SDLK_SEMICOLON: keyIndex = 24; break;
803 case SDLK_EQUALS: keyIndex = 25; break;
804 case SDLK_LEFTBRACKET: keyIndex = 26; break;
805 case SDLK_BACKSLASH: keyIndex = 27; break;
806 case SDLK_RIGHTBRACKET: keyIndex = 28; break;
807 case SDLK_BACKQUOTE: keyIndex = 29; break;
808 case SDLK_a: keyIndex = 30; break;
809 case SDLK_b: keyIndex = 31; break;
810 case SDLK_c: keyIndex = 32; break;
811 case SDLK_d: keyIndex = 33; break;
812 case SDLK_e: keyIndex = 34; break;
813 case SDLK_f: keyIndex = 35; break;
814 case SDLK_g: keyIndex = 36; break;
815 case SDLK_h: keyIndex = 37; break;
816 case SDLK_i: keyIndex = 38; break;
817 case SDLK_j: keyIndex = 39; break;
818 case SDLK_k: keyIndex = 40; break;
819 case SDLK_l: keyIndex = 41; break;
820 case SDLK_m: keyIndex = 42; break;
821 case SDLK_n: keyIndex = 43; break;
822 case SDLK_o: keyIndex = 44; break;
823 case SDLK_p: keyIndex = 45; break;
824 case SDLK_q: keyIndex = 46; break;
825 case SDLK_r: keyIndex = 47; break;
826 case SDLK_s: keyIndex = 48; break;
827 case SDLK_t: keyIndex = 49; break;
828 case SDLK_u: keyIndex = 50; break;
829 case SDLK_v: keyIndex = 51; break;
830 case SDLK_w: keyIndex = 52; break;
831 case SDLK_x: keyIndex = 53; break;
832 case SDLK_y: keyIndex = 54; break;
833 case SDLK_z: keyIndex = 55; break;
836 // Stuff the key in if we have a valid one...
837 if (keyIndex != 0xFF)
839 // Handle Shift, CTRL, & Shift+CTRL combos
842 if (event.key.keysym.mod & KMOD_CTRL)
845 if (event.key.keysym.mod & KMOD_SHIFT)
848 lastKeyPressed = apple2e_keycode[table][keyIndex];
852 if ((SDL_GetModState() & KMOD_CAPS)
853 && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A))
854 lastKeyPressed -= 0x20;
856 // Handle key repeat if the key hasn't been held
860 // Buffer the key held. Note that the last key is always
861 // stuffed into keysHeld[0].
862 if (keyDownCount >= 2)
864 keysHeld[1] = keysHeld[0];
865 keysHeldAppleCode[1] = keysHeldAppleCode[0];
867 if (keyDownCount > 2)
871 keysHeld[0] = event.key.keysym.sym;
872 keysHeldAppleCode[0] = lastKeyPressed;
876 if (event.key.keysym.sym == SDLK_PAUSE)
878 pauseMode = !pauseMode;
883 SpawnMessage("*** PAUSED ***");
888 SpawnMessage("*** RESUME ***");
892 else if (event.key.keysym.sym == SDLK_LALT)
893 openAppleDown = true;
894 else if (event.key.keysym.sym == SDLK_RALT)
895 closedAppleDown = true;
896 else if (event.key.keysym.sym == SDLK_F2)
898 else if (event.key.keysym.sym == SDLK_F3)
900 else if (event.key.keysym.sym == SDLK_F4)
902 else if (event.key.keysym.sym == SDLK_F5)
905 char volStr[19] = "[****************]";
907 for(int i=GetVolume(); i<16; i++)
910 SpawnMessage("Volume: %s", volStr);
912 else if (event.key.keysym.sym == SDLK_F6)
915 char volStr[19] = "[****************]";
917 for(int i=GetVolume(); i<16; i++)
920 SpawnMessage("Volume: %s", volStr);
922 else if (event.key.keysym.sym == SDLK_F7)
924 // 4th root of 2 is ~1.18920711500272 (~1.5 dB)
925 // This attenuates by ~3 dB
926 VAY_3_8910::maxVolume /= 1.4142135f;
927 SpawnMessage("MB Volume: %d", (int)VAY_3_8910::maxVolume);
929 else if (event.key.keysym.sym == SDLK_F8)
931 VAY_3_8910::maxVolume *= 1.4142135f;
932 SpawnMessage("MB Volume: %d", (int)VAY_3_8910::maxVolume);
934 else if (event.key.keysym.sym == SDLK_F9)
936 floppyDrive[0].CreateBlankImage(1);
937 // SpawnMessage("Image cleared...");
939 /*else if (event.key.keysym.sym == SDLK_F10)
941 floppyDrive[0].SwapImages();
942 // SpawnMessage("Image swapped...");
944 // Toggle the disassembly process
945 else if (event.key.keysym.sym == SDLK_F11)
948 SpawnMessage("Trace: %s", (dumpDis ? "ON" : "off"));
950 else if (event.key.keysym.sym == SDLK_F12)
952 if (!fullscreenDebounce)
955 fullscreenDebounce = true;
962 if (event.key.keysym.sym == SDLK_F12)
963 fullscreenDebounce = false;
964 // Paddle buttons 0 & 1
965 else if (event.key.keysym.sym == SDLK_LALT)
966 openAppleDown = false;
967 else if (event.key.keysym.sym == SDLK_RALT)
968 closedAppleDown = false;
969 else if ((event.key.keysym.mod & KMOD_CTRL)
970 && (event.key.keysym.sym == SDLK_HOME))
971 resetKeyDown = false;
974 // Handle key buffering 'key up' event (2 key rollover)
975 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
978 keyDelay = 0; // Reset key delay
980 else if (keyDownCount == 2)
982 if (event.key.keysym.sym == keysHeld[0])
985 keysHeld[0] = keysHeld[1];
986 keysHeldAppleCode[0] = keysHeldAppleCode[1];
988 else if (event.key.keysym.sym == keysHeld[1])
997 case SDL_MOUSEBUTTONDOWN:
998 GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
1001 case SDL_MOUSEBUTTONUP:
1002 GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
1005 case SDL_MOUSEMOTION:
1006 GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
1008 // Handle mouse showing when the mouse is hidden...
1009 if (hideMouseTimeout == -1)
1012 hideMouseTimeout = 60;
1015 case SDL_WINDOWEVENT:
1016 if (event.window.event == SDL_WINDOWEVENT_LEAVE)
1017 GUI::MouseMove(0, 0, 0);
1026 // Hide the mouse if it's been 1s since the last time it was moved
1027 // N.B.: Should disable mouse hiding if it's over the GUI...
1028 if ((hideMouseTimeout > 0) && !(GUI::sidebarState == SBS_SHOWN || DiskSelector::showWindow == true))
1030 else if (hideMouseTimeout == 0)
1036 // Stuff the Apple keyboard buffer, if any keys are pending
1037 // N.B.: May have to simulate the key repeat delay too [yup, sure do]
1038 // According to "Understanding the Apple IIe", the initial delay is
1039 // between 32 & 48 jiffies and the repeat is every 4 jiffies.
1040 if (keyDownCount > 0)
1047 lastKeyPressed = keysHeldAppleCode[0];
1052 // Handle power request from the GUI
1053 if (powerStateChangeRequested)
1055 if (GUI::powerOnState)
1058 // Unlock the CPU thread...
1059 SDL_SemPost(mainSem);
1064 // Should lock until CPU thread is waiting...
1065 SDL_SemWait(mainSem);
1069 powerStateChangeRequested = false;
1072 blinkTimer = (blinkTimer + 1) & 0x1F;
1074 if (blinkTimer == 0)
1077 // Render the Apple screen + GUI overlay
1078 RenderAppleScreen(sdlRenderer);
1079 GUI::Render(sdlRenderer);
1080 SDL_RenderPresent(sdlRenderer);
1081 SetCallbackTime(FrameCallback, 16666.66666667);
1083 #ifdef CPU_CLOCK_CHECKING
1084 //We know it's stopped, so we can get away with this...
1088 uint64_t clock = GetCurrentV65C02Clock();
1089 //totalCPU += (uint32_t)(clock - lastClock);
1091 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1098 // This is the problem: If you set the interval to 16, it runs faster than
1099 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1100 // have it do 16 for one frame, then 17 for two others. Then it should average
1101 // out to 1/60s per frame every 3 frames. [And now it does!]
1102 // Maybe we need a higher resolution timer, as the SDL_GetTicks() (in ms) seems
1103 // to jitter all over the place...
1104 frameCount = (frameCount + 1) % 3;
1105 // uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1106 // Get number of ticks burned in this frame, for displaying elsewhere
1108 frameTicks = SDL_GetTicks() - startTicks;
1110 frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
1113 // Wait for next frame...
1114 // while (SDL_GetTicks() - startTicks < waitFrameTime)
1118 startTicks = SDL_GetTicks();
1120 startTicks = SDL_GetPerformanceCounter();
1123 uint64_t cpuCycles = GetCurrentV65C02Clock();
1124 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1125 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1126 lastCPUCycles = cpuCycles
1129 //let's wait, then signal...
1130 //works longer, but then still falls behind... [FIXED, see above]
1131 #ifdef THREADED_65C02
1133 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1138 static void BlinkTimer(void)
1140 // Set up blinking at 1/4 sec intervals
1142 SetCallbackTime(BlinkTimer, 250000);
1147 Next problem is this: How to have events occur and synchronize with the rest
1150 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1151 remainder CPU cycles over...)
1153 One way would be to use a fractional accumulator, then subtract 1 every
1154 time it overflows. Like so:
1156 double overflow = 0;
1160 Execute6808(&soundCPU, time);
1161 overflow += 0.289115646;