]> Shamusworld >> Repos - apple2/blob - src/apple2.cpp
Improvements to timing, disk selector; added Double LoRes.
[apple2] / src / apple2.cpp
1 //
2 // Apple 2 SDL Portable Apple Emulator
3 //
4 // by James Hammons
5 // © 2017 Underground Software
6 //
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
11 // there.
12 //
13 // JLH = James Hammons <jlhamm@acm.org>
14 //
15 // WHO  WHEN        WHAT
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
22 //
23
24 // STILL TO DO:
25 //
26 // - Port to SDL [DONE]
27 // - GUI goodies
28 // - Weed out unneeded functions [DONE]
29 // - Disk I/O [DONE]
30 // - 128K IIe related stuff [DONE]
31 // - State loading/saving
32 //
33 // BUGS:
34 //
35 // - Having a directory in the ${disks} directory causes a segfault in floppy
36 //
37
38 #include "apple2.h"
39
40 #include <SDL2/SDL.h>
41 #include <fstream>
42 #include <iomanip>
43 #include <iostream>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string>
47 #include <time.h>
48 #include "firmware.h"
49 #include "floppy.h"
50 #include "log.h"
51 #include "mmu.h"
52 #include "settings.h"
53 #include "sound.h"
54 #include "timing.h"
55 #include "video.h"
56 #include "gui/gui.h"
57 #include "gui/diskselector.h"
58
59 // Debug and misc. defines
60
61 #define THREADED_65C02
62 #define CPU_THREAD_OVERFLOW_COMPENSATION
63 #define DEBUG_LC
64 //#define CPU_CLOCK_CHECKING
65 //#define THREAD_DEBUGGING
66 #define SOFT_SWITCH_DEBUGGING
67
68 // Global variables
69
70 uint8_t ram[0x10000], rom[0x10000];                     // RAM & ROM spaces
71 uint8_t ram2[0x10000];                                          // Auxillary RAM
72 V65C02REGS mainCPU;                                                     // v65C02 execution context
73 uint8_t appleType = APPLE_TYPE_IIE;
74 FloppyDrive floppyDrive;
75 bool powerStateChangeRequested = false;
76 uint64_t frameCycleStart;
77
78 // Exported variables
79
80 uint8_t lastKeyPressed = 0;
81 bool keyDown = false;
82 bool openAppleDown = false;
83 bool closedAppleDown = false;
84 bool store80Mode = false;
85 bool vbl = false;
86 bool slotCXROM = false;
87 bool slotC3ROM = false;
88 bool ramrd = false;
89 bool ramwrt = false;
90 bool altzp = false;
91 bool ioudis = true;
92 bool dhires = false;
93 // Language card state (ROM read, no write)
94 uint8_t lcState = 0x02;
95
96 static bool running = true;                                     // Machine running state flag...
97 static uint32_t startTicks;
98 static bool pauseMode = false;
99 static bool fullscreenDebounce = false;
100 static bool capsLock = false;
101 static bool capsLockDebounce = false;
102 static bool resetKeyDown = false;
103 static int8_t hideMouseTimeout = 60;
104
105 // Vars to handle the //e's 2-key rollover
106 static SDL_Keycode keysHeld[2];
107 static uint8_t keysHeldAppleCode[2];
108 static uint8_t keyDownCount = 0;
109 static uint8_t keyDelay = 0;
110
111 // Local functions
112
113 static void SaveApple2State(const char * filename);
114 static bool LoadApple2State(const char * filename);
115 static void ResetApple2State(void);
116
117 // Local timer callback functions
118
119 static void FrameCallback(void);
120 static void BlinkTimer(void);
121
122 #ifdef THREADED_65C02
123 // Test of threaded execution of 6502
124 static SDL_Thread * cpuThread = NULL;
125 static SDL_cond * cpuCond = NULL;
126 static SDL_sem * mainSem = NULL;
127 static bool cpuFinished = false;
128
129 // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz
130 //     This is a lie. At the end of each 65 cycle line, there is an elongated
131 //     cycle (the so-called 'long' cycle) that throws the calcs out of whack.
132
133 // Let's try a thread...
134 //
135 // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes
136 // it up.
137 //
138 int CPUThreadFunc(void * data)
139 {
140         // Mutex must be locked for conditional to work...
141         // Also, must be created in the thread that uses it...
142         SDL_mutex * cpuMutex = SDL_CreateMutex();
143
144 #ifdef CPU_THREAD_OVERFLOW_COMPENSATION
145         float overflow = 0.0;
146 #endif
147
148         do
149         {
150 // decrement mainSem...
151 #ifdef THREAD_DEBUGGING
152 WriteLog("CPU: SDL_SemWait(mainSem);\n");
153 #endif
154                 SDL_SemWait(mainSem);
155
156                 // There are exactly 800 slices of 21.333 cycles per frame, so it works
157                 // out evenly.
158                 // [Actually, seems it's 786 slices of 21.666 cycles per frame]
159
160                 // Set our frame cycle counter to the correct # of cycles at the start
161                 // of this frame
162                 frameCycleStart = mainCPU.clock - mainCPU.overflow;
163 #ifdef THREAD_DEBUGGING
164 WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
165 #endif
166 //              for(int i=0; i<800; i++)
167                 for(int i=0; i<786; i++)
168                 {
169                         uint32_t cycles = 21;
170 //                      overflow += 0.333333334;
171                         overflow += 0.666666667;
172
173                         if (overflow > 1.0)
174                         {
175                                 cycles++;
176                                 overflow -= 1.0;
177                         }
178
179                         // If the CTRL+Reset key combo is being held, make sure the RESET
180                         // line stays asserted:
181                         if (resetKeyDown)
182                                 mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
183
184                         Execute65C02(&mainCPU, cycles);
185                         WriteSampleToBuffer();
186
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 happens on lines 230 thru 233).
192                         vbl = ((i > 17) && (i < 592) ? true : false);
193                 }
194 /*
195 Other timings from UTA2E:
196 Power-up reset                          32.6 msec / 512 horizontal scans
197 Flash cycle                                     1.87 Hz / Vertical freq./32
198 Delay before auto repeat        534-801 msec / 32-48 vertical scans
199 Auto repeat frequency           15 Hz / Vertical freq./4
200 Vertical frequency                      59.94 Hz
201 Horizontal frequency            15,734 Hz
202 1 NTSC frame = 17030 cycles
203 1 line = 65 cycles
204 70 blank lines for top margin, 192 lines for screen, (35 & 35?)
205 VA-C,V0-5 is upcounter starting at 011111010 ($FA) to 111111111 ($1FF)
206 Horizontal counter is upcounter resets to 0000000, then jumps to 1000000 &
207 counts up to 1111111 (bit 7 is Horizontal Preset Enable, which resets the counter when it goes low, the rest are H0-5)
208
209 pg. 3-24 says one cycle before VBL the counters will be at
210 010111111/1111111 (that doesn't look right to me...)
211
212 Video address bits:
213
214 A0 <- H0
215 A1 <- H1
216 A2 <- H2
217 A3 <- SUM-A3
218 A4 <- SUM-A4
219 A5 <- SUM-A5
220 A6 <- SUM-A6
221 A7 <- V0
222 A8 <- V1
223 A9 <- V2
224
225 SUMS are calculated like so:
226
227   1        1       0       1
228           H5      H4      H3
229   V4      V3      V4      V3
230 ------------------------------
231 SUM-A6  SUM-A5  SUM-A4  SUM-A3
232
233 In TEXT mode, A10 == (80STORE' * PAGE2)', A11 == 80STORE' * PAGE2
234 A12-A15 == 0
235
236 In HIRES mode, A13 == (PAGE2 * 80STORE')', A14 == PAGE2 * 80STORE'
237 A10 == VA, A11 == VB, A12 == VC, A15 == 0
238
239 N.B.: VA-C are lower bits than V5-0
240
241 HC, from 00, 0 to 23 is the HBL interval, with horizontal retrace occuring between cycles 8 and 11.
242 VC, from line 0-5 and 198-261 is the VBL interval, with vertical retrace occuring between lines 230-233
243
244 */
245
246 #ifdef THREAD_DEBUGGING
247 WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
248 #endif
249                 SDL_mutexP(cpuMutex);
250                 // increment mainSem...
251 #ifdef THREAD_DEBUGGING
252 WriteLog("CPU: SDL_SemPost(mainSem);\n");
253 #endif
254                 SDL_SemPost(mainSem);
255 #ifdef THREAD_DEBUGGING
256 WriteLog("CPU: SDL_CondWait(cpuCond, cpuMutex);\n");
257 #endif
258                 SDL_CondWait(cpuCond, cpuMutex);
259
260 #ifdef THREAD_DEBUGGING
261 WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
262 #endif
263                 SDL_mutexV(cpuMutex);
264         }
265         while (!cpuFinished);
266
267         SDL_DestroyMutex(cpuMutex);
268
269         return 0;
270 }
271 #endif
272
273
274 //
275 // Request a change in the power state of the emulated Apple
276 //
277 void SetPowerState(void)
278 {
279         powerStateChangeRequested = true;
280 }
281
282
283 //
284 // Load a file into RAM/ROM image space
285 //
286 bool LoadImg(char * filename, uint8_t * ram, int size)
287 {
288         FILE * fp = fopen(filename, "rb");
289
290         if (fp == NULL)
291                 return false;
292
293         fread(ram, 1, size, fp);
294         fclose(fp);
295
296         return true;
297 }
298
299
300 const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.0";
301 static void SaveApple2State(const char * filename)
302 {
303         WriteLog("Main: Saving Apple2 state...\n");
304         FILE * file = fopen(filename, "wb");
305
306         if (!file)
307         {
308                 WriteLog("Could not open file \"%s\" for writing!\n", filename);
309                 return;
310         }
311
312         // Write out header
313         fwrite(stateHeader, 1, 18, file);
314
315         // Write out CPU state
316         fwrite(&mainCPU, 1, sizeof(mainCPU), file);
317
318         // Write out main memory
319         fwrite(ram, 1, 0x10000, file);
320         fwrite(ram2, 1, 0x10000, file);
321
322         // Write out state variables
323         fputc((uint8_t)keyDown, file);
324         fputc((uint8_t)openAppleDown, file);
325         fputc((uint8_t)closedAppleDown, file);
326         fputc((uint8_t)store80Mode, file);
327         fputc((uint8_t)vbl, file);
328         fputc((uint8_t)slotCXROM, file);
329         fputc((uint8_t)slotC3ROM, file);
330         fputc((uint8_t)ramrd, file);
331         fputc((uint8_t)ramwrt, file);
332         fputc((uint8_t)altzp, file);
333         fputc((uint8_t)ioudis, file);
334         fputc((uint8_t)dhires, file);
335         fputc((uint8_t)flash, file);
336         fputc((uint8_t)textMode, file);
337         fputc((uint8_t)mixedMode, file);
338         fputc((uint8_t)displayPage2, file);
339         fputc((uint8_t)hiRes, file);
340         fputc((uint8_t)alternateCharset, file);
341         fputc((uint8_t)col80Mode, file);
342         fputc(lcState, file);
343
344         // Write out floppy state
345         floppyDrive.SaveState(file);
346         fclose(file);
347 }
348
349
350 static bool LoadApple2State(const char * filename)
351 {
352         WriteLog("Main: Loading Apple2 state...\n");
353         FILE * file = fopen(filename, "rb");
354
355         if (!file)
356         {
357                 WriteLog("Could not open file \"%s\" for reading!\n", filename);
358                 return false;
359         }
360
361         uint8_t buffer[18];
362         fread(buffer, 1, 18, file);
363
364         // Sanity check...
365         if (memcmp(buffer, stateHeader, 18) != 0)
366         {
367                 fclose(file);
368                 WriteLog("File \"%s\" is not a valid Apple2 save state file!\n", filename);
369                 return false;
370         }
371
372         // Read CPU state
373         fread(&mainCPU, 1, sizeof(mainCPU), file);
374
375         // Read main memory
376         fread(ram, 1, 0x10000, file);
377         fread(ram2, 1, 0x10000, file);
378
379         // Read in state variables
380         keyDown = (bool)fgetc(file);
381         openAppleDown = (bool)fgetc(file);
382         closedAppleDown = (bool)fgetc(file);
383         store80Mode = (bool)fgetc(file);
384         vbl = (bool)fgetc(file);
385         slotCXROM = (bool)fgetc(file);
386         slotC3ROM = (bool)fgetc(file);
387         ramrd = (bool)fgetc(file);
388         ramwrt = (bool)fgetc(file);
389         altzp = (bool)fgetc(file);
390         ioudis = (bool)fgetc(file);
391         dhires = (bool)fgetc(file);
392         flash = (bool)fgetc(file);
393         textMode = (bool)fgetc(file);
394         mixedMode = (bool)fgetc(file);
395         displayPage2 = (bool)fgetc(file);
396         hiRes = (bool)fgetc(file);
397         alternateCharset = (bool)fgetc(file);
398         col80Mode = (bool)fgetc(file);
399         lcState = fgetc(file);
400
401         // Read in floppy state
402         floppyDrive.LoadState(file);
403
404         fclose(file);
405
406         // Make sure things are in a sane state before execution :-P
407         mainCPU.RdMem = AppleReadMem;
408         mainCPU.WrMem = AppleWriteMem;
409         ResetMMUPointers();
410
411         return true;
412 }
413
414
415 static void ResetApple2State(void)
416 {
417         keyDown = false;
418         openAppleDown = false;
419         closedAppleDown = false;
420         store80Mode = false;
421         vbl = false;
422         slotCXROM = false;
423         slotC3ROM = false;
424         ramrd = false;
425         ramwrt = false;
426         altzp = false;
427         ioudis = true;
428         dhires = false;
429         lcState = 0x02;
430         ResetMMUPointers();
431
432         // Without this, you can wedge the system :-/
433         memset(ram, 0, 0x10000);
434         memset(ram2, 0, 0x10000);
435         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
436 }
437
438
439 #ifdef CPU_CLOCK_CHECKING
440 uint8_t counter = 0;
441 uint32_t totalCPU = 0;
442 uint64_t lastClock = 0;
443 #endif
444 //
445 // Main loop
446 //
447 int main(int /*argc*/, char * /*argv*/[])
448 {
449         InitLog("./apple2.log");
450         LoadSettings();
451         srand(time(NULL));                      // Initialize RNG
452
453 #if 0
454 // Make some timing/address tables
455
456         for(uint32_t line=0; line<262; line++)
457         {
458                 WriteLog("LINE %03i: ", line);
459
460                 for(uint32_t col=0; col<65; col++)
461                 {
462                         // Convert these to H/V counters
463                         uint32_t hcount = col - 1;
464
465                         // HC sees zero twice:
466                         if (hcount == 0xFFFFFFFF)
467                                 hcount = 0;
468
469                         uint32_t vcount = line + 0xFA;
470
471                         // Now do the address calculations
472                         uint32_t sum = 0xD + ((hcount & 0x38) >> 3)
473                                 + (((vcount & 0xC0) >> 6) | ((vcount & 0xC0) >> 4));
474                         uint32_t address = ((vcount & 0x38) << 4) | ((sum & 0x0F) << 3) | (hcount & 0x07);
475
476                         // Add in particulars for the gfx mode we're in...
477                         if (false)
478                                 // non hires
479                                 address |= (!(!store80Mode && displayPage2) ? 0x400 : 0)
480                                         | (!store80Mode && displayPage2 ? 0x800 : 0);
481                         else
482                                 // hires
483                                 address |= (!(!store80Mode && displayPage2) ? 0x2000: 0)
484                                         | (!store80Mode && displayPage2 ? 0x4000 : 0)
485                                         | ((vcount & 0x07) << 10);
486
487                         WriteLog("$%04X ", address);
488                 }
489
490                 WriteLog("\n");
491         }
492
493 #endif
494
495         // Zero out memory
496         memset(ram, 0, 0x10000);
497         memset(rom, 0, 0x10000);
498         memset(ram2, 0, 0x10000);
499
500         // Set up MMU
501         SetupAddressMap();
502         ResetMMUPointers();
503
504         // Set up V65C02 execution context
505         memset(&mainCPU, 0, sizeof(V65C02REGS));
506         mainCPU.RdMem = AppleReadMem;
507         mainCPU.WrMem = AppleWriteMem;
508         mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET;
509
510         if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000))
511         {
512                 WriteLog("Could not open file '%s'!\n", settings.BIOSPath);
513                 return -1;
514         }
515
516         WriteLog("About to initialize video...\n");
517
518         if (!InitVideo())
519         {
520                 printf("Could not init screen: aborting!\n");
521                 return -1;
522         }
523
524         GUI::Init(sdlRenderer);
525         WriteLog("About to initialize audio...\n");
526         SoundInit();
527
528         if (settings.autoStateSaving)
529         {
530                 // Load last state from file...
531                 if (!LoadApple2State(settings.autoStatePath))
532                         WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath);
533         }
534
535         running = true;
536         InitializeEventList();
537         // Set frame to fire at 1/60 s interval
538         SetCallbackTime(FrameCallback, 16666.66666667);
539         // Set up blinking at 1/4 s intervals
540         SetCallbackTime(BlinkTimer, 250000);
541         startTicks = SDL_GetTicks();
542
543 #ifdef THREADED_65C02
544         // Kick off the CPU...
545         cpuCond = SDL_CreateCond();
546         mainSem = SDL_CreateSemaphore(1);
547         cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
548 //Hmm... CPU does POST (+1), wait, then WAIT (-1)
549 //      SDL_sem * mainMutex = SDL_CreateMutex();
550 #endif
551
552         WriteLog("Entering main loop...\n");
553
554         while (running)
555         {
556                 double timeToNextEvent = GetTimeToNextEvent();
557 #ifndef THREADED_65C02
558                 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent));
559
560         #ifdef CPU_CLOCK_CHECKING
561 totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
562         #endif
563 #endif
564                 HandleNextEvent();
565         }
566
567 #ifdef THREADED_65C02
568 WriteLog("Main: cpuFinished = true;\n");
569         cpuFinished = true;
570
571 WriteLog("Main: SDL_SemWait(mainSem);\n");
572         // Only do this if NOT in power off/emulation paused mode!
573         if (!pauseMode)
574                 // Should lock until CPU thread is waiting...
575                 SDL_SemWait(mainSem);
576 #endif
577
578 WriteLog("Main: SDL_CondSignal(cpuCond);\n");
579         SDL_CondSignal(cpuCond);//thread is probably asleep, so wake it up
580 WriteLog("Main: SDL_WaitThread(cpuThread, NULL);\n");
581         SDL_WaitThread(cpuThread, NULL);
582 WriteLog("Main: SDL_DestroyCond(cpuCond);\n");
583         SDL_DestroyCond(cpuCond);
584         SDL_DestroySemaphore(mainSem);
585
586         // Autosave state here, if requested...
587         if (settings.autoStateSaving)
588                 SaveApple2State(settings.autoStatePath);
589
590         floppyDrive.SaveImage(0);
591         floppyDrive.SaveImage(1);
592
593         SoundDone();
594         VideoDone();
595         SaveSettings();
596         LogDone();
597
598         return 0;
599 }
600
601
602 //
603 // Apple //e scancodes. Tables are normal (0), +CTRL (1), +SHIFT (2),
604 // +CTRL+SHIFT (3). Order of keys is:
605 // Delete, left, tab, down, up, return, right, escape
606 // Space, single quote, comma, minus, period, slash
607 // Numbers 0-9
608 // Semicolon, equals, left bracket, backslash, right bracket, backquote
609 // Letters a-z (lowercase)
610 //
611 // N.B.: The Apple //e keyboard maps its shift characters like most modern US
612 //       keyboards, so this table should suffice for the shifted keys just fine.
613 //
614 uint8_t apple2e_keycode[4][56] = {
615         {       // Normal
616                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
617                 0x20, 0x27, 0x2C, 0x2D, 0x2E, 0x2F,
618                 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
619                 0x3B, 0x3D, 0x5B, 0x5C, 0x5D, 0x60,
620                 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
621                 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
622                 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A
623         },
624         {       // With CTRL held
625                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
626                 0x20, 0x27, 0x2C, 0x1F, 0x2E, 0x2F,
627                 0x30, 0x31, 0x00, 0x33, 0x34, 0x35, 0x1E, 0x37, 0x38, 0x39,
628                 0x3B, 0x3D, 0x1B, 0x1C, 0x1D, 0x60,
629                 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
630                 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
631                 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
632         },
633         {       // With Shift held
634                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
635                 0x20, 0x22, 0x3C, 0x5F, 0x3E, 0x3F,
636                 0x29, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28,
637                 0x3A, 0x2B, 0x7B, 0x7C, 0x7D, 0x7E,
638                 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
639                 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54,
640                 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A
641         },
642         {       // With CTRL+Shift held
643                 0x7F, 0x08, 0x09, 0x0A, 0x0B, 0x0D, 0x15, 0x1B,
644                 0x20, 0x22, 0x3C, 0x1F, 0x3E, 0x3F,
645                 0x29, 0x21, 0x00, 0x23, 0x24, 0x25, 0x1E, 0x26, 0x2A, 0x28,
646                 0x3A, 0x2B, 0x1B, 0x1C, 0x1D, 0x7E,
647                 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
648                 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
649                 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
650         }
651 };
652
653 static uint32_t frameCount = 0;
654 static void FrameCallback(void)
655 {
656         SDL_Event event;
657         uint8_t keyIndex;
658
659         while (SDL_PollEvent(&event))
660         {
661                 switch (event.type)
662                 {
663                 case SDL_KEYDOWN:
664                         // We do our own repeat handling thank you very much! :-)
665                         if (event.key.repeat != 0)
666                                 break;
667
668                         // Use CTRL+SHIFT+Q to exit, as well as the usual window decoration
669                         // method
670                         if ((event.key.keysym.mod & KMOD_CTRL)
671                                 && (event.key.keysym.mod & KMOD_SHIFT)
672                                 && (event.key.keysym.sym == SDLK_q))
673                         {
674                                 running = false;
675                                 // We return here, because we don't want to pick up any
676                                 // spurious keypresses with our exit sequence.
677                                 return;
678                         }
679
680                         // CTRL+RESET key emulation (mapped to CTRL+HOME)
681                         if ((event.key.keysym.mod & KMOD_CTRL)
682                                 && (event.key.keysym.sym == SDLK_HOME))
683                         {
684 //seems to leave the machine in an inconsistent state vis-a-vis the language card... [does it anymore?]
685                                 resetKeyDown = true;
686                                 break;
687                         }
688
689                         // There has GOT to be a better way off mapping SDLKs to our
690                         // keyindex. But for now, this should suffice.
691                         keyIndex = 0xFF;
692
693                         switch (event.key.keysym.sym)
694                         {
695                         case SDLK_BACKSPACE:    keyIndex =  0; break;
696                         case SDLK_LEFT:         keyIndex =  1; break;
697                         case SDLK_TAB:          keyIndex =  2; break;
698                         case SDLK_DOWN:         keyIndex =  3; break;
699                         case SDLK_UP:           keyIndex =  4; break;
700                         case SDLK_RETURN:       keyIndex =  5; break;
701                         case SDLK_RIGHT:        keyIndex =  6; break;
702                         case SDLK_ESCAPE:       keyIndex =  7; break;
703                         case SDLK_SPACE:        keyIndex =  8; break;
704                         case SDLK_QUOTE:        keyIndex =  9; break;
705                         case SDLK_COMMA:        keyIndex = 10; break;
706                         case SDLK_MINUS:        keyIndex = 11; break;
707                         case SDLK_PERIOD:       keyIndex = 12; break;
708                         case SDLK_SLASH:        keyIndex = 13; break;
709                         case SDLK_0:            keyIndex = 14; break;
710                         case SDLK_1:            keyIndex = 15; break;
711                         case SDLK_2:            keyIndex = 16; break;
712                         case SDLK_3:            keyIndex = 17; break;
713                         case SDLK_4:            keyIndex = 18; break;
714                         case SDLK_5:            keyIndex = 19; break;
715                         case SDLK_6:            keyIndex = 20; break;
716                         case SDLK_7:            keyIndex = 21; break;
717                         case SDLK_8:            keyIndex = 22; break;
718                         case SDLK_9:            keyIndex = 23; break;
719                         case SDLK_SEMICOLON:    keyIndex = 24; break;
720                         case SDLK_EQUALS:       keyIndex = 25; break;
721                         case SDLK_LEFTBRACKET:  keyIndex = 26; break;
722                         case SDLK_BACKSLASH:    keyIndex = 27; break;
723                         case SDLK_RIGHTBRACKET: keyIndex = 28; break;
724                         case SDLK_BACKQUOTE:    keyIndex = 29; break;
725                         case SDLK_a:            keyIndex = 30; break;
726                         case SDLK_b:            keyIndex = 31; break;
727                         case SDLK_c:            keyIndex = 32; break;
728                         case SDLK_d:            keyIndex = 33; break;
729                         case SDLK_e:            keyIndex = 34; break;
730                         case SDLK_f:            keyIndex = 35; break;
731                         case SDLK_g:            keyIndex = 36; break;
732                         case SDLK_h:            keyIndex = 37; break;
733                         case SDLK_i:            keyIndex = 38; break;
734                         case SDLK_j:            keyIndex = 39; break;
735                         case SDLK_k:            keyIndex = 40; break;
736                         case SDLK_l:            keyIndex = 41; break;
737                         case SDLK_m:            keyIndex = 42; break;
738                         case SDLK_n:            keyIndex = 43; break;
739                         case SDLK_o:            keyIndex = 44; break;
740                         case SDLK_p:            keyIndex = 45; break;
741                         case SDLK_q:            keyIndex = 46; break;
742                         case SDLK_r:            keyIndex = 47; break;
743                         case SDLK_s:            keyIndex = 48; break;
744                         case SDLK_t:            keyIndex = 49; break;
745                         case SDLK_u:            keyIndex = 50; break;
746                         case SDLK_v:            keyIndex = 51; break;
747                         case SDLK_w:            keyIndex = 52; break;
748                         case SDLK_x:            keyIndex = 53; break;
749                         case SDLK_y:            keyIndex = 54; break;
750                         case SDLK_z:            keyIndex = 55; break;
751                         }
752
753                         // Stuff the key in if we have a valid one...
754                         if (keyIndex != 0xFF)
755                         {
756                                 // Handle Shift, CTRL, & Shift+CTRL combos
757                                 uint8_t table = 0;
758
759                                 if (event.key.keysym.mod & KMOD_CTRL)
760                                         table |= 1;
761
762                                 if (event.key.keysym.mod & KMOD_SHIFT)
763                                         table |= 2;
764
765                                 lastKeyPressed = apple2e_keycode[table][keyIndex];
766                                 keyDown = true;
767
768                                 // Handle Caps Lock
769                                 if (capsLock
770                                         && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A))
771                                         lastKeyPressed -= 0x20;
772
773                                 // Handle key repeat if the key hasn't been held
774 //                              if (keyDelay == 0)
775                                         keyDelay = 15;
776
777                                 keyDownCount++;
778
779                                 // Buffer the key held. Note that the last key is always
780                                 // stuffed into keysHeld[0].
781                                 if (keyDownCount >= 2)
782                                 {
783                                         keysHeld[1] = keysHeld[0];
784                                         keysHeldAppleCode[1] = keysHeldAppleCode[0];
785
786                                         if (keyDownCount > 2)
787                                                 keyDownCount = 2;
788                                 }
789
790                                 keysHeld[0] = event.key.keysym.sym;
791                                 keysHeldAppleCode[0] = lastKeyPressed;
792                                 break;
793                         }
794
795                         if (event.key.keysym.sym == SDLK_PAUSE)
796                         {
797                                 pauseMode = !pauseMode;
798
799                                 if (pauseMode)
800                                 {
801                                         SoundPause();
802                                         SpawnMessage("*** PAUSED ***");
803                                 }
804                                 else
805                                 {
806                                         SoundResume();
807                                         SpawnMessage("*** RESUME ***");
808                                 }
809                         }
810                         // Buttons 0 & 1
811                         else if (event.key.keysym.sym == SDLK_LALT)
812                                 openAppleDown = true;
813                         else if (event.key.keysym.sym == SDLK_RALT)
814                                 closedAppleDown = true;
815                         // Toggle the disassembly process
816                         else if (event.key.keysym.sym == SDLK_F11)
817                                 dumpDis = !dumpDis;
818
819 /*else if (event.key.keysym.sym == SDLK_F9)
820 {
821         floppyDrive.CreateBlankImage(0);
822 //      SpawnMessage("Image cleared...");
823 }//*/
824 /*else if (event.key.keysym.sym == SDLK_F10)
825 {
826         floppyDrive.SwapImages();
827 //      SpawnMessage("Image swapped...");
828 }//*/
829
830                         else if (event.key.keysym.sym == SDLK_F2)
831                                 TogglePalette();
832                         else if (event.key.keysym.sym == SDLK_F3)
833                                 CycleScreenTypes();
834                         else if (event.key.keysym.sym == SDLK_F5)
835                         {
836                                 VolumeDown();
837                                 char volStr[19] = "[****************]";
838
839                                 for(int i=GetVolume(); i<16; i++)
840                                         volStr[1 + i] = '-';
841
842                                 SpawnMessage("Volume: %s", volStr);
843                         }
844                         else if (event.key.keysym.sym == SDLK_F6)
845                         {
846                                 VolumeUp();
847                                 char volStr[19] = "[****************]";
848
849                                 for(int i=GetVolume(); i<16; i++)
850                                         volStr[1 + i] = '-';
851
852                                 SpawnMessage("Volume: %s", volStr);
853                         }
854                         else if (event.key.keysym.sym == SDLK_F12)
855                         {
856                                 if (!fullscreenDebounce)
857                                 {
858                                         ToggleFullScreen();
859                                         fullscreenDebounce = true;
860                                 }
861                         }
862                         else if (event.key.keysym.sym == SDLK_CAPSLOCK)
863                         {
864                                 if (!capsLockDebounce)
865                                 {
866                                         capsLock = !capsLock;
867                                         capsLockDebounce = true;
868                                 }
869                         }
870
871                         break;
872
873                 case SDL_KEYUP:
874                         if (event.key.keysym.sym == SDLK_F12)
875                                 fullscreenDebounce = false;
876                         else if (event.key.keysym.sym == SDLK_CAPSLOCK)
877                                 capsLockDebounce = false;
878                         // Paddle buttons 0 & 1
879                         else if (event.key.keysym.sym == SDLK_LALT)
880                                 openAppleDown = false;
881                         else if (event.key.keysym.sym == SDLK_RALT)
882                                 closedAppleDown = false;
883                         else if ((event.key.keysym.mod & KMOD_CTRL)
884                                 && (event.key.keysym.sym == SDLK_HOME))
885                                 resetKeyDown = false;
886                         else
887                         {
888                                 // Handle key buffering 'key up' event (2 key rollover)
889                                 if ((keyDownCount == 1) && (event.key.keysym.sym == keysHeld[0]))
890                                 {
891                                         keyDownCount--;
892                                         keyDelay = 0;   // Reset key delay
893                                 }
894                                 else if (keyDownCount == 2)
895                                 {
896                                         if (event.key.keysym.sym == keysHeld[0])
897                                         {
898                                                 keyDownCount--;
899                                                 keysHeld[0] = keysHeld[1];
900                                                 keysHeldAppleCode[0] = keysHeldAppleCode[1];
901                                         }
902                                         else if (event.key.keysym.sym == keysHeld[1])
903                                         {
904                                                 keyDownCount--;
905                                         }
906                                 }
907                         }
908
909                         break;
910
911                 case SDL_MOUSEBUTTONDOWN:
912                         GUI::MouseDown(event.motion.x, event.motion.y, event.motion.state);
913                         break;
914
915                 case SDL_MOUSEBUTTONUP:
916                         GUI::MouseUp(event.motion.x, event.motion.y, event.motion.state);
917                         break;
918
919                 case SDL_MOUSEMOTION:
920                         GUI::MouseMove(event.motion.x, event.motion.y, event.motion.state);
921
922                         // Handle mouse showing when the mouse is hidden...
923                         if (hideMouseTimeout == -1)
924                                 SDL_ShowCursor(1);
925
926                         hideMouseTimeout = 60;
927                         break;
928
929                 case SDL_WINDOWEVENT:
930                         if (event.window.event == SDL_WINDOWEVENT_LEAVE)
931                                 GUI::MouseMove(0, 0, 0);
932
933                         break;
934
935                 case SDL_QUIT:
936                         running = false;
937                 }
938         }
939
940         // Hide the mouse if it's been 1s since the last time it was moved
941         // N.B.: Should disable mouse hiding if it's over the GUI...
942         if ((hideMouseTimeout > 0) && !(GUI::sidebarState == SBS_SHOWN || DiskSelector::showWindow == true))
943                 hideMouseTimeout--;
944         else if (hideMouseTimeout == 0)
945         {
946                 hideMouseTimeout--;
947                 SDL_ShowCursor(0);
948         }
949
950         // Stuff the Apple keyboard buffer, if any keys are pending
951         // N.B.: May have to simulate the key repeat delay too [yup, sure do]
952         //       According to "Understanding the Apple IIe", the initial delay is
953         //       between 32 & 48 jiffies and the repeat is every 4 jiffies.
954         if (keyDownCount > 0)
955         {
956                 keyDelay--;
957
958                 if (keyDelay == 0)
959                 {
960                         keyDelay = 3;
961                         lastKeyPressed = keysHeldAppleCode[0];
962                         keyDown = true;
963                 }
964         }
965
966         // Handle power request from the GUI
967         if (powerStateChangeRequested)
968         {
969                 if (GUI::powerOnState)
970                 {
971                         pauseMode = false;
972                         // Unlock the CPU thread...
973                         SDL_SemPost(mainSem);
974                 }
975                 else
976                 {
977                         pauseMode = true;
978                         // Should lock until CPU thread is waiting...
979                         SDL_SemWait(mainSem);
980                         ResetApple2State();
981                 }
982
983                 powerStateChangeRequested = false;
984         }
985
986         // Render the Apple screen + GUI overlay
987         RenderAppleScreen(sdlRenderer);
988         GUI::Render(sdlRenderer);
989         SDL_RenderPresent(sdlRenderer);
990         SetCallbackTime(FrameCallback, 16666.66666667);
991
992 #ifdef CPU_CLOCK_CHECKING
993 //We know it's stopped, so we can get away with this...
994 counter++;
995 if (counter == 60)
996 {
997         uint64_t clock = GetCurrentV65C02Clock();
998 //totalCPU += (uint32_t)(clock - lastClock);
999
1000         printf("Executed %u cycles...\n", (uint32_t)(clock - lastClock));
1001         lastClock = clock;
1002 //      totalCPU = 0;
1003         counter = 0;
1004 }
1005 #endif
1006
1007 // This is the problem: If you set the interval to 16, it runs faster than
1008 // 1/60s per frame. If you set it to 17, it runs slower. What we need is to
1009 // have it do 16 for one frame, then 17 for two others. Then it should average
1010 // out to 1/60s per frame every 3 frames. [And now it does!]
1011         frameCount = (frameCount + 1) % 3;
1012         uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0);
1013
1014         // Wait for next frame...
1015         while (SDL_GetTicks() - startTicks < waitFrameTime)
1016                 SDL_Delay(1);
1017
1018         startTicks = SDL_GetTicks();
1019 #if 0
1020         uint64_t cpuCycles = GetCurrentV65C02Clock();
1021         uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles);
1022         WriteLog("FrameCallback: used %i cycles\n", cyclesBurned);
1023         lastCPUCycles = cpuCycles
1024 #endif
1025
1026 //let's wait, then signal...
1027 //works longer, but then still falls behind... [FIXED, see above]
1028 #ifdef THREADED_65C02
1029         if (!pauseMode)
1030                 SDL_CondSignal(cpuCond);//OK, let the CPU go another frame...
1031 #endif
1032 }
1033
1034
1035 static void BlinkTimer(void)
1036 {
1037         // Set up blinking at 1/4 sec intervals
1038         flash = !flash;
1039         SetCallbackTime(BlinkTimer, 250000);
1040 }
1041
1042
1043 /*
1044 Next problem is this: How to have events occur and synchronize with the rest
1045 of the threads?
1046
1047   o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
1048     remainder CPU cycles over...)
1049
1050 One way would be to use a fractional accumulator, then subtract 1 every
1051 time it overflows. Like so:
1052
1053 double overflow = 0;
1054 uint32_t time = 20;
1055 while (!done)
1056 {
1057         Execute6808(&soundCPU, time);
1058         overflow += 0.289115646;
1059         if (overflow > 1.0)
1060         {
1061                 overflow -= 1.0;
1062                 time = 21;
1063         }
1064         else
1065                 time = 20;
1066 }
1067 */
1068