diff --git a/reinetteII+.c b/reinetteII+.c index 30d0b23..530ae65 100644 --- a/reinetteII+.c +++ b/reinetteII+.c @@ -2,8 +2,8 @@ reinette II plus, a french Apple II emulator, using SDL2 and powered by puce6502 - a MOS 6502 cpu emulator by the same author - Last modified 1st of August 2020 - Copyright (c) 2020 Arthur Ferreira (arthur.ferreira2@gmail.com) + Last modified 13th of August 2020 + Copyright (c) 2020 Arthur Ferreira (arthur.ferreira2gmail.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -30,80 +30,120 @@ #include "puce6502.h" + //================================================================ SOFT SWITCHES -uint8_t TEXT = 0; // start of text (0=full text, 20=mixed or 24=full GR) -uint8_t PAGE = 1; // 0xC054 for page1 or 0xC055 for page2 -bool HIRES = false; // 0xC056 (off: GR) or 0xC057 (on: HGR) - -bool SPKR = false; // 0xC030 Speaker toggle -bool MUTED = false; // not an Apple II Soft Switch, press F9 to mute/unmute - -uint8_t KBD = 0; // 0xC000, 0xC010 ascii value of keyboard input - -uint8_t PB0 = 0; // 0xC061 Push Button 0 (bit 7) / Open Apple -uint8_t PB1 = 0; // 0xC062 Push Button 1 (bit 7) / Solid Apple - -uint8_t GC0 = 0; // 0xC064 Game Controller 0 (bit 7) -uint8_t GC1 = 0; // 0xC065 Game Controller 1 (bit 7) -float TGC0, TGC1; // Timers for GC0 and GC1 -float trimGC = .24; // Game Controller trim use F5 and F6 to adjust it +uint8_t KBD = 0; // 0xC000, 0xC010 ascii value of keyboard input +bool SPKR = false; // 0xC030 Speaker toggle +bool TEXT = true; // 0xC050 CLRTEXT / 0xC051 SETTEXT +bool MIXED = false; // 0xC052 CLRMIXED / 0xC053 SETMIXED +uint8_t PAGE = 1; // 0xC054 PAGE1 / 0xC055 PAGE2 +bool HIRES = false; // 0xC056 GR / 0xC057 HGR +uint8_t PB0 = 0; // 0xC061 Push Button 0 (bit 7) / Open Apple +uint8_t PB1 = 0; // 0xC062 Push Button 1 (bit 7) / Solid Apple +uint8_t PB2 = 0; // 0xC063 Push Button 2 (bit 7) / shift mod !!! +uint8_t GC0 = 0; // 0xC064 Game Controller 0 (bit 7) +uint8_t GC1 = 0; // 0xC065 Game Controller 1 (bit 7) +float TGC0, TGC1; // Timers for GC0 and GC1 +float trimGC = .24; // Game Controller trim use F5 and F6 to adjust it //======================================================================== AUDIO -#define audioBufferSize 512 // found to be large enought -#define rate 50 // 1 Mhz / 20000 Hz (the wavSpec.freq) +#define audioBufferSize 512 // found to be large enought +double rate = 23.19727891156463; // 1023000 Hz / 44100 Hz (the wavSpec.freq) +bool muted = false; // press F9 to mute/unmute SDL_AudioDeviceID audioDevice; -uint8_t audioBuffer[2][audioBufferSize]; // see main() for more details -long long int lastTick = 0LL; +Sint8 audioBuffer[2][audioBufferSize] = {0}; // see main() for more details //====================================================================== DISK ][ -uint8_t slot6[256] = {0}; // disk ][ PROM in slot 6 +uint8_t slot6[256] = {0}; // P5A disk ][ PROM in slot 6 struct drive{ - char filename[512]; // the full disk image path - bool readOnly; // based on the image file attributes - uint8_t data[232960]; // nibblelized disk image - bool motorOn; // motor status - bool writeMode; // writes to file are not implemented - uint8_t track; // current track position - uint16_t nibble; // current nibble under head position -} disk[2] = {0}; // two disk ][ drive units + char filename[512]; // the full disk image path + bool readOnly; // based on the image file attributes + uint8_t data[232960]; // nibblelized disk image + bool motorOn; // motor status + bool writeMode; // writes to file are not implemented + uint8_t track; // current track position + uint16_t nibble; // ptr to nibble under head position +} disk[2] = {0}; // two disk ][ drive units + +int curDrv = 0; // only one can be enabled at a time + + +int insertFloppy(SDL_Window *wdo, char *filename, int drv){ + int i, a, b; + char title[1024]; + + FILE *f = fopen(filename, "rb"); // open it in read binary + if (!f){ + printf("Could not open %s\n", filename); + return(0); + } + if (fread(disk[drv].data, 1, 232960, f) != 232960){ // load it into memory and check size + printf("Floppy image should be exactly 232960 Bytes long\n"); + return(0); + } + fclose(f); + + sprintf(disk[drv].filename,"%s", filename); // update disk filename + + f = fopen(filename, "ab"); // check if file is writeable + if (f){ + disk[drv].readOnly = false; // f will be NULL if open in W failed + fclose(f); + } + else disk[drv].readOnly = true; + + i = a = 0; + while (disk[0].filename[i] != 0) // find start of filename for disk0 + if (disk[0].filename[i++] == '\\') a = i; + + i = b = 0; + while (disk[1].filename[i] != 0) // find start of filename for disk1 + if (disk[1].filename[i++] == '\\') b = i; + + sprintf(title, "reinette II+ D1: %s D2: %s", disk[0].filename + a, \ + disk[1].filename + b); + SDL_SetWindowTitle(wdo, title); // updates window title + + return(1); +} -int curDrv = 0; // only one can be enabled at a time void stepMotor(uint16_t address){ - static bool phases[2][4] = {0}; // phases states (for both drives) - static bool phasesB[2][4] = {0}; // phases states Before - static bool phasesBB[2][4] = {0}; // phases states Before Before - static int pIdx[2] = {0}; // phase index (for both drives) - static int pIdxB[2] = {0}; // phase index Before + static bool phases[2][4] = {0}; // phases states (for both drives) + static bool phasesB[2][4] = {0}; // phases states Before + static bool phasesBB[2][4] = {0}; // phases states Before Before + static int pIdx[2] = {0}; // phase index (for both drives) + static int pIdxB[2] = {0}; // phase index Before static int halfTrackPos[2] = {0}; - address &= 7; - int phase = address >> 1; + address &= 7; + int phase = address >> 1; - phasesBB[curDrv][pIdxB[curDrv]] = phasesB[curDrv][pIdxB[curDrv]]; + phasesBB[curDrv][pIdxB[curDrv]] = phasesB[curDrv][pIdxB[curDrv]]; phasesB[curDrv][pIdx[curDrv]] = phases[curDrv][pIdx[curDrv]]; pIdxB[curDrv] = pIdx[curDrv]; - pIdx[curDrv] = phase; + pIdx[curDrv] = phase; - if ((address & 1) == 0){ // head not moving (PHASE X OFF) - phases[curDrv][phase] = false; + if (!(address & 1)){ // head not moving (PHASE x OFF) + phases[curDrv][phase] = false; return; } - // head is moving in - if ((phasesBB[curDrv][(phase + 1) & 3]) && (--halfTrackPos[curDrv] < 0)) - halfTrackPos[curDrv] = 0; - // head is moving out - if ((phasesBB[curDrv][(phase - 1) & 3]) && (++halfTrackPos[curDrv] > 140)) - halfTrackPos[curDrv] = 140; - // update track - phases[curDrv][phase] = true; - disk[curDrv].track = (halfTrackPos[curDrv] + 1) / 2; + + if ((phasesBB[curDrv][(phase + 1) & 3]) && (--halfTrackPos[curDrv] < 0)) // head is moving in + halfTrackPos[curDrv] = 0; + + if ((phasesBB[curDrv][(phase - 1) & 3]) && (++halfTrackPos[curDrv] > 140)) // head is moving out + halfTrackPos[curDrv] = 140; + + phases[curDrv][phase] = true; // update track# + disk[curDrv].track = (halfTrackPos[curDrv] + 1) / 2; + disk[curDrv].nibble = 0; // not sure this is necessary ? } @@ -112,80 +152,86 @@ void stepMotor(uint16_t address){ // it complements both functions when address is between 0xC000 and 0xCFFF uint8_t softSwitches(uint16_t address, uint8_t value){ - static uint8_t dLatch = 0; // disk ][ I/O reg + static uint8_t dLatch = 0; // disk ][ I/O reg + static long long int lastTick = 0LL; - if (address>>8 == 0xC6) return(slot6[address - 0xC600]); // disk ][ PROM + if ((address >> 8) == 0xC6) return(slot6[address - 0xC600]); // disk ][ PROM switch (address){ - case 0xC000: return(KBD); // keyboard - case 0xC010: KBD &= 0x7F; return(KBD); // key strobe + case 0xC000: return(KBD); // KEYBOARD + case 0xC010: KBD &= 0x7F; return(KBD); // key STROBE - case 0xC030: // Sound - SPKR = !SPKR; // toggle speaker - if (!MUTED){ - uint16_t length = (ticks - lastTick) / rate; - if (length > audioBufferSize) length = audioBufferSize; - ticks -= length << 2; // speed up CPU + case 0xC020: // TAPEOUT (shall we listen it ?) + case 0xC030: // SPEAKER + case 0xC033: // apple invader + if (!muted){ + SPKR = !SPKR; // toggle speaker + Uint32 length = (ticks - lastTick) / rate; lastTick = ticks; + if (length > audioBufferSize) length = audioBufferSize; SDL_QueueAudio(audioDevice, audioBuffer[SPKR], length); } break; - case 0xC050: if (!TEXT) TEXT = 20; break; // Graphics - case 0xC051: TEXT = 0; break; // Text - case 0xC052: TEXT = 24; break; // Full Screen - case 0xC053: TEXT = 20; break; // Mixed Screen - case 0xC054: PAGE = 1; break; // Page 1 - case 0xC055: PAGE = 2; if (HIRES) TEXT = 24; break; // Page 2 - case 0xC056: HIRES = false; break; // HiRes off - case 0xC057: HIRES = true; break; // HiRes on + case 0xC050: TEXT = false; break; // Graphics + case 0xC051: TEXT = true; break; // Text + case 0xC052: MIXED = false; break; // Mixed off + case 0xC053: MIXED = true; break; // Mixed on + case 0xC054: PAGE = 1; break; // Page 1 + case 0xC055: PAGE = 2; break; // Page 2 + case 0xC056: HIRES = false; break; // HiRes off + case 0xC057: HIRES = true; break; // HiRes on - case 0xC061: return(PB0); // Push Button 0 - case 0xC062: return(PB1); // Push Button 1 - case 0xC064: return((TGC0-=trimGC) > 192? 0x80: 0x00); // Paddle 0 - case 0xC065: return((TGC1-=trimGC) > 192? 0x80: 0x00); // Paddle 1 - case 0xC070: TGC0 = GC0; TGC1 = GC1; break; // paddle timer RST + case 0xC061: return(PB0); // Push Button 0 + case 0xC062: return(PB1); // Push Button 1 + case 0xC063: return(PB2); // Push Button 2 + case 0xC064: return((TGC0-=trimGC) > 192? 0x80: 0x00); // Paddle 0 + case 0xC065: return((TGC1-=trimGC) > 192? 0x80: 0x00); // Paddle 1 + case 0xC070: TGC0 = GC0; TGC1 = GC1; break; // paddle timer RST - case 0xC0E0: // PHASE0OFF - case 0xC0E1: // PHASE0ON - case 0xC0E2: // PHASE1OFF - case 0xC0E3: // PHASE1ON - case 0xC0E4: // PHASE2OFF - case 0xC0E5: // PHASE2ON - case 0xC0E6: // PHASE3OFF - case 0xC0E7: stepMotor(address); break; // PHASE3ON + case 0xC0E0: // PHASE0OFF + case 0xC0E1: // PHASE0ON + case 0xC0E2: // PHASE1OFF + case 0xC0E3: // PHASE1ON + case 0xC0E4: // PHASE2OFF + case 0xC0E5: // PHASE2ON + case 0xC0E6: // PHASE3OFF + case 0xC0E7: stepMotor(address); break; // PHASE3ON - case 0xC0E8: disk[curDrv].motorOn = false; break; // MOTOROFF - case 0xC0E9: disk[curDrv].motorOn = true; break; // MOTORON + case 0xC0E8: disk[curDrv].motorOn = false; break; // MOTOROFF + case 0xC0E9: disk[curDrv].motorOn = true; break; // MOTORON - case 0xC0EA: // DRIVE0EN + case 0xC0EA: // DRIVE0EN disk[0].motorOn = disk[1].motorOn || disk[0].motorOn; disk[1].motorOn = false; curDrv = 0; break; - case 0xC0EB: // DRIVE1EN + case 0xC0EB: // DRIVE1EN disk[1].motorOn = disk[0].motorOn || disk[1].motorOn; disk[0].motorOn = false; curDrv = 1; break; - case 0xC0EC: // Shift Data Latch - if (disk[curDrv].writeMode) // writting + case 0xC0EC: // Shift Data Latch + if (disk[curDrv].writeMode) // writting disk[curDrv].data[disk[curDrv].track*0x1A00+disk[curDrv].nibble]=dLatch; - else // reading + else // reading dLatch=disk[curDrv].data[disk[curDrv].track*0x1A00+disk[curDrv].nibble]; - disk[curDrv].nibble = (disk[curDrv].nibble+1)%0x1A00; // turn floppy + disk[curDrv].nibble = (disk[curDrv].nibble+1)%0x1A00; // turn floppy return(dLatch); - case 0xC0ED: dLatch = value; break; // Load Data Latch + case 0xC0ED: dLatch = value; break; // Load Data Latch - case 0xC0EE: // latch for READ - disk[curDrv].writeMode = false; - return(disk[curDrv].readOnly ? 0x80 : 0); // check protection + case 0xC0EE: // latch for READ + disk[curDrv].writeMode = false; + return(disk[curDrv].readOnly ? 0x80 : 0); // check protection - case 0xC0EF: disk[curDrv].writeMode = true; break; // latch for WRITE + case 0xC0EF: disk[curDrv].writeMode = true; break; // latch for WRITE + + default: printf("Uncaught Soft Switch access at %04X\n", address); } - return(0); // catch all + + return(0); // catch all } @@ -193,60 +239,14 @@ uint8_t softSwitches(uint16_t address, uint8_t value){ int main(int argc, char *argv[]){ - // VM INITIALIZATION - - FILE *f = fopen("appleII+.rom", "rb"); // load the Apple II+ ROM - if (f){ - if (fread(rom, 1, ROMSIZE, f) != ROMSIZE){ - printf("appleII+.rom should be 12 K Bytes\n"); - return(1); - } - fclose(f); - } - else { - printf("Could not open appleII+.rom\n"); - return(2); - } - - f = fopen("diskII.rom", "rb"); // load the disk ][ PROM - if (f){ - if (fread(slot6, 1, 256, f) != 256){ - printf("diskII.rom should be 256 Bytes\n"); - return(3); - } - fclose(f); - } - else { - printf("Could not open diskII.rom\n"); - return(4); - } - - if (argc > 1){ // load .nib in drive 0 - f = fopen(argv[1], "rb"); // open it in read binary - if (f){ - if (fread(disk[0].data, 1, 232960, f) != 232960){ - printf("This floppy image should be 232960 Bytes\n"); - return(5); - } - fclose(f); - sprintf(disk[0].filename,"%s", argv[1]); // update disk filename - - f = fopen(argv[1], "ab"); // check if file is writeable - if (f){ - disk[0].readOnly = true; // f will be NULL if open failed - fclose(f); - } - else disk[0].readOnly = false; - } - } - - puce6502Reset(); // reset the 6502 - - // SDL INITIALIZATION int zoom = 2; - char title[1024] = "reinette II+"; + const float frameDelay = 1000/60; // targeting 60 FPS + float fps = 60; + Uint32 frameStart = 0, frameTime = 0, frame = 0, reftime = 0, blink = 0; + SDL_Event event; + bool paused = false, running = true, ctrl, shift, alt; if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[DEBUG] > %s", SDL_GetError()); @@ -255,30 +255,27 @@ int main(int argc, char *argv[]){ SDL_EventState(SDL_DROPFILE, SDL_ENABLE); - SDL_Window *wdo = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, \ + SDL_Window *wdo = SDL_CreateWindow("reinette II+", SDL_WINDOWPOS_CENTERED, \ SDL_WINDOWPOS_CENTERED, 280*zoom, 192*zoom, SDL_WINDOW_OPENGL); - SDL_Renderer *rdr = SDL_CreateRenderer(wdo, -1, SDL_RENDERER_ACCELERATED); + SDL_Surface *icon = SDL_LoadBMP("icon.bmp"); // add an icon to the window title bar + SDL_SetColorKey(icon, SDL_TRUE, SDL_MapRGB(icon->format, 255, 255, 255)); + SDL_SetWindowIcon(wdo, icon); - SDL_SetRenderDrawBlendMode(rdr, SDL_BLENDMODE_BLEND); + SDL_Renderer *rdr = SDL_CreateRenderer(wdo, -1, SDL_RENDERER_ACCELERATED); // | SDL_RENDERER_PRESENTVSYNC); + SDL_SetRenderDrawBlendMode(rdr, SDL_BLENDMODE_NONE); // SDL_BLENDMODE_BLEND); SDL_RenderSetScale(rdr, zoom, zoom); - const int frameDelay = 1000/60; // targeting 60 FPS - Uint32 frameStart = 0, frameTime = 0, frame = 0; - SDL_Event event; - bool paused = false, running = true, ctrl, shift, alt; - // SDL AUDIO INITIALIZATION - SDL_AudioSpec wavSpec = {20000, AUDIO_U8, 1, 0, 8, 0, 0, NULL, NULL}; - audioDevice = SDL_OpenAudioDevice(NULL, 0, &wavSpec, NULL, 0); - SDL_PauseAudioDevice(audioDevice, MUTED); + SDL_AudioSpec desired = {44100, AUDIO_S8, 1, 0, 4096, 0, 0, NULL, NULL}; + audioDevice = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, SDL_FALSE); + SDL_PauseAudioDevice(audioDevice, muted); - // two audio buffers, one when the speaker is 'on', the other when it's 'off' - for (int i=0; i 1) insertFloppy(wdo, argv[1], 0); // load .nib in drive 0 + + puce6502Reset(); // reset the 6502 //================================================================== MAIN LOOP + reftime = SDL_GetTicks(); + while (running){ - frameStart = SDL_GetTicks(); // start of a new frame - - - //================================================================== RUN CPU + frameStart = SDL_GetTicks(); // start of a new frame if (!paused){ - puce6502Exec(16666); // execute 1000000/60 cycles at each frame - if (disk[curDrv].motorOn) // drive is active - puce6502Exec(500000); // artificial drive speed up + puce6502Exec((long long int)(1023000.0 / fps)); // adjusted ~1M/60 to the actual frame rate + int i; + i = 0; + while (disk[curDrv].motorOn && i < 50){ // until motor is off or i reaches 50 + puce6502Exec(10000); // artificial drive speed up + i++; + } } - - //=========================================================== KEYBOARD INPUT + //=============================================================== USER INPUT while (SDL_PollEvent(&event)){ - ctrl = SDL_GetModState() & KMOD_CTRL ? true : false; alt = SDL_GetModState() & KMOD_ALT ? true : false; + ctrl = SDL_GetModState() & KMOD_CTRL ? true : false; shift = SDL_GetModState() & KMOD_SHIFT ? true : false;; - PB0 = alt ? 0xFF : 0x00; // update push button 0 - PB1 = ctrl ? 0xFF : 0x00; // update push button 1 + PB0 = alt ? 0xFF : 0x00; // update push button 0 + PB1 = ctrl ? 0xFF : 0x00; // update push button 1 + PB2 = shift ? 0xFF : 0x00; // update push button 2 + if (event.type == SDL_QUIT) running = false; // WM sent TERM signal - if (event.type == SDL_QUIT) running = false; // WM sent TERM signal + // if (event.type == SDL_WINDOWEVENT){ + // if(event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) + // paused = true; + // if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) + // paused = false; + // } - - if (event.type == SDL_DROPFILE){ // user dropped a file - - char* filename = event.drop.file; // get full pathname - f = fopen(filename, "rb"); // open it in read binary - if (f){ - fread(disk[alt].data, 1, 232960, f); // if ALT : drv 1 else drv 0 - fclose(f); - } - - f = fopen(filename, "ab"); // check if file is writable - if (f) { - disk[alt].readOnly = true; // f is NULL if open failed - fclose(f); - } - else disk[alt].readOnly = false; - - sprintf(disk[alt].filename,"%s", filename); // update disk filename - SDL_free(filename); // free filename memory - - int i, a, b; // updates window title - i = a = 0; - while (disk[0].filename[i] != 0) // find start of filename - if (disk[0].filename[i++] == '\\') a = i; // for disk0 - i = b = 0; - while (disk[1].filename[i] != 0) // find start of filename - if (disk[1].filename[i++] == '\\') b = i; // for disk1 - sprintf(title, "reinette II+ D1: %s D2: %s", \ - disk[0].filename + a, disk[1].filename + b); - - SDL_SetWindowTitle(wdo, title); // set the title - paused = false; // might already be the case - if (!alt) puce6502Goto(0xC600); // force reboot + if (event.type == SDL_DROPFILE){ // user dropped a file + char* filename = event.drop.file; // get full pathname + insertFloppy(wdo, filename, alt); // if ALT : drv 1 else drv 0 + SDL_free(filename); // free filename memory + paused = false; // might already be the case + if (!(alt || ctrl)) // unless ALT or CTRL were + puce6502Goto(0xC600); // pressed, force reboot } - - if (event.type == SDL_KEYDOWN) // a key has been pressed + if (event.type == SDL_KEYDOWN) // a key has been pressed switch (event.key.keysym.sym){ // EMULATOR CONTROL : - case SDLK_F1: // save disk 0 back to host + case SDLK_F1: // save disk 0 back to host if (disk[0].filename[0] && !disk[0].readOnly){ f = fopen(disk[0].filename, "wb"); if (f){ if (fwrite(disk[0].data, 1, 232960, f) != 232960){ printf("Write failed\n"); - return(20); } fclose(f); } } break; - case SDLK_F2: // save disk 1 back to host + case SDLK_F2: // save disk 1 back to host if (disk[1].filename[0] && !disk[1].readOnly){ f = fopen(disk[1].filename, "wb"); if (f){ if (fwrite(disk[1].data, 1, 232960, f) != 232960){ printf("Write failed\n"); - return(21); } fclose(f); } } break; + case SDLK_F3: paused = !paused; break; // pause / un-pause - case SDLK_F3: paused = !paused; break; // - - case SDLK_F4: // paste txt from clipboard + case SDLK_F4: // paste txt from clipboard if (SDL_HasClipboardText()){ char *clipboardText = SDL_GetClipboardText(); int c = 0; - while (clipboardText[c]){ // all chars until ascii NUL - KBD = clipboardText[c++] | 0x80; // set bit7 - if (KBD == 0x8A) KBD = 0x8D; // Line Feed to Carriage Ret - puce6502Exec(400000); // to process each char + while (clipboardText[c]){ // all chars until ascii NUL + KBD = clipboardText[c++] | 0x80; // set bit7 + if (KBD == 0x8A) KBD = 0x8D; // Line Feed to Carriage Ret + puce6502Exec(400000); // to process each char } SDL_free(clipboardText); } break; - - case SDLK_F5: if ((zoom-=2) < 0) zoom = 0; // zoom out - case SDLK_F6: if (++zoom > 8) zoom = 8; // zoom in + case SDLK_F5: if ((zoom-=2) < 0) zoom = 0; // zoom out + case SDLK_F6: if (++zoom > 8) zoom = 8; // zoom in SDL_SetWindowSize(wdo, 280*zoom, 192*zoom); SDL_RenderSetScale(rdr, zoom, zoom); break; + case SDLK_F7: trimGC -= .01; break; // PDL Trim + case SDLK_F8: trimGC += .01; break; // PDL Trim - case SDLK_F7: trimGC -= .01; break; // PDL Trim - case SDLK_F8: trimGC += .01; break; // PDL Trim + case SDLK_F9: muted = !muted; break; // mute + case SDLK_F10: monochrome = !monochrome; break; // ... - case SDLK_F9: MUTED = !MUTED; break; // mute - - case SDLK_F10: MONOCHROME = !MONOCHROME; break; // - - case SDLK_F11: // reset + case SDLK_F11: // reset if (ctrl) puce6502Break(); else { puce6502Reset(); - softSwitches(0xC0E8,0); // motorOff - softSwitches(0xC0E9,0); // drive0En + softSwitches(0xC0E9,0); // drive0En + softSwitches(0xC0E8,0); // motorOff } break; - case SDLK_F12: running = false; break; // goodbye - + case SDLK_F12: running = false; break; // goodbye // EMULATED KEYS : - case SDLK_ESCAPE: KBD = 0x9B; break; // ESC - case SDLK_RETURN: KBD = 0x8D; break; // CR - case SDLK_DELETE: KBD = 0x80; break; // DEL->NUL - case SDLK_LEFT: KBD = 0x88; break; // BS - case SDLK_RIGHT: KBD = 0x95; break; // NAK - case SDLK_BACKSPACE: KBD = 0x88; break; // BS - case SDLK_SPACE: KBD = 0xA0; break; - case SDLK_a: KBD = ctrl ? 0x81: 0xC1; break; // a - case SDLK_b: KBD = ctrl ? 0x82: 0xC2; break; // b STX - case SDLK_c: KBD = ctrl ? 0x83: 0xC3; break; // c ETX - case SDLK_d: KBD = ctrl ? 0x84: 0xC4; break; // d EOT - case SDLK_e: KBD = ctrl ? 0x85: 0xC5; break; // e - case SDLK_f: KBD = ctrl ? 0x86: 0xC6; break; // f ACK - case SDLK_g: KBD = ctrl ? 0x87: 0xC7; break; // g BELL - case SDLK_h: KBD = ctrl ? 0x88: 0xC8; break; // h BS - case SDLK_i: KBD = ctrl ? 0x89: 0xC9; break; // i HTAB - case SDLK_j: KBD = ctrl ? 0x8A: 0xCA; break; // j LF - case SDLK_k: KBD = ctrl ? 0x8B: 0xCB; break; // k VTAB - case SDLK_l: KBD = ctrl ? 0x8C: 0xCC; break; // l FF - case SDLK_m: KBD = ctrl ? 0x8D: 0xCD; break; // m CR - case SDLK_n: KBD = ctrl ? 0x8E: 0xCE; break; // n - case SDLK_o: KBD = ctrl ? 0x8F: 0xCF; break; // o - case SDLK_p: KBD = ctrl ? 0x90: 0xD0; break; // p - case SDLK_q: KBD = ctrl ? 0x91: 0xD1; break; // q - case SDLK_r: KBD = ctrl ? 0x92: 0xD2; break; // r - case SDLK_s: KBD = ctrl ? 0x93: 0xD3; break; // s ESC - case SDLK_t: KBD = ctrl ? 0x94: 0xD4; break; // t - case SDLK_u: KBD = ctrl ? 0x95: 0xD5; break; // u NAK - case SDLK_v: KBD = ctrl ? 0x96: 0xD6; break; // v - case SDLK_w: KBD = ctrl ? 0x97: 0xD7; break; // w - case SDLK_x: KBD = ctrl ? 0x98: 0xD8; break; // x CANCEL - case SDLK_y: KBD = ctrl ? 0x99: 0xD9; break; // y - case SDLK_z: KBD = ctrl ? 0x9A: 0xDA; break; // z - case SDLK_0: KBD = shift? 0xA9: 0xB0; break; // 0 ) - case SDLK_1: KBD = shift? 0xA1: 0xB1; break; // 1 ! - case SDLK_2: KBD = shift? 0xC0: 0xB2; break; // 2 @ - case SDLK_3: KBD = shift? 0xA3: 0xB3; break; // 3 # - case SDLK_4: KBD = shift? 0xA4: 0xB4; break; // 4 $ - case SDLK_5: KBD = shift? 0xA5: 0xB5; break; // 5 % - case SDLK_6: KBD = shift? 0xDE: 0xB6; break; // 6 ^ - case SDLK_7: KBD = shift? 0xA6: 0xB7; break; // 7 & - case SDLK_8: KBD = shift? 0xAA: 0xB8; break; // 8 * - case SDLK_9: KBD = shift? 0xA8: 0xB9; break; // 9 ( - case SDLK_QUOTE: KBD = shift? 0xA2: 0xA7; break; // ' " - case SDLK_EQUALS: KBD = shift? 0xAB: 0xBD; break; // = + - case SDLK_SEMICOLON: KBD = shift? 0xBA: 0xBB; break; // ; : - case SDLK_COMMA: KBD = shift? 0xBC: 0xAC; break; // , < - case SDLK_PERIOD: KBD = shift? 0xBE: 0xAE; break; // . > - case SDLK_SLASH: KBD = shift? 0xBF: 0xAF; break; // / ? - case SDLK_MINUS: KBD = shift? 0xDF: 0xAD; break; // - _ - case SDLK_LEFTBRACKET: KBD = shift? 0xFB: 0xDB; break; // [ { - case SDLK_BACKSLASH: KBD = shift? 0xFC: 0xDC; break; // \ | - case SDLK_RIGHTBRACKET: KBD = shift? 0xFD: 0xDD; break; // ] } - case SDLK_BACKQUOTE: KBD = shift? 0xFE: 0xE0; break; // ` ~ - case SDLK_KP_1: GC0 = 192; break; // pdl0 <- - case SDLK_KP_3: GC0 = 255; break; // pdl0 -> - case SDLK_KP_5: GC1 = 192; break; // pdl1 <- - case SDLK_KP_2: GC1 = 255; break; // pdl1 -> + case SDLK_ESCAPE: KBD = 0x9B; break; // ESC + case SDLK_RETURN: KBD = 0x8D; break; // CR + case SDLK_DELETE: KBD = 0x80; break; // DEL->NUL + case SDLK_LEFT: KBD = 0x88; break; // BS + case SDLK_RIGHT: KBD = 0x95; break; // NAK + case SDLK_BACKSPACE: KBD = 0x88; break; // BS + case SDLK_SPACE: KBD = 0xA0; break; + case SDLK_a: KBD = ctrl ? 0x81: 0xC1; break; // a + case SDLK_b: KBD = ctrl ? 0x82: 0xC2; break; // b STX + case SDLK_c: KBD = ctrl ? 0x83: 0xC3; break; // c ETX + case SDLK_d: KBD = ctrl ? 0x84: 0xC4; break; // d EOT + case SDLK_e: KBD = ctrl ? 0x85: 0xC5; break; // e + case SDLK_f: KBD = ctrl ? 0x86: 0xC6; break; // f ACK + case SDLK_g: KBD = ctrl ? 0x87: 0xC7; break; // g BELL + case SDLK_h: KBD = ctrl ? 0x88: 0xC8; break; // h BS + case SDLK_i: KBD = ctrl ? 0x89: 0xC9; break; // i HTAB + case SDLK_j: KBD = ctrl ? 0x8A: 0xCA; break; // j LF + case SDLK_k: KBD = ctrl ? 0x8B: 0xCB; break; // k VTAB + case SDLK_l: KBD = ctrl ? 0x8C: 0xCC; break; // l FF + case SDLK_m: KBD = ctrl ? 0x8D: 0xCD; break; // m CR + case SDLK_n: KBD = ctrl ? 0x8E: 0xCE; break; // n + case SDLK_o: KBD = ctrl ? 0x8F: 0xCF; break; // o + case SDLK_p: KBD = ctrl ? 0x90: 0xD0; break; // p + case SDLK_q: KBD = ctrl ? 0x91: 0xD1; break; // q + case SDLK_r: KBD = ctrl ? 0x92: 0xD2; break; // r + case SDLK_s: KBD = ctrl ? 0x93: 0xD3; break; // s ESC + case SDLK_t: KBD = ctrl ? 0x94: 0xD4; break; // t + case SDLK_u: KBD = ctrl ? 0x95: 0xD5; break; // u NAK + case SDLK_v: KBD = ctrl ? 0x96: 0xD6; break; // v + case SDLK_w: KBD = ctrl ? 0x97: 0xD7; break; // w + case SDLK_x: KBD = ctrl ? 0x98: 0xD8; break; // x CANCEL + case SDLK_y: KBD = ctrl ? 0x99: 0xD9; break; // y + case SDLK_z: KBD = ctrl ? 0x9A: 0xDA; break; // z + case SDLK_0: KBD = shift? 0xA9: 0xB0; break; // 0 ) + case SDLK_1: KBD = shift? 0xA1: 0xB1; break; // 1 ! + case SDLK_2: KBD = shift? 0xC0: 0xB2; break; // 2 + case SDLK_3: KBD = shift? 0xA3: 0xB3; break; // 3 # + case SDLK_4: KBD = shift? 0xA4: 0xB4; break; // 4 $ + case SDLK_5: KBD = shift? 0xA5: 0xB5; break; // 5 % + case SDLK_6: KBD = shift? 0xDE: 0xB6; break; // 6 ^ + case SDLK_7: KBD = shift? 0xA6: 0xB7; break; // 7 & + case SDLK_8: KBD = shift? 0xAA: 0xB8; break; // 8 * + case SDLK_9: KBD = shift? 0xA8: 0xB9; break; // 9 ( + case SDLK_QUOTE: KBD = shift? 0xA2: 0xA7; break; // ' " + case SDLK_EQUALS: KBD = shift? 0xAB: 0xBD; break; // = + + case SDLK_SEMICOLON: KBD = shift? 0xBA: 0xBB; break; // ; : + case SDLK_COMMA: KBD = shift? 0xBC: 0xAC; break; // , < + case SDLK_PERIOD: KBD = shift? 0xBE: 0xAE; break; // . > + case SDLK_SLASH: KBD = shift? 0xBF: 0xAF; break; // / ? + case SDLK_MINUS: KBD = shift? 0xDF: 0xAD; break; // - _ + case SDLK_LEFTBRACKET: KBD = shift? 0xFB: 0xDB; break; // [ { + case SDLK_BACKSLASH: KBD = shift? 0xFC: 0xDC; break; // \ | + case SDLK_RIGHTBRACKET: KBD = shift? 0xFD: 0xDD; break; // ] } + case SDLK_BACKQUOTE: KBD = shift? 0xFE: 0xE0; break; // ` ~ + + // EMULATED JOYSTICK + + case SDLK_KP_1: GC0 = 192; break; // pdl0 <- + case SDLK_KP_3: GC0 = 255; break; // pdl0 -> + case SDLK_KP_5: GC1 = 192; break; // pdl1 <- + case SDLK_KP_2: GC1 = 255; break; // pdl1 -> } if (event.type == SDL_KEYUP) switch (event.key.keysym.sym){ - case SDLK_KP_1: GC0 = 224; break; // reset - case SDLK_KP_3: GC0 = 224; break; // the - case SDLK_KP_5: GC1 = 224; break; // paddles - case SDLK_KP_2: GC1 = 224; break; // to center + case SDLK_KP_1: GC0 = 224; break; // reset + case SDLK_KP_3: GC0 = 224; break; // the + case SDLK_KP_5: GC1 = 224; break; // paddles + case SDLK_KP_2: GC1 = 224; break; // to center } } - //============================================================= VIDEO OUTPUT - //======================================================== HIGH RES GRAPHICS + // HIGH RES GRAPHICS - if (HIRES){ - int word, bits[16], bit, pbit, colorSet; - bool even; + if (!TEXT && HIRES){ + int word, bits[16], bit, pbit, colorSet, even; + vRamBase = PAGE * 0x2000; // PAGE is 1 or 2 + lineLimit = MIXED ? 160 : 192; - vRamBase = PAGE * 0x2000; // PAGE is 1 or 2 - - for (int line=0; line> bit) & 1; // store all bits of the word into the 'bits' array - // store all bits of the word into the bits array - for (bit=0; bit<16; bit++) bits[bit] = (word >> bit) & 1; + colorSet = bits[7] * 4; // select the right color set + pbit = previousBit[line][col]; // the bit value of the left dot + bit = 0; // starting at 1st bit of 1st byte - colorSet = bits[7] * 4; // select the right color set - pbit = previousBit[line][col]; - bit = 0; // starting at 1st bit of 1st byte + while (bit < 15){ // until we reach bit7 of 2nd byte - while (bit < 15){ // until we reach bit7 of 2nd byte - - if (bit == 7){ // moving into the second byte - colorSet = bits[15] * 4; // update the color set - pbit = bits[6]; - bit++; // skip bit 7 + if (bit == 7){ // moving into the second byte + colorSet = bits[15] * 4; // update the color set + bit++; // skip bit 7 } - if (MONOCHROME) - colorIdx = bits[bit] * 3; + if (monochrome) + colorIdx = bits[bit] * 3; // black if bit==0, white if bit==1 else - colorIdx = colorSet + (bits[bit] << 1) + (pbit); + colorIdx = even + colorSet + (bits[bit] << 1) + (pbit); - if (even) - SDL_SetRenderDrawColor(rdr, hcolor[colorIdx][0], \ - hcolor[colorIdx][1], hcolor[colorIdx][2], SDL_ALPHA_OPAQUE); - else - SDL_SetRenderDrawColor(rdr, dhcolor[colorIdx][0], \ - dhcolor[colorIdx][1], dhcolor[colorIdx][2], SDL_ALPHA_OPAQUE); + SDL_SetRenderDrawColor(rdr, hcolor[colorIdx][0], \ + hcolor[colorIdx][1], hcolor[colorIdx][2], SDL_ALPHA_OPAQUE); + SDL_RenderDrawPoint(rdr, x++, line); - SDL_RenderDrawPoint(rdr, x, line); - - x++; // proceed to the next dot - pbit = bits[bit]; - bit++; - even = !even; - } // while (bit < 15) - - previousDots[line][col] = word; // update the video cache - - if ((col < 37) && (previousBit[line][col + 2] != pbit)){ - previousBit[line][col + 2] = pbit; // check for color franging - previousDots[line][col + 2] = -1; + pbit = bits[bit++]; // proceed to the next pixel + even = even ? 0 : 8; // one pixel every two is darker } - } // if (previousDots[line][col] ... + previousDots[line][col] = word; // update the video cache + if ((col < 37) && (previousBit[line][col + 2] != pbit)){ // check it this dot has a color franging effect on the next dot + previousBit[line][col + 2] = pbit; // set pbit and clear the + previousDots[line][col + 2] = -1; // video cache for next dot + } + + } // if (previousDots[line][col] ... } } - } // if (HIRES) + } + // lOW RES GRAPHICS - - - //========================================================= lOW RES GRAPHICS - - else if (TEXT){ // not in full text + else if (!TEXT){ // not in text vRamBase = PAGE * 0x0400; - for (int col=0; col<40; col++){ // for each column + lineLimit = MIXED ? 20 : 24; + + for (int col=0; col<40; col++){ // for each column pixelGR.x = col * 7; - for (int line=0; line> 4; // second nibble + pixelGR.y += 4; // second block + colorIdx = (glyph & 0xF0) >> 4; // second nibble SDL_SetRenderDrawColor(rdr, color[colorIdx][0], \ color[colorIdx][1], color[colorIdx][2], SDL_ALPHA_OPAQUE); SDL_RenderFillRect(rdr, &pixelGR); @@ -671,28 +658,29 @@ int main(int argc, char *argv[]){ } } + // TEXT 40 COLUMNS - //========================================================== TEXT 40 COLUMNS - - if (TEXT != 24){ + if (TEXT || MIXED){ vRamBase = PAGE * 0x0400; - for (int col=0; col<40; col++){ // for each column + lineLimit = TEXT ? 0 : 20; + + for (int col=0; col<40; col++){ // for each column dstRect.x = col * 7; - for (int line=TEXT; line<24; line++){ // for each row + for (int line=lineLimit; line<24; line++){ // for each row dstRect.y = line * 8; - glyph = ram[vRamBase + offsetGR[line] + col]; // read video memory + glyph = ram[vRamBase + offsetGR[line] + col]; // read video memory - if (glyph < 0x40) glyphAttribute = A_INVERSE; // is INVERSE ? - else if (glyph > 0x7F) glyphAttribute = A_NORMAL; // is NORMAL ? - else glyphAttribute = A_FLASH; // it's FLASH ! + if (glyph > 0x7F) glyphAttribute = A_NORMAL; // is NORMAL ? + else if (glyph < 0x40) glyphAttribute = A_INVERSE; // is INVERSE ? + else glyphAttribute = A_FLASH; // it's FLASH ! - glyph &= 0x7F; // unset bit 7 + glyph &= 0x7F; // unset bit 7 - if (glyph > 0x5F) glyph &= 0x3F; // shifts to match - if (glyph < 0x20) glyph |= 0x40; // the ASCII codes + if (glyph > 0x5F) glyph &= 0x3F; // shifts to match + if (glyph < 0x20) glyph |= 0x40; // the ASCII codes - if (glyphAttribute==A_NORMAL || (glyphAttribute==A_FLASH && frame<15)) + if (glyphAttribute == A_NORMAL || blink < 15) SDL_RenderCopy(rdr, normCharTexture, &charRects[glyph], &dstRect); else SDL_RenderCopy(rdr, revCharTexture, &charRects[glyph], &dstRect); @@ -700,29 +688,36 @@ int main(int argc, char *argv[]){ } } - //====================================================== DISPLAY DISK STATUS - if (disk[curDrv].motorOn){ // drive is active + if (disk[curDrv].motorOn){ // drive is active if (disk[curDrv].writeMode) - SDL_SetRenderDrawColor(rdr, 255, 0, 0, 85); // red for writes + SDL_SetRenderDrawColor(rdr, 255, 0, 0, 85); // red for writes else - SDL_SetRenderDrawColor(rdr, 0, 255, 0, 85); // green for reads + SDL_SetRenderDrawColor(rdr, 0, 255, 0, 85); // green for reads - SDL_RenderFillRect(rdr, &drvRect[curDrv]); + SDL_RenderFillRect(rdr, &drvRect[curDrv]); // square actually } //========================================================= SDL RENDER FRAME - if (++frame == 30) frame = 0; // 1/2 second timer + frameTime = SDL_GetTicks() - frameStart; // frame duration + if (frameDelay > frameTime) { // do we have time ? + SDL_Delay(frameDelay - frameTime); // wait 'vsync' + SDL_RenderPresent(rdr); // swap buffers + } // else, skip frame - frameTime = SDL_GetTicks() - frameStart; // frame duration - if (frameDelay > frameTime) SDL_Delay(frameDelay - frameTime); // wait vsync + frame++; + if (frameStart > reftime + 1000){ + fps = (float)(frame * 1000.0) / (float)(frameStart - reftime); + // printf("TIME:%d FPS:%f FRAME:%d\n", frameStart - reftime, fps, frame); + frame = 0; + reftime = SDL_GetTicks(); + } - SDL_RenderPresent(rdr); // swap buffers - - } // while (running) + if (++blink == 30) blink = 0; + } // while (running) //================================================ RELEASE RESSOURSES AND EXIT