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