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
52 #include "mos6522via.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 FloppyDrive floppyDrive;
77 bool powerStateChangeRequested = false;
78 uint64_t frameCycleStart;
81 uint32_t frameTicks = 0;
82 uint32_t frameTime[60];
84 uint64_t frameTicks = 0;
85 uint64_t frameTime[60];
87 uint32_t frameTimePtr = 0;
91 uint8_t lastKeyPressed = 0;
93 bool openAppleDown = false;
94 bool closedAppleDown = false;
95 bool store80Mode = false;
97 bool slotCXROM = false;
98 bool slotC3ROM = false;
104 // Language card state (ROM read, no write)
105 uint8_t lcState = 0x02;
107 static bool running = true; // Machine running state flag...
109 static uint32_t startTicks;
111 static uint64_t startTicks;
113 static bool pauseMode = false;
114 static bool fullscreenDebounce = false;
115 //static bool capsLock = false;
116 //static bool capsLockDebounce = false;
117 static bool resetKeyDown = false;
118 static int8_t hideMouseTimeout = 60;
120 // Vars to handle the //e's 2-key rollover
121 static SDL_Keycode keysHeld[2];
122 static uint8_t keysHeldAppleCode[2];
123 static uint8_t keyDownCount = 0;
124 static uint8_t keyDelay = 0;
128 static void SaveApple2State(const char * filename);
129 static bool LoadApple2State(const char * filename);
130 static void ResetApple2State(void);
131 static void AppleTimer(uint16_t);
133 // Local timer callback functions
135 static void FrameCallback(void);
136 static void BlinkTimer(void);
138 #ifdef THREADED_65C02
139 // Test of threaded execution of 6502
140 static SDL_Thread * cpuThread = NULL;
141 static SDL_cond * cpuCond = NULL;
142 static SDL_sem * mainSem = NULL;
143 static bool cpuFinished = false;
145 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
146 // This is a lie. At the end of each 65 cycle line, there is an elongated
147 // cycle (the so-called 'long' cycle) that throws the calcs out of whack.
148 // So actually, it's supposed to be 1,020,484.32 Hz
150 // Let's try a thread...
152 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
155 static uint32_t sampleCount;
156 static uint64_t sampleClock, lastSampleClock;
157 int CPUThreadFunc(void * data)
159 // Mutex must be locked for conditional to work...
160 // Also, must be created in the thread that uses it...
161 SDL_mutex * cpuMutex = SDL_CreateMutex();
163 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
164 // float overflow = 0.0;
169 uint64_t cpuFrameTickStart = SDL_GetPerformanceCounter();
170 uint64_t oldClock = mainCPU.clock;
172 sampleClock = lastSampleClock = mainCPU.clock;
173 // decrement mainSem...
174 #ifdef THREAD_DEBUGGING
175 WriteLog("CPU: SDL_SemWait(mainSem);\n");
177 SDL_SemWait(mainSem);
179 // There are exactly 800 slices of 21.333 cycles per frame, so it works
181 // [Actually, seems it's 786 slices of 21.666 cycles per frame]
183 // Set our frame cycle counter to the correct # of cycles at the start
185 frameCycleStart = mainCPU.clock - mainCPU.overflow;
186 #ifdef THREAD_DEBUGGING
187 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
189 // for(int i=0; i<786; i++)
190 for(int i=0; i<262; i++)
192 // uint32_t cycles = 21;
193 // overflow += 0.666666667;
195 // if (overflow > 1.0)
201 // If the CTRL+Reset key combo is being held, make sure the RESET
202 // line stays asserted:
204 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
206 // Execute65C02(&mainCPU, cycles);
207 Execute65C02(&mainCPU, 65);
208 // WriteSampleToBuffer();
210 // According to "Understanding The Apple IIe", VBL asserted after
211 // the last byte of the screen is read and let go on the first read
212 // of the first byte of the screen. We now know that the screen
213 // starts on line #6 and ends on line #197 (of the vertical
214 // counter--actual VBLANK happens on lines 230 thru 233).
215 // vbl = ((i > 17) && (i < 592) ? true : false);
216 vbl = ((i >= 6) && (i <= 197) ? true : false);
219 WriteLog("*** Frame ran for %d cycles (%.3lf µs, %d samples).\n", mainCPU.clock - oldClock, ((double)(SDL_GetPerformanceCounter() - cpuFrameTickStart) * 1000000.0) / (double)SDL_GetPerformanceFrequency(), sampleCount);
220 // frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
222 Other timings from UTA2E:
223 Power-up reset 32.6 msec / 512 horizontal scans
224 Flash cycle 1.87 Hz / Vertical freq./32
225 Delay before auto repeat 534-801 msec / 32-48 vertical scans
226 Auto repeat frequency 15 Hz / Vertical freq./4
227 Vertical frequency 59.94 Hz (actually, 59.92 Hz [59.92274339401])
228 Horizontal frequency 15,734 Hz (actually, 15700 Hz)
229 1 NTSC frame = 17030 cycles (N.B.: this works out to 1021800 cycles per sec.)
230 NTSC clock frequency ("composite" freq.) is 1.02048432 MHz, which is 14.31818 x (65 / (65 x 14 + 2)) MHz.
232 70 blank lines for top margin, 192 lines for screen, (35 & 35?)
233 VA-C,V0-5 is upcounter starting at 011111010 ($FA) to 111111111 ($1FF)
234 Horizontal counter is upcounter resets to 0000000, then jumps to 1000000 &
235 counts up to 1111111 (bit 7 is Horizontal Preset Enable, which resets the counter when it goes low, the rest are H0-5)
237 pg. 3-24 says one cycle before VBL the counters will be at
238 010111111/1111111 (that doesn't look right to me...)
253 SUMS are calculated like so:
258 ------------------------------
259 SUM-A6 SUM-A5 SUM-A4 SUM-A3
261 In TEXT mode, A10 == (80STORE' * PAGE2)', A11 == 80STORE' * PAGE2
264 In HIRES mode, A13 == (PAGE2 * 80STORE')', A14 == PAGE2 * 80STORE'
265 A10 == VA, A11 == VB, A12 == VC, A15 == 0
267 N.B.: VA-C are lower bits than V5-0
269 HC, from 00, 0 to 23 is the HBL interval, with horizontal retrace occuring between cycles 8 and 11.
270 VC, from line 0-5 and 198-261 is the VBL interval, with vertical retrace occuring between lines 230-233
274 #ifdef THREAD_DEBUGGING
275 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
277 SDL_mutexP(cpuMutex);
278 // increment mainSem...
279 #ifdef THREAD_DEBUGGING
280 WriteLog("CPU: SDL_SemPost(mainSem);\n");
282 SDL_SemPost(mainSem);
283 #ifdef THREAD_DEBUGGING
284 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
286 SDL_CondWait(cpuCond, cpuMutex);
288 #ifdef THREAD_DEBUGGING
289 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
291 SDL_mutexV(cpuMutex);
293 while (!cpuFinished);
295 SDL_DestroyMutex(cpuMutex);
303 // Request a change in the power state of the emulated Apple
305 void SetPowerState(void)
307 powerStateChangeRequested = true;
312 // Load a file into RAM/ROM image space
314 bool LoadImg(char * filename, uint8_t * ram, int size)
316 FILE * fp = fopen(filename, "rb");
321 fread(ram, 1, size, fp);
328 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.1";
329 static void SaveApple2State(const char * filename)
331 WriteLog("Main: Saving Apple2 state...\n");
332 FILE * file = fopen(filename, "wb");
336 WriteLog("Could not open file \"%s\" for writing!\n", filename);
341 fwrite(stateHeader, 1, 18, file);
343 // Write out CPU state
344 fwrite(&mainCPU, 1, sizeof(mainCPU), file);
346 // Write out main memory
347 fwrite(ram, 1, 0x10000, file);
348 fwrite(ram2, 1, 0x10000, file);
350 // Write out state variables
351 fputc((uint8_t)keyDown, file);
352 fputc((uint8_t)openAppleDown, file);
353 fputc((uint8_t)closedAppleDown, file);
354 fputc((uint8_t)store80Mode, file);
355 fputc((uint8_t)vbl, file);
356 fputc((uint8_t)slotCXROM, file);
357 fputc((uint8_t)slotC3ROM, file);
358 fputc((uint8_t)ramrd, file);
359 fputc((uint8_t)ramwrt, file);
360 fputc((uint8_t)altzp, file);
361 fputc((uint8_t)ioudis, file);
362 fputc((uint8_t)dhires, file);
363 fputc((uint8_t)flash, file);
364 fputc((uint8_t)textMode, file);
365 fputc((uint8_t)mixedMode, file);
366 fputc((uint8_t)displayPage2, file);
367 fputc((uint8_t)hiRes, file);
368 fputc((uint8_t)alternateCharset, file);
369 fputc((uint8_t)col80Mode, file);
370 fputc(lcState, file);
372 // Write out floppy state
373 floppyDrive.SaveState(file);
378 static bool LoadApple2State(const char * filename)
380 WriteLog("Main: Loading Apple2 state...\n");
381 FILE * file = fopen(filename, "rb");
385 WriteLog("Could not open file \"%s\" for reading!\n", filename);
390 fread(buffer, 1, 18, file);
393 if (memcmp(buffer, stateHeader, 18) != 0)
396 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
401 fread(&mainCPU, 1, sizeof(mainCPU), file);
404 fread(ram, 1, 0x10000, file);
405 fread(ram2, 1, 0x10000, file);
407 // Read in state variables
408 keyDown = (bool)fgetc(file);
409 openAppleDown = (bool)fgetc(file);
410 closedAppleDown = (bool)fgetc(file);
411 store80Mode = (bool)fgetc(file);
412 vbl = (bool)fgetc(file);
413 slotCXROM = (bool)fgetc(file);
414 slotC3ROM = (bool)fgetc(file);
415 ramrd = (bool)fgetc(file);
416 ramwrt = (bool)fgetc(file);
417 altzp = (bool)fgetc(file);
418 ioudis = (bool)fgetc(file);
419 dhires = (bool)fgetc(file);
420 flash = (bool)fgetc(file);
421 textMode = (bool)fgetc(file);
422 mixedMode = (bool)fgetc(file);
423 displayPage2 = (bool)fgetc(file);
424 hiRes = (bool)fgetc(file);
425 alternateCharset = (bool)fgetc(file);
426 col80Mode = (bool)fgetc(file);
427 lcState = fgetc(file);
429 // Read in floppy state
430 floppyDrive.LoadState(file);
434 // Make sure things are in a sane state before execution :-P
435 mainCPU.RdMem = AppleReadMem;
436 mainCPU.WrMem = AppleWriteMem;
437 mainCPU.Timer = AppleTimer;
444 static void ResetApple2State(void)
447 openAppleDown = false;
448 closedAppleDown = false;
461 #ifdef USE_NEW_AY8910
469 // Without this, you can wedge the system :-/
470 memset(ram, 0, 0x10000);
471 memset(ram2, 0, 0x10000);
472 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
476 static double cyclesForSample = 0;
477 static void AppleTimer(uint16_t cycles)
479 // Handle PHI2 clocked stuff here...
480 bool via1T1HitZero = (mbvia[0].timer1counter <= cycles ? true : false);
481 bool via2T1HitZero = (mbvia[1].timer1counter <= cycles ? true : false);
483 mbvia[0].timer1counter -= cycles;
484 mbvia[0].timer2counter -= cycles;
485 mbvia[1].timer1counter -= cycles;
486 mbvia[1].timer2counter -= cycles;
490 if (mbvia[0].acr & 0x40)
492 mbvia[0].timer1counter += mbvia[0].timer1latch;
494 if (mbvia[0].ier & 0x40)
496 mbvia[0].ifr |= (0x80 | 0x40);
497 AssertLine(V65C02_ASSERT_LINE_IRQ);
502 mbvia[0].ier &= 0x3F; // Disable T1 interrupt (VIA #1)
508 if (mbvia[1].acr & 0x40)
510 mbvia[1].timer1counter += mbvia[1].timer1latch;
512 if (mbvia[1].ier & 0x40)
514 mbvia[1].ifr |= (0x80 | 0x40);
515 AssertLine(V65C02_ASSERT_LINE_NMI);
520 mbvia[1].ier &= 0x3F; // Disable T1 interrupt (VIA #2)
526 // 21.26009 cycles per sample @ 48000 (running @ 1,020,484.32 Hz)
527 // Noooooope. We need ~801 cycles per frame. Averaging about 786, so missing 15 or so.
528 // 16.688154500083 ms = 1 frame
529 cyclesForSample += (double)cycles;
531 if (cyclesForSample >= 21.26009)
534 sampleClock = GetCurrentV65C02Clock();
535 WriteLog(" cyclesForSample = %lf (%d samples, cycles=%d)\n", cyclesForSample, sampleClock - lastSampleClock, cycles);
537 lastSampleClock = sampleClock;
539 WriteSampleToBuffer();
540 cyclesForSample -= 21.26009;
546 #ifdef CPU_CLOCK_CHECKING
548 uint32_t totalCPU = 0;
549 uint64_t lastClock = 0;
554 int main(int /*argc*/, char * /*argv*/[])
556 InitLog("./apple2.log");
558 srand(time(NULL)); // Initialize RNG
561 // Make some timing/address tables
563 for(uint32_t line=0; line<262; line++)
565 WriteLog("LINE %03i: ", line);
567 for(uint32_t col=0; col<65; col++)
569 // Convert these to H/V counters
570 uint32_t hcount = col - 1;
572 // HC sees zero twice:
573 if (hcount == 0xFFFFFFFF)
576 uint32_t vcount = line + 0xFA;
578 // Now do the address calculations
579 uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
580 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
581 uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
583 // Add in particulars for the gfx mode we're in...
586 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
587 | (!store80Mode && displayPage2 ? 0x800 : 0);
590 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
591 | (!store80Mode && displayPage2 ? 0x4000 : 0)
592 | ((vcount & 0x07) << 10);
594 WriteLog("$%04X ", address);
603 memset(ram, 0, 0x10000);
604 memset(rom, 0, 0x10000);
605 memset(ram2, 0, 0x10000);
611 // Set up Mockingboard
612 memset(&mbvia[0], 0, sizeof(MOS6522VIA));
613 memset(&mbvia[1], 0, sizeof(MOS6522VIA));
614 //(at some point this shite will have to go into the state file...)
615 #ifdef USE_NEW_AY8910
618 AY8910_InitAll(1020484, 48000);
621 // Set up V65C02 execution context
622 memset(&mainCPU, 0, sizeof(V65C02REGS));
623 mainCPU.RdMem = AppleReadMem;
624 mainCPU.WrMem = AppleWriteMem;
625 mainCPU.Timer = AppleTimer;
626 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
628 if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
630 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
634 WriteLog("About to initialize video...\n");
638 printf("Could not init screen: aborting!\n");
642 GUI::Init(sdlRenderer);
644 WriteLog("About to initialize audio...\n");
647 if (settings.autoStateSaving)
649 // Load last state from file...
650 if (!LoadApple2State(settings.autoStatePath))
651 WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
655 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.
657 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.
659 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.
661 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).
663 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.
669 InitializeEventList();
670 // Set frame to fire at 1/60 s interval
671 SetCallbackTime(FrameCallback, 16666.66666667);
672 // Set up blinking at 1/4 s intervals
673 SetCallbackTime(BlinkTimer, 250000);
674 startTicks = SDL_GetTicks();
676 #ifdef THREADED_65C02
677 // Kick off the CPU...
678 cpuCond = SDL_CreateCond();
679 mainSem = SDL_CreateSemaphore(1);
680 cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
681 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
682 // SDL_sem * mainMutex = SDL_CreateMutex();
685 WriteLog("Entering main loop...\n");
689 #ifdef CPU_CLOCK_CHECKING
690 double timeToNextEvent = GetTimeToNextEvent();
692 #ifndef THREADED_65C02
693 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
695 #ifdef CPU_CLOCK_CHECKING
696 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
702 #ifdef THREADED_65C02
703 WriteLog("Main: cpuFinished = true;\n");
706 WriteLog("Main: SDL_SemWait(mainSem);\n");
707 // Only do this if NOT in power off/emulation paused mode!
709 // Should lock until CPU thread is waiting...
710 SDL_SemWait(mainSem);
713 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
714 SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
715 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
716 SDL_WaitThread(cpuThread, NULL);
717 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
718 SDL_DestroyCond(cpuCond);
719 SDL_DestroySemaphore(mainSem);
721 // Autosave state here, if requested...
722 if (settings.autoStateSaving)
723 SaveApple2State(settings.autoStatePath);
725 floppyDrive.SaveImage(0);
726 floppyDrive.SaveImage(1);
738 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
739 // +CTRL+SHIFT (3). Order of keys is:
740 // Delete, left, tab, down, up, return, right, escape
741 // Space, single quote, comma, minus, period, slash
743 // Semicolon, equals, left bracket, backslash, right bracket, backquote
744 // Letters a-z (lowercase)
746 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
747 // keyboards, so this table should suffice for the shifted keys just
750 uint8_t apple2e_keycode[4][56] = {
752 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
753 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
754 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
755 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
756 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
757 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
758 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
761 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
762 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
763 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
764 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
765 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
766 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
767 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
770 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
771 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
772 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
773 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
774 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
775 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
776 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
778 { // With CTRL+Shift held
779 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
780 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
781 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
782 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
783 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
784 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
785 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
789 static uint32_t frameCount = 0;
790 static void FrameCallback(void)
795 frameTimePtr = (frameTimePtr + 1) % 60;
796 frameTime[frameTimePtr] = startTicks;
798 while (SDL_PollEvent(&event))
803 // We do our own repeat handling thank you very much! :-)
804 if (event.key.repeat != 0)
807 // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
809 if ((event.key.keysym.mod & KMOD_CTRL)
810 && (event.key.keysym.mod & KMOD_SHIFT)
811 && (event.key.keysym.sym == SDLK_q))
814 // We return here, because we don't want to pick up any
815 // spurious keypresses with our exit sequence.
819 // CTRL+RESET key emulation (mapped to CTRL+HOME)
820 if ((event.key.keysym.mod & KMOD_CTRL)
821 && (event.key.keysym.sym == SDLK_HOME))
823 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
828 // There has GOT to be a better way off mapping SDLKs to our
829 // keyindex. But for now, this should suffice.
832 switch (event.key.keysym.sym)
834 case SDLK_BACKSPACE: keyIndex = 0; break;
835 case SDLK_LEFT: keyIndex = 1; break;
836 case SDLK_TAB: keyIndex = 2; break;
837 case SDLK_DOWN: keyIndex = 3; break;
838 case SDLK_UP: keyIndex = 4; break;
839 case SDLK_RETURN: keyIndex = 5; break;
840 case SDLK_RIGHT: keyIndex = 6; break;
841 case SDLK_ESCAPE: keyIndex = 7; break;
842 case SDLK_SPACE: keyIndex = 8; break;
843 case SDLK_QUOTE: keyIndex = 9; break;
844 case SDLK_COMMA: keyIndex = 10; break;
845 case SDLK_MINUS: keyIndex = 11; break;
846 case SDLK_PERIOD: keyIndex = 12; break;
847 case SDLK_SLASH: keyIndex = 13; break;
848 case SDLK_0: keyIndex = 14; break;
849 case SDLK_1: keyIndex = 15; break;
850 case SDLK_2: keyIndex = 16; break;
851 case SDLK_3: keyIndex = 17; break;
852 case SDLK_4: keyIndex = 18; break;
853 case SDLK_5: keyIndex = 19; break;
854 case SDLK_6: keyIndex = 20; break;
855 case SDLK_7: keyIndex = 21; break;
856 case SDLK_8: keyIndex = 22; break;
857 case SDLK_9: keyIndex = 23; break;
858 case SDLK_SEMICOLON: keyIndex = 24; break;
859 case SDLK_EQUALS: keyIndex = 25; break;
860 case SDLK_LEFTBRACKET: keyIndex = 26; break;
861 case SDLK_BACKSLASH: keyIndex = 27; break;
862 case SDLK_RIGHTBRACKET: keyIndex = 28; break;
863 case SDLK_BACKQUOTE: keyIndex = 29; break;
864 case SDLK_a: keyIndex = 30; break;
865 case SDLK_b: keyIndex = 31; break;
866 case SDLK_c: keyIndex = 32; break;
867 case SDLK_d: keyIndex = 33; break;
868 case SDLK_e: keyIndex = 34; break;
869 case SDLK_f: keyIndex = 35; break;
870 case SDLK_g: keyIndex = 36; break;
871 case SDLK_h: keyIndex = 37; break;
872 case SDLK_i: keyIndex = 38; break;
873 case SDLK_j: keyIndex = 39; break;
874 case SDLK_k: keyIndex = 40; break;
875 case SDLK_l: keyIndex = 41; break;
876 case SDLK_m: keyIndex = 42; break;
877 case SDLK_n: keyIndex = 43; break;
878 case SDLK_o: keyIndex = 44; break;
879 case SDLK_p: keyIndex = 45; break;
880 case SDLK_q: keyIndex = 46; break;
881 case SDLK_r: keyIndex = 47; break;
882 case SDLK_s: keyIndex = 48; break;
883 case SDLK_t: keyIndex = 49; break;
884 case SDLK_u: keyIndex = 50; break;
885 case SDLK_v: keyIndex = 51; break;
886 case SDLK_w: keyIndex = 52; break;
887 case SDLK_x: keyIndex = 53; break;
888 case SDLK_y: keyIndex = 54; break;
889 case SDLK_z: keyIndex = 55; break;
892 // Stuff the key in if we have a valid one...
893 if (keyIndex != 0xFF)
895 // Handle Shift, CTRL, & Shift+CTRL combos
898 if (event.key.keysym.mod & KMOD_CTRL)
901 if (event.key.keysym.mod & KMOD_SHIFT)
904 lastKeyPressed = apple2e_keycode[table][keyIndex];
908 if ((SDL_GetModState() & KMOD_CAPS)
909 && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A))
910 lastKeyPressed -= 0x20;
912 // Handle key repeat if the key hasn't been held
913 // if (keyDelay == 0)
918 // Buffer the key held. Note that the last key is always
919 // stuffed into keysHeld[0].
920 if (keyDownCount >= 2)
922 keysHeld[1] = keysHeld[0];
923 keysHeldAppleCode[1] = keysHeldAppleCode[0];
925 if (keyDownCount > 2)
929 keysHeld[0] = event.key.keysym.sym;
930 keysHeldAppleCode[0] = lastKeyPressed;
934 if (event.key.keysym.sym == SDLK_PAUSE)
936 pauseMode = !pauseMode;
941 SpawnMessage("*** PAUSED ***");
946 SpawnMessage("*** RESUME ***");
950 else if (event.key.keysym.sym == SDLK_LALT)
951 openAppleDown = true;
952 else if (event.key.keysym.sym == SDLK_RALT)
953 closedAppleDown = true;
954 // Toggle the disassembly process
955 else if (event.key.keysym.sym == SDLK_F11)
958 SpawnMessage("Trace: %s", (dumpDis ? "ON" : "off"));
960 else if (event.key.keysym.sym == SDLK_F12)
962 logAYInternal = !logAYInternal;
963 SpawnMessage("AY Trace: %s", (logAYInternal ? "ON" : "off"));
966 /*else if (event.key.keysym.sym == SDLK_F9)
968 floppyDrive.CreateBlankImage(0);
969 // SpawnMessage("Image cleared...");
971 /*else if (event.key.keysym.sym == SDLK_F10)
973 floppyDrive.SwapImages();
974 // SpawnMessage("Image swapped...");
977 else if (event.key.keysym.sym == SDLK_F2)
979 else if (event.key.keysym.sym == SDLK_F3)
981 else if (event.key.keysym.sym == SDLK_F4)
983 else if (event.key.keysym.sym == SDLK_F5)
986 char volStr[19] = "[****************]";
988 for(int i=GetVolume(); i<16; i++)
991 SpawnMessage("Volume: %s", volStr);
993 else if (event.key.keysym.sym == SDLK_F6)
996 char volStr[19] = "[****************]";
998 for(int i=GetVolume(); i<16; i++)
1001 SpawnMessage("Volume: %s", volStr);
1003 else if (event.key.keysym.sym == SDLK_F7)
1005 // 4th root of 2 is ~1.18920711500272 (~1.5 dB)
1006 maxVolume /= 1.4142135f; // This attenuates by ~3 dB
1007 SpawnMessage("MB Volume: %d", (int)maxVolume);
1009 else if (event.key.keysym.sym == SDLK_F8)
1011 maxVolume *= 1.4142135f;
1012 SpawnMessage("MB Volume: %d", (int)maxVolume);
1014 else if (event.key.keysym.sym == SDLK_F12)
1016 if (!fullscreenDebounce)
1019 fullscreenDebounce = true;
1026 if (event.key.keysym.sym == SDLK_F12)
1027 fullscreenDebounce = false;
1028 // Paddle buttons 0 & 1
1029 else if (event.key.keysym.sym == SDLK_LALT)
1030 openAppleDown = false;
1031 else if (event.key.keysym.sym == SDLK_RALT)
1032 closedAppleDown = false;
1033 else if ((event.key.keysym.mod & KMOD_CTRL)
1034 && (event.key.keysym.sym == SDLK_HOME))
1035 resetKeyDown = false;
1038 // Handle key buffering 'key up' event (2 key rollover)
1039 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
1042 keyDelay = 0; // Reset key delay
1044 else if (keyDownCount == 2)
1046 if (event.key.keysym.sym == keysHeld[0])
1049 keysHeld[0] = keysHeld[1];
1050 keysHeldAppleCode[0] = keysHeldAppleCode[1];
1052 else if (event.key.keysym.sym == keysHeld[1])
1061 case SDL_MOUSEBUTTONDOWN:
1062 GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
1065 case SDL_MOUSEBUTTONUP:
1066 GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
1069 case SDL_MOUSEMOTION:
1070 GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
1072 // Handle mouse showing when the mouse is hidden...
1073 if (hideMouseTimeout == -1)
1076 hideMouseTimeout = 60;
1079 case SDL_WINDOWEVENT:
1080 if (event.window.event == SDL_WINDOWEVENT_LEAVE)
1081 GUI::MouseMove(0, 0, 0);
1090 // Hide the mouse if it's been 1s since the last time it was moved
1091 // N.B.: Should disable mouse hiding if it's over the GUI...
1092 if ((hideMouseTimeout > 0) && !(GUI::sidebarState == SBS_SHOWN || DiskSelector::showWindow == true))
1094 else if (hideMouseTimeout == 0)
1100 // Stuff the Apple keyboard buffer, if any keys are pending
1101 // N.B.: May have to simulate the key repeat delay too [yup, sure do]
1102 // According to "Understanding the Apple IIe", the initial delay is
1103 // between 32 & 48 jiffies and the repeat is every 4 jiffies.
1104 if (keyDownCount > 0)
1111 lastKeyPressed = keysHeldAppleCode[0];
1116 // Handle power request from the GUI
1117 if (powerStateChangeRequested)
1119 if (GUI::powerOnState)
1122 // Unlock the CPU thread...
1123 SDL_SemPost(mainSem);
1128 // Should lock until CPU thread is waiting...
1129 SDL_SemWait(mainSem);
1133 powerStateChangeRequested = false;
1136 // Render the Apple screen + GUI overlay
1137 RenderAppleScreen(sdlRenderer);
1138 GUI::Render(sdlRenderer);
1139 SDL_RenderPresent(sdlRenderer);
1140 SetCallbackTime(FrameCallback, 16666.66666667);
1142 #ifdef CPU_CLOCK_CHECKING
1143 //We know it's stopped, so we can get away with this...
1147 uint64_t clock = GetCurrentV65C02Clock();
1148 //totalCPU += (uint32_t)(clock - lastClock);
1150 printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1157 // This is the problem: If you set the interval to 16, it runs faster than
1158 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1159 // have it do 16 for one frame, then 17 for two others. Then it should average
1160 // out to 1/60s per frame every 3 frames. [And now it does!]
1161 // Maybe we need a higher resolution timer, as the SDL_GetTicks() (in ms) seems
1162 // to jitter all over the place...
1163 frameCount = (frameCount + 1) % 3;
1164 // uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1165 // Get number of ticks burned in this frame, for displaying elsewhere
1167 frameTicks = SDL_GetTicks() - startTicks;
1169 frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency();
1172 // Wait for next frame...
1173 // while (SDL_GetTicks() - startTicks < waitFrameTime)
1177 startTicks = SDL_GetTicks();
1179 startTicks = SDL_GetPerformanceCounter();
1182 uint64_t cpuCycles = GetCurrentV65C02Clock();
1183 uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1184 WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1185 lastCPUCycles = cpuCycles
1188 //let's wait, then signal...
1189 //works longer, but then still falls behind... [FIXED, see above]
1190 #ifdef THREADED_65C02
1192 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1197 static void BlinkTimer(void)
1199 // Set up blinking at 1/4 sec intervals
1201 SetCallbackTime(BlinkTimer, 250000);
1206 Next problem is this: How to have events occur and synchronize with the rest
1209 o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1210 remainder CPU cycles over...)
1212 One way would be to use a fractional accumulator, then subtract 1 every
1213 time it overflows. Like so:
1215 double overflow = 0;
1219 Execute6808(&soundCPU, time);
1220 overflow += 0.289115646;