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