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