From 92fbd445099cf43df759ccff12df762ac46b6809 Mon Sep 17 00:00:00 2001 From: Shamus Hammons Date: Sun, 9 Sep 2018 22:47:51 -0500 Subject: [PATCH] First stab at adding Mockingboard support. Currently only supports one Mockingboard in slot 4, but it should be fairly trivial to add another in slot 5. Tested with Ultima 3, 4 & 5, and Mockingboard disk #1. Also, added some fixes to correct the timing of the 6502 and sound; I believe I have a good understanding of it now, even though there's still work to do to keep the main CPU thread from starving the audio thread (which still happens, but less often now). --- .gitignore | 10 +- Makefile | 1 + src/apple2.cpp | 244 ++++++++++++--- src/apple2.h | 8 + src/ay8910.cpp | 760 +++++++++++++++++++++++++++++++++++---------- src/ay8910.h | 17 +- src/mmu.cpp | 215 ++++++++++++- src/mos6522via.cpp | 20 ++ src/mos6522via.h | 31 ++ src/settings.cpp | 5 + src/settings.h | 5 + src/sound.cpp | 96 +++--- src/sound.h | 5 +- src/v65c02.cpp | 53 +++- src/v65c02.h | 38 +-- src/video.cpp | 166 ++++++---- src/video.h | 3 +- 17 files changed, 1352 insertions(+), 325 deletions(-) create mode 100644 src/mos6522via.cpp create mode 100644 src/mos6522via.h diff --git a/.gitignore b/.gitignore index a7dd4bf..539de20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,15 @@ apple2 -apple2.log +*.log +*.zip +*.state* +*.bak disks/ gmon.out obj/ bugs/ reference/ +src/gui/foooked/ +ROMs/bin2c +ROMs/from-applewin/ +res/ +docs/ diff --git a/Makefile b/Makefile index 11e3fd8..e6515e8 100644 --- a/Makefile +++ b/Makefile @@ -113,6 +113,7 @@ OBJS = \ obj/firmware.o \ obj/floppy.o \ obj/log.o \ + obj/mos6522via.o \ obj/mmu.o \ obj/sdlemu_config.o \ obj/settings.o \ diff --git a/src/apple2.cpp b/src/apple2.cpp index 246c737..cb61084 100644 --- a/src/apple2.cpp +++ b/src/apple2.cpp @@ -2,7 +2,7 @@ // Apple 2 SDL Portable Apple Emulator // // by James Hammons -// © 2017 Underground Software +// © 2018 Underground Software // // Parts loosely inspired by AppleWin by Tom Charlesworth which was based on // AppleWin by Oliver Schmidt which was based on AppleWin by Michael O'Brien. @@ -24,11 +24,11 @@ // STILL TO DO: // // - Port to SDL [DONE] -// - GUI goodies // - Weed out unneeded functions [DONE] // - Disk I/O [DONE] // - 128K IIe related stuff [DONE] -// - State loading/saving +// - State loading/saving [DONE] +// - GUI goodies // // BUGS: // @@ -45,9 +45,11 @@ #include #include #include +#include "ay8910.h" #include "firmware.h" #include "floppy.h" #include "log.h" +#include "mos6522via.h" #include "mmu.h" #include "settings.h" #include "sound.h" @@ -75,6 +77,15 @@ FloppyDrive floppyDrive; bool powerStateChangeRequested = false; uint64_t frameCycleStart; +#if 0 +uint32_t frameTicks = 0; +uint32_t frameTime[60]; +#else +uint64_t frameTicks = 0; +uint64_t frameTime[60]; +#endif +uint32_t frameTimePtr = 0; + // Exported variables uint8_t lastKeyPressed = 0; @@ -94,11 +105,15 @@ bool dhires = false; uint8_t lcState = 0x02; static bool running = true; // Machine running state flag... +#if 0 static uint32_t startTicks; +#else +static uint64_t startTicks; +#endif static bool pauseMode = false; static bool fullscreenDebounce = false; -static bool capsLock = false; -static bool capsLockDebounce = false; +//static bool capsLock = false; +//static bool capsLockDebounce = false; static bool resetKeyDown = false; static int8_t hideMouseTimeout = 60; @@ -113,6 +128,7 @@ static uint8_t keyDelay = 0; static void SaveApple2State(const char * filename); static bool LoadApple2State(const char * filename); static void ResetApple2State(void); +static void AppleTimer(uint16_t); // Local timer callback functions @@ -129,12 +145,15 @@ static bool cpuFinished = false; // NB: Apple //e Manual sez 6502 is running @ 1,022,727 Hz // This is a lie. At the end of each 65 cycle line, there is an elongated // cycle (the so-called 'long' cycle) that throws the calcs out of whack. +// So actually, it's supposed to be 1,020,484.32 Hz // Let's try a thread... // // Here's how it works: Execute 1 frame's worth, then sleep. Other stuff wakes // it up. // +static uint32_t sampleCount; +static uint64_t sampleClock, lastSampleClock; int CPUThreadFunc(void * data) { // Mutex must be locked for conditional to work... @@ -142,11 +161,15 @@ int CPUThreadFunc(void * data) SDL_mutex * cpuMutex = SDL_CreateMutex(); #ifdef CPU_THREAD_OVERFLOW_COMPENSATION - float overflow = 0.0; +// float overflow = 0.0; #endif do { +uint64_t cpuFrameTickStart = SDL_GetPerformanceCounter(); +uint64_t oldClock = mainCPU.clock; +sampleCount = 0; +sampleClock = lastSampleClock = mainCPU.clock; // decrement mainSem... #ifdef THREAD_DEBUGGING WriteLog("CPU: SDL_SemWait(mainSem);\n"); @@ -163,43 +186,48 @@ WriteLog("CPU: SDL_SemWait(mainSem);\n"); #ifdef THREAD_DEBUGGING WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n"); #endif -// for(int i=0; i<800; i++) - for(int i=0; i<786; i++) +// for(int i=0; i<786; i++) + for(int i=0; i<262; i++) { - uint32_t cycles = 21; -// overflow += 0.333333334; - overflow += 0.666666667; +// uint32_t cycles = 21; +// overflow += 0.666666667; - if (overflow > 1.0) - { - cycles++; - overflow -= 1.0; - } +// if (overflow > 1.0) +// { +// cycles++; +// overflow -= 1.0; +// } // If the CTRL+Reset key combo is being held, make sure the RESET // line stays asserted: if (resetKeyDown) mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET; - Execute65C02(&mainCPU, cycles); - WriteSampleToBuffer(); +// Execute65C02(&mainCPU, cycles); + Execute65C02(&mainCPU, 65); +// WriteSampleToBuffer(); // According to "Understanding The Apple IIe", VBL asserted after // the last byte of the screen is read and let go on the first read // of the first byte of the screen. We now know that the screen // starts on line #6 and ends on line #197 (of the vertical // counter--actual VBLANK happens on lines 230 thru 233). - vbl = ((i > 17) && (i < 592) ? true : false); +// vbl = ((i > 17) && (i < 592) ? true : false); + vbl = ((i >= 6) && (i <= 197) ? true : false); } + +WriteLog("*** Frame ran for %d cycles (%.3lf µs, %d samples).\n", mainCPU.clock - oldClock, ((double)(SDL_GetPerformanceCounter() - cpuFrameTickStart) * 1000000.0) / (double)SDL_GetPerformanceFrequency(), sampleCount); +// frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency(); /* Other timings from UTA2E: Power-up reset 32.6 msec / 512 horizontal scans Flash cycle 1.87 Hz / Vertical freq./32 Delay before auto repeat 534-801 msec / 32-48 vertical scans Auto repeat frequency 15 Hz / Vertical freq./4 -Vertical frequency 59.94 Hz -Horizontal frequency 15,734 Hz -1 NTSC frame = 17030 cycles +Vertical frequency 59.94 Hz (actually, 59.92 Hz [59.92274339401]) +Horizontal frequency 15,734 Hz (actually, 15700 Hz) +1 NTSC frame = 17030 cycles (N.B.: this works out to 1021800 cycles per sec.) +NTSC clock frequency ("composite" freq.) is 1.02048432 MHz, which is 14.31818 x (65 / (65 x 14 + 2)) MHz. 1 line = 65 cycles 70 blank lines for top margin, 192 lines for screen, (35 & 35?) VA-C,V0-5 is upcounter starting at 011111010 ($FA) to 111111111 ($1FF) @@ -297,7 +325,7 @@ bool LoadImg(char * filename, uint8_t * ram, int size) } -const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.0"; +const uint8_t stateHeader[19] = "APPLE2SAVESTATE1.1"; static void SaveApple2State(const char * filename) { WriteLog("Main: Saving Apple2 state...\n"); @@ -406,6 +434,7 @@ static bool LoadApple2State(const char * filename) // Make sure things are in a sane state before execution :-P mainCPU.RdMem = AppleReadMem; mainCPU.WrMem = AppleWriteMem; + mainCPU.Timer = AppleTimer; ResetMMUPointers(); return true; @@ -428,6 +457,14 @@ static void ResetApple2State(void) dhires = false; lcState = 0x02; ResetMMUPointers(); + ResetMBVIAs(); +#ifdef USE_NEW_AY8910 + AYReset(0); + AYReset(1); +#else + AY8910_reset(0); + AY8910_reset(1); +#endif // Without this, you can wedge the system :-/ memset(ram, 0, 0x10000); @@ -436,6 +473,76 @@ static void ResetApple2State(void) } +static double cyclesForSample = 0; +static void AppleTimer(uint16_t cycles) +{ + // Handle PHI2 clocked stuff here... + bool via1T1HitZero = (mbvia[0].timer1counter <= cycles ? true : false); + bool via2T1HitZero = (mbvia[1].timer1counter <= cycles ? true : false); + + mbvia[0].timer1counter -= cycles; + mbvia[0].timer2counter -= cycles; + mbvia[1].timer1counter -= cycles; + mbvia[1].timer2counter -= cycles; + + if (via1T1HitZero) + { + if (mbvia[0].acr & 0x40) + { + mbvia[0].timer1counter += mbvia[0].timer1latch; + + if (mbvia[0].ier & 0x40) + { + mbvia[0].ifr |= (0x80 | 0x40); + AssertLine(V65C02_ASSERT_LINE_IRQ); + } + } + else + { + mbvia[0].ier &= 0x3F; // Disable T1 interrupt (VIA #1) + } + } + + if (via2T1HitZero) + { + if (mbvia[1].acr & 0x40) + { + mbvia[1].timer1counter += mbvia[1].timer1latch; + + if (mbvia[1].ier & 0x40) + { + mbvia[1].ifr |= (0x80 | 0x40); + AssertLine(V65C02_ASSERT_LINE_NMI); + } + } + else + { + mbvia[1].ier &= 0x3F; // Disable T1 interrupt (VIA #2) + } + } + +#if 1 + // Handle sound + // 21.26009 cycles per sample @ 48000 (running @ 1,020,484.32 Hz) + // Noooooope. We need ~801 cycles per frame. Averaging about 786, so missing 15 or so. + // 16.688154500083 ms = 1 frame + cyclesForSample += (double)cycles; + + if (cyclesForSample >= 21.26009) + { +#if 0 +sampleClock = GetCurrentV65C02Clock(); +WriteLog(" cyclesForSample = %lf (%d samples, cycles=%d)\n", cyclesForSample, sampleClock - lastSampleClock, cycles); +sampleCount++; +lastSampleClock = sampleClock; +#endif + WriteSampleToBuffer(); + cyclesForSample -= 21.26009; + } +#endif +} + + #ifdef CPU_CLOCK_CHECKING uint8_t counter = 0; uint32_t totalCPU = 0; @@ -501,10 +608,21 @@ int main(int /*argc*/, char * /*argv*/[]) SetupAddressMap(); ResetMMUPointers(); + // Set up Mockingboard + memset(&mbvia[0], 0, sizeof(MOS6522VIA)); + memset(&mbvia[1], 0, sizeof(MOS6522VIA)); +//(at some point this shite will have to go into the state file...) +#ifdef USE_NEW_AY8910 + AYInit(); +#else + AY8910_InitAll(1020484, 48000); +#endif + // Set up V65C02 execution context memset(&mainCPU, 0, sizeof(V65C02REGS)); mainCPU.RdMem = AppleReadMem; mainCPU.WrMem = AppleWriteMem; + mainCPU.Timer = AppleTimer; mainCPU.cpuFlags |= V65C02_ASSERT_LINE_RESET; if (!LoadImg(settings.BIOSPath, rom + 0xC000, 0x4000)) @@ -522,6 +640,7 @@ int main(int /*argc*/, char * /*argv*/[]) } GUI::Init(sdlRenderer); + WriteLog("About to initialize audio...\n"); SoundInit(); @@ -532,6 +651,20 @@ int main(int /*argc*/, char * /*argv*/[]) WriteLog("Unable to use Apple2 state file \"%s\"!\n", settings.autoStatePath); } +/* +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. + +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. + +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. + +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). + +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. + + +*/ + running = true; InitializeEventList(); // Set frame to fire at 1/60 s interval @@ -553,7 +686,9 @@ int main(int /*argc*/, char * /*argv*/[]) while (running) { +#ifdef CPU_CLOCK_CHECKING double timeToNextEvent = GetTimeToNextEvent(); +#endif #ifndef THREADED_65C02 Execute65C02(&mainCPU, USEC_TO_M6502_CYCLES(timeToNextEvent)); @@ -609,7 +744,8 @@ WriteLog("Main: SDL_DestroyCond(cpuCond);\n"); // Letters a-z (lowercase) // // N.B.: The Apple //e keyboard maps its shift characters like most modern US -// keyboards, so this table should suffice for the shifted keys just fine. +// keyboards, so this table should suffice for the shifted keys just +// fine. // uint8_t apple2e_keycode[4][56] = { { // Normal @@ -656,6 +792,9 @@ static void FrameCallback(void) SDL_Event event; uint8_t keyIndex; + frameTimePtr = (frameTimePtr + 1) % 60; + frameTime[frameTimePtr] = startTicks; + while (SDL_PollEvent(&event)) { switch (event.type) @@ -766,7 +905,7 @@ static void FrameCallback(void) keyDown = true; // Handle Caps Lock - if (capsLock + if ((SDL_GetModState() & KMOD_CAPS) && (lastKeyPressed >= 0x61) && (lastKeyPressed <= 0x7A)) lastKeyPressed -= 0x20; @@ -814,7 +953,15 @@ static void FrameCallback(void) closedAppleDown = true; // Toggle the disassembly process else if (event.key.keysym.sym == SDLK_F11) + { dumpDis = !dumpDis; + SpawnMessage("Trace: %s", (dumpDis ? "ON" : "off")); + } + else if (event.key.keysym.sym == SDLK_F12) + { + logAYInternal = !logAYInternal; + SpawnMessage("AY Trace: %s", (logAYInternal ? "ON" : "off")); + } /*else if (event.key.keysym.sym == SDLK_F9) { @@ -831,6 +978,8 @@ static void FrameCallback(void) TogglePalette(); else if (event.key.keysym.sym == SDLK_F3) CycleScreenTypes(); + else if (event.key.keysym.sym == SDLK_F4) + ToggleTickDisplay(); else if (event.key.keysym.sym == SDLK_F5) { VolumeDown(); @@ -851,6 +1000,17 @@ static void FrameCallback(void) SpawnMessage("Volume: %s", volStr); } + else if (event.key.keysym.sym == SDLK_F7) + { + // 4th root of 2 is ~1.18920711500272 (~1.5 dB) + maxVolume /= 1.4142135f; // This attenuates by ~3 dB + SpawnMessage("MB Volume: %d", (int)maxVolume); + } + else if (event.key.keysym.sym == SDLK_F8) + { + maxVolume *= 1.4142135f; + SpawnMessage("MB Volume: %d", (int)maxVolume); + } else if (event.key.keysym.sym == SDLK_F12) { if (!fullscreenDebounce) @@ -859,22 +1019,12 @@ static void FrameCallback(void) fullscreenDebounce = true; } } - else if (event.key.keysym.sym == SDLK_CAPSLOCK) - { - if (!capsLockDebounce) - { - capsLock = !capsLock; - capsLockDebounce = true; - } - } break; case SDL_KEYUP: if (event.key.keysym.sym == SDLK_F12) fullscreenDebounce = false; - else if (event.key.keysym.sym == SDLK_CAPSLOCK) - capsLockDebounce = false; // Paddle buttons 0 & 1 else if (event.key.keysym.sym == SDLK_LALT) openAppleDown = false; @@ -1005,17 +1155,29 @@ if (counter == 60) #endif // This is the problem: If you set the interval to 16, it runs faster than -// 1/60s per frame. If you set it to 17, it runs slower. What we need is to -// have it do 16 for one frame, then 17 for two others. Then it should average -// out to 1/60s per frame every 3 frames. [And now it does!] +// 1/60s per frame. If you set it to 17, it runs slower. What we need is to +// have it do 16 for one frame, then 17 for two others. Then it should average +// out to 1/60s per frame every 3 frames. [And now it does!] +// Maybe we need a higher resolution timer, as the SDL_GetTicks() (in ms) seems +// to jitter all over the place... frameCount = (frameCount + 1) % 3; - uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0); +// uint32_t waitFrameTime = 17 - (frameCount == 0 ? 1 : 0); + // Get number of ticks burned in this frame, for displaying elsewhere +#if 0 + frameTicks = SDL_GetTicks() - startTicks; +#else + frameTicks = ((SDL_GetPerformanceCounter() - startTicks) * 1000) / SDL_GetPerformanceFrequency(); +#endif // Wait for next frame... - while (SDL_GetTicks() - startTicks < waitFrameTime) - SDL_Delay(1); +// while (SDL_GetTicks() - startTicks < waitFrameTime) +// SDL_Delay(1); +#if 0 startTicks = SDL_GetTicks(); +#else + startTicks = SDL_GetPerformanceCounter(); +#endif #if 0 uint64_t cpuCycles = GetCurrentV65C02Clock(); uint32_t cyclesBurned = (uint32_t)(cpuCycles - lastCPUCycles); diff --git a/src/apple2.h b/src/apple2.h index 6627bbe..3e8559e 100644 --- a/src/apple2.h +++ b/src/apple2.h @@ -35,4 +35,12 @@ extern bool ioudis; extern bool dhires; extern uint8_t lcState; extern uint64_t frameCycleStart; +#if 0 +extern uint32_t frameTicks; +extern uint32_t frameTime[]; +#else +extern uint64_t frameTicks; +extern uint64_t frameTime[]; +#endif +extern uint32_t frameTimePtr; diff --git a/src/ay8910.cpp b/src/ay8910.cpp index 5549248..cfb293d 100644 --- a/src/ay8910.cpp +++ b/src/ay8910.cpp @@ -1,3 +1,406 @@ +// AY-3-8910 Emulator +// +// This was written mainly from the General Instruments datasheet for the 8910 +// part. I would have used the one from MAME, but it was so poorly written and +// so utterly incomprehensible that I decided to start from scratch to see if I +// could do any better; and so here we are. I did use a bit of code from +// MAME's AY-3-8910 RNG, as it was just too neat not to use. :-) +// +// by James Hammons +// (C) 2018 Underground Software +// + +#include "ay8910.h" + +#include // for memset() +#include "log.h" +#include "sound.h" + + +struct AY_3_8910 +{ + // User visible registers + uint16_t period[3]; // Channel A-C period + int16_t volume[3]; // Channel A-C volume (non-envelope mode) + bool envEnable[3]; // Channel A-C envelope enable + bool toneEnable[3]; // Channel A-C tone enable + bool noiseEnable[3]; // Channel A-C noise enable + uint16_t noisePeriod; // Noise period (5 bits * 16) + uint32_t envPeriod; // Envelope period (16 bits * 256) + bool envAttack; // Envelope Attack bit + bool envAlternate; // Envelope Alternate bit + bool envHold; // Envelope Hold bit + // Internal registers + uint16_t count[3]; // Channel A-C current count + bool state[3]; // Channel A-C current state + uint16_t noiseCount; // Noise current count + bool noiseState; // Noise state + uint32_t envCount[3]; // Envelope current count + int16_t envDirection[3];// Envelope direction (rising, 0, or falling) + uint32_t prng; // Psuedo RNG (17 bits) +}; + + +// Maximum volume that can be generated by one voice +float maxVolume = 8192.0f; + +// Normalized volumes (zero to one) for AY-3-8910 output, in 16 steps +static float normalizedVolume[16];// = {}; + +// AY-3-8910 register IDs +enum { AY_AFINE = 0, AY_ACOARSE, AY_BFINE, AY_BCOARSE, AY_CFINE, AY_CCOARSE, + AY_NOISEPER, AY_ENABLE, AY_AVOL, AY_BVOL, AY_CVOL, AY_EFINE, AY_ECOARSE, + AY_ESHAPE, AY_PORTA, AY_PORTB }; + +// Chip structs (for up to four separate chips) +static AY_3_8910 ay[4]; + + +void AYInit(void) +{ + for(int chip=0; chip<4; chip++) + AYReset(chip); + + // Our normalized volume levels are from 0 to -48 dB, in 3 dB steps. + // N.B.: It's 3dB steps because those sound the best. Dunno what it really + // is, as nothing in the documentation tells you (it only says that + // each channel's volume is normalized from 0 to 1.0V). + float level = 1.0f; + + for(int i=15; i>=0; i--) + { + normalizedVolume[i] = level; + level /= 1.4125375446228; // 10.0 ^ (3.0 / 20.0) = 3 dB + } + + // In order to get a scale that goes from 0 to 1 smoothly, we renormalize + // our volumes so that volume[0] is actually 0, and volume[15] is 1. + // Basically, we're sliding the curve down the Y-axis so that volume[0] + // touches the X-axis, then stretching the result so that it fits into the + // interval (0, 1). + float vol0 = normalizedVolume[0]; + float vol15 = normalizedVolume[15] - vol0; + + for(int i=0; i<16; i++) + normalizedVolume[i] = (normalizedVolume[i] - vol0) / vol15; + +#if 0 + WriteLog("\nRenormalized volume, level (max=%d):\n", (int)maxVolume); + for(int i=0; i<16; i++) + WriteLog("%lf, %d\n", normalizedVolume[i], (int)(normalizedVolume[i] * maxVolume)); + WriteLog("\n"); +#endif +} +/* +Renormalized: +0.000000, 0 +0.002333, 13 +0.005628, 33 +0.010283, 61 +0.016859, 101 +0.026146, 156 +0.039266, 235 +0.057797, 346 +0.083974, 503 +0.120949, 725 +0.173178, 1039 +0.246954, 1481 +0.351165, 2106 +0.498366, 2990 +0.706294, 4237 +1.000000, 6000 +*/ + + +void AYReset(int chipNum) +{ + memset(&ay[chipNum], 0, sizeof(struct AY_3_8910)); + ay[chipNum].prng = 1; // Set correct PRNG seed +} + + +void AYWrite(int chipNum, int reg, int value) +{ +#if 0 +static char regname[16][32] = { + "AY_AFINE ", + "AY_ACOARSE ", + "AY_BFINE ", + "AY_BCOARSE ", + "AY_CFINE ", + "AY_CCOARSE ", + "AY_NOISEPER", + "AY_ENABLE ", + "AY_AVOL ", + "AY_BVOL ", + "AY_CVOL ", + "AY_EFINE ", + "AY_ECOARSE ", + "AY_ESHAPE ", + "AY_PORTA ", + "AY_PORTB " +}; +WriteLog("*** AY(%d) Reg: %s = $%02X\n", chipNum, regname[reg], value); +#endif + AY_3_8910 * chip = &ay[chipNum]; + value &= 0xFF; // Ensure passed in value is no larger than 8 bits + + switch (reg) + { + case AY_AFINE: + // The square wave period is the passed in value times 16, so we handle + // that here. + chip->period[0] = (chip->period[0] & 0xF000) | (value << 4); + break; + case AY_ACOARSE: + chip->period[0] = ((value & 0x0F) << 12) | (chip->period[0] & 0xFF0); + break; + case AY_BFINE: + chip->period[1] = (chip->period[1] & 0xF000) | (value << 4); + break; + case AY_BCOARSE: + chip->period[1] = ((value & 0x0F) << 12) | (chip->period[1] & 0xFF0); + break; + case AY_CFINE: + chip->period[2] = (chip->period[2] & 0xF000) | (value << 4); + break; + case AY_CCOARSE: + chip->period[2] = ((value & 0x0F) << 12) | (chip->period[2] & 0xFF0); + break; + case AY_NOISEPER: + // Like the square wave period, the value is the what's passed * 16. + chip->noisePeriod = (value & 0x1F) << 4; + break; + case AY_ENABLE: + chip->toneEnable[0] = (value & 0x01 ? false : true); + chip->toneEnable[1] = (value & 0x02 ? false : true); + chip->toneEnable[2] = (value & 0x04 ? false : true); + chip->noiseEnable[0] = (value & 0x08 ? false : true); + chip->noiseEnable[1] = (value & 0x10 ? false : true); + chip->noiseEnable[2] = (value & 0x20 ? false : true); + break; + case AY_AVOL: + chip->volume[0] = value & 0x0F; + chip->envEnable[0] = (value & 0x10 ? true : false); + + if (chip->envEnable[0]) + { + chip->envCount[0] = 0; + chip->volume[0] = (chip->envAttack ? 0 : 15); + chip->envDirection[0] = (chip->envAttack ? 1 : -1); + } + break; + case AY_BVOL: + chip->volume[1] = value & 0x0F; + chip->envEnable[1] = (value & 0x10 ? true : false); + + if (chip->envEnable[1]) + { + chip->envCount[1] = 0; + chip->volume[1] = (chip->envAttack ? 0 : 15); + chip->envDirection[1] = (chip->envAttack ? 1 : -1); + } + break; + case AY_CVOL: + chip->volume[2] = value & 0x0F; + chip->envEnable[2] = (value & 0x10 ? true : false); + + if (chip->envEnable[2]) + { + chip->envCount[2] = 0; + chip->volume[2] = (chip->envAttack ? 0 : 15); + chip->envDirection[2] = (chip->envAttack ? 1 : -1); + } + break; + case AY_EFINE: + // The envelope period is 256 times the passed in value + chip->envPeriod = (chip->envPeriod & 0xFF0000) | (value << 8); + break; + case AY_ECOARSE: + chip->envPeriod = (value << 16) | (chip->envPeriod & 0xFF00); + break; + case AY_ESHAPE: + chip->envAttack = (value & 0x04 ? true : false); + chip->envAlternate = (value & 0x02 ? true : false); + chip->envHold = (value & 0x01 ? true : false); + + // If the Continue bit is *not* set, the Alternate bit is forced to the + // Attack bit, and Hold is forced on. + if (!(value & 0x08)) + { + chip->envAlternate = chip->envAttack; + chip->envHold = true; + } + + // Reset all voice envelope counts... + for(int i=0; i<3; i++) + { + chip->envCount[i] = 0; + chip->envDirection[i] = (chip->envAttack ? 1 : -1); + + // Only reset the volume if the envelope is enabled! + if (chip->envEnable[i]) + chip->volume[i] = (chip->envAttack ? 0 : 15); + } + break; + } +} + + +// +// Generate one sample and quit +// +bool logAYInternal = false; +uint16_t AYGetSample(int chipNum) +{ + AY_3_8910 * chip = &ay[chipNum]; + uint16_t sample = 0; + + // Number of cycles per second to run the PSG is the 6502 clock rate + // divided by the host sample rate + const static double exactCycles = 1020484.32 / (double)SAMPLE_RATE; + static double overflow = 0; + + int fullCycles = (int)exactCycles; + overflow += exactCycles - (double)fullCycles; + + if (overflow >= 1.0) + { + fullCycles++; + overflow -= 1.0; + } + + for(int i=0; itoneEnable[j] && (chip->period[j] > 16)) + { + chip->count[j]++; + + // It's (period / 2) because one full period of a square wave + // is 0 for half of its period and 1 for the other half! + if (chip->count[j] > (chip->period[j] / 2)) + { + chip->count[j] = 0; + chip->state[j] = !chip->state[j]; + } + } + + // Envelope generator only runs if the corresponding voice flag is + // enabled. + if (chip->envEnable[j]) + { + chip->envCount[j]++; + + // It's (EP / 16) because there are 16 volume steps in each EP. + if (chip->envCount[j] > (chip->envPeriod / 16)) + { + // Attack 0 = \, 1 = / (attack lasts one EP) + // Alternate = mirror envelope's last attack + // Hold = run 1 EP, hold at level (Alternate XOR Attack) + chip->envCount[j] = 0; + + // We've hit a point where we need to make a change to the + // envelope's volume, so do it: + chip->volume[j] += chip->envDirection[j]; + + // If we hit the end of the EP, change the state of the + // envelope according to the envelope's variables. + if ((chip->volume[j] > 15) || (chip->volume[j] < 0)) + { + // Hold means we set the volume to (Alternate XOR + // Attack) and stay there after the Attack EP. + if (chip->envHold) + { + chip->volume[j] = (chip->envAttack != chip->envAlternate ? 15: 0); + chip->envDirection[j] = 0; + } + else + { + // If the Alternate bit is set, we mirror the + // Attack pattern; otherwise we reset it to the + // whatever level was set by the Attack bit. + if (chip->envAlternate) + { + chip->envDirection[j] = -chip->envDirection[j]; + chip->volume[j] += chip->envDirection[j]; + } + else + chip->volume[j] = (chip->envAttack ? 0 : 15); + } + } + } + } + } + + // Noise generator (the PRNG) runs all the time: + chip->noiseCount++; + + if (chip->noiseCount > chip->noisePeriod) + { + chip->noiseCount = 0; + + // The following is from MAME's AY-3-8910 code: + // The Pseudo Random Number Generator of the 8910 is a 17-bit shift + // register. The input to the shift register is bit0 XOR bit3 (bit0 + // is the output). This was verified on AY-3-8910 and YM2149 chips. + + // The following is a fast way to compute bit17 = bit0 ^ bit3. + // Instead of doing all the logic operations, we only check bit0, + // relying on the fact that after three shifts of the register, + // what now is bit3 will become bit0, and will invert, if + // necessary, bit14, which previously was bit17. + if (chip->prng & 0x00001) + { + // This version is called the "Galois configuration". + chip->prng ^= 0x24000; + // The noise wave *toggles* when a one shows up in bit0... + chip->noiseState = !chip->noiseState; + } + + chip->prng >>= 1; + } + } + + // We mix channels A-C here into one sample, because the Mockingboard just + // sums the output of the AY-3-8910 by tying their lines together. + // We also handle the various cases (of which there are four) of mixing + // pure tones and "noise" tones together. + for(int i=0; i<3; i++) + { + // Set the volume level scaled by the maximum volume (which can be + // altered outside of this module). + int level = (int)(normalizedVolume[chip->volume[i]] * maxVolume); + + if (chip->toneEnable[i] && !chip->noiseEnable[i]) + sample += (chip->state[i] ? level : 0); + else if (!chip->toneEnable[i] && chip->noiseEnable[i]) + sample += (chip->noiseState ? level : 0); + else if (chip->toneEnable[i] && chip->noiseEnable[i]) + sample += (chip->state[i] & chip->noiseState ? level : 0); + else if (!chip->toneEnable[i] && !chip->noiseEnable[i]) + sample += level; + } + + if (logAYInternal) + { + WriteLog(" (%d) State A,B,C: %s %s %s, Sample: $%04X, P: $%X, $%X, $%X\n", chipNum, (chip->state[0] ? "1" : "0"), (chip->state[1] ? "1" : "0"), (chip->state[2] ? "1" : "0"), sample, chip->period[0], chip->period[1], chip->period[2]); + } + + return sample; +} + + + + + +// STUFF TO DELETE... + +#if 0 + /*************************************************************************** ay8910.cpp @@ -29,9 +432,6 @@ // JLH: Commented out MAME specific crap -#include "ay8910.h" -#include // for memset() - #define MAX_OUTPUT 0x7FFF // See AY8910_set_clock() for definition of STEP @@ -41,13 +441,8 @@ struct AY8910 { int Channel; int SampleRate; -// mem_read_handler PortAread; -// mem_read_handler PortBread; -// mem_write_handler PortAwrite; -// mem_write_handler PortBwrite; int register_latch; unsigned char Regs[16]; - int lastEnable; unsigned int UpdateStep; int PeriodA, PeriodB, PeriodC, PeriodN, PeriodE; int CountA, CountB, CountC, CountN, CountE; @@ -60,7 +455,8 @@ struct AY8910 unsigned int VolTable[32]; }; -/* register id's */ +static struct AY8910 AYPSG[MAX_8910]; /* array of PSG's */ + #define AY_AFINE (0) #define AY_ACOARSE (1) #define AY_BFINE (2) @@ -75,30 +471,47 @@ struct AY8910 #define AY_EFINE (11) #define AY_ECOARSE (12) #define AY_ESHAPE (13) - -#define AY_PORTA (14) -#define AY_PORTB (15) - - -static struct AY8910 AYPSG[MAX_8910]; /* array of PSG's */ +//#define AY_PORTA (14) +//#define AY_PORTB (15) void _AYWriteReg(int n, int r, int v) { - struct AY8910 *PSG = &AYPSG[n]; +#if 1 +static char regname[16][32] = { +"AY_AFINE ", +"AY_ACOARSE ", +"AY_BFINE ", +"AY_BCOARSE ", +"AY_CFINE ", +"AY_CCOARSE ", +"AY_NOISEPER", +"AY_ENABLE ", +"AY_AVOL ", +"AY_BVOL ", +"AY_CVOL ", +"AY_EFINE ", +"AY_ECOARSE ", +"AY_ESHAPE ", +"AY_PORTA ", +"AY_PORTB " +}; +WriteLog("*** AY(%d) Reg: %s = $%02X\n", n, regname[r], v); +#endif + struct AY8910 * PSG = &AYPSG[n]; int old; PSG->Regs[r] = v; - /* A note about the period of tones, noise and envelope: for speed reasons, * - * we count down from the period to 0, but careful studies of the chip * - * output prove that it instead counts up from 0 until the counter becomes * - * greater or equal to the period. This is an important difference when the * - * program is rapidly changing the period to modulate the sound. * - * To compensate for the difference, when the period is changed we adjust * - * our internal counter. * - * Also, note that period = 0 is the same as period = 1. This is mentioned * - * in the YM2203 data sheets. However, this does NOT apply to the Envelope * + /* A note about the period of tones, noise and envelope: for speed reasons, + * we count down from the period to 0, but careful studies of the chip + * output prove that it instead counts up from 0 until the counter becomes + * greater or equal to the period. This is an important difference when the + * program is rapidly changing the period to modulate the sound. + * To compensate for the difference, when the period is changed we adjust + * our internal counter. + * Also, note that period = 0 is the same as period = 1. This is mentioned + * in the YM2203 data sheets. However, this does NOT apply to the Envelope * period. In that case, period = 0 is half as period = 1. */ switch (r) { @@ -106,7 +519,8 @@ void _AYWriteReg(int n, int r, int v) case AY_ACOARSE: PSG->Regs[AY_ACOARSE] &= 0x0F; old = PSG->PeriodA; - PSG->PeriodA = (PSG->Regs[AY_AFINE] + 256 * PSG->Regs[AY_ACOARSE]) * PSG->UpdateStep; +// PSG->PeriodA = (PSG->Regs[AY_AFINE] + 256 * PSG->Regs[AY_ACOARSE]) * PSG->UpdateStep; + PSG->PeriodA = ((PSG->Regs[AY_ACOARSE] << 8) | PSG->Regs[AY_AFINE]) * PSG->UpdateStep; if (PSG->PeriodA == 0) PSG->PeriodA = PSG->UpdateStep; @@ -157,39 +571,45 @@ void _AYWriteReg(int n, int r, int v) if (PSG->CountN <= 0) PSG->CountN = 1; break; - case AY_ENABLE: +/* case AY_ENABLE: if ((PSG->lastEnable == -1) || ((PSG->lastEnable & 0x40) != (PSG->Regs[AY_ENABLE] & 0x40))) { - /* write out 0xff if port set to input */ -// if (PSG->PortAwrite) -// (*PSG->PortAwrite)(0, (UINT8) ((PSG->Regs[AY_ENABLE] & 0x40) ? PSG->Regs[AY_PORTA] : 0xff)); // [TC: UINT8 cast] + // write out $FF if port set to input + if (PSG->PortAwrite) + (*PSG->PortAwrite)(0, (UINT8) ((PSG->Regs[AY_ENABLE] & 0x40) ? PSG->Regs[AY_PORTA] : 0xff)); // [TC: UINT8 cast] } if ((PSG->lastEnable == -1) || ((PSG->lastEnable & 0x80) != (PSG->Regs[AY_ENABLE] & 0x80))) { - /* write out 0xff if port set to input */ -// if (PSG->PortBwrite) -// (*PSG->PortBwrite)(0, (UINT8) ((PSG->Regs[AY_ENABLE] & 0x80) ? PSG->Regs[AY_PORTB] : 0xff)); // [TC: UINT8 cast] + // write out $FF if port set to input + if (PSG->PortBwrite) + (*PSG->PortBwrite)(0, (UINT8) ((PSG->Regs[AY_ENABLE] & 0x80) ? PSG->Regs[AY_PORTB] : 0xff)); // [TC: UINT8 cast] } PSG->lastEnable = PSG->Regs[AY_ENABLE]; - break; + break;*/ case AY_AVOL: PSG->Regs[AY_AVOL] &= 0x1F; PSG->EnvelopeA = PSG->Regs[AY_AVOL] & 0x10; - PSG->VolA = PSG->EnvelopeA ? PSG->VolE : PSG->VolTable[PSG->Regs[AY_AVOL] ? PSG->Regs[AY_AVOL]*2+1 : 0]; + PSG->VolA = (PSG->EnvelopeA ? PSG->VolE : + (PSG->VolTable[PSG->Regs[AY_AVOL] ? PSG->Regs[AY_AVOL] * 2 + 1 + : 0])); break; case AY_BVOL: PSG->Regs[AY_BVOL] &= 0x1F; PSG->EnvelopeB = PSG->Regs[AY_BVOL] & 0x10; - PSG->VolB = PSG->EnvelopeB ? PSG->VolE : PSG->VolTable[PSG->Regs[AY_BVOL] ? PSG->Regs[AY_BVOL]*2+1 : 0]; + PSG->VolB = (PSG->EnvelopeB ? PSG->VolE : + (PSG->VolTable[PSG->Regs[AY_BVOL] ? PSG->Regs[AY_BVOL] * 2 + 1 + : 0])); break; case AY_CVOL: PSG->Regs[AY_CVOL] &= 0x1F; PSG->EnvelopeC = PSG->Regs[AY_CVOL] & 0x10; - PSG->VolC = PSG->EnvelopeC ? PSG->VolE : PSG->VolTable[PSG->Regs[AY_CVOL] ? PSG->Regs[AY_CVOL]*2+1 : 0]; + PSG->VolC = (PSG->EnvelopeC ? PSG->VolE + : (PSG->VolTable[PSG->Regs[AY_CVOL] ? PSG->Regs[AY_CVOL] * 2 + 1 + : 0])); break; case AY_EFINE: case AY_ECOARSE: @@ -232,7 +652,7 @@ void _AYWriteReg(int n, int r, int v) just a smoother curve, we always use the YM2149 behaviour. */ PSG->Regs[AY_ESHAPE] &= 0x0F; - PSG->Attack = (PSG->Regs[AY_ESHAPE] & 0x04) ? 0x1F : 0x00; + PSG->Attack = (PSG->Regs[AY_ESHAPE] & 0x04 ? 0x1F : 0x00); if ((PSG->Regs[AY_ESHAPE] & 0x08) == 0) { @@ -260,60 +680,65 @@ void _AYWriteReg(int n, int r, int v) if (PSG->EnvelopeC) PSG->VolC = PSG->VolE; break; - case AY_PORTA: +/* case AY_PORTA: if (PSG->Regs[AY_ENABLE] & 0x40) { -// if (PSG->PortAwrite) -// (*PSG->PortAwrite)(0, PSG->Regs[AY_PORTA]); -// else -// logerror("PC %04x: warning - write %02x to 8910 #%d Port A\n",activecpu_get_pc(),PSG->Regs[AY_PORTA],n); + if (PSG->PortAwrite) + (*PSG->PortAwrite)(0, PSG->Regs[AY_PORTA]); + else + logerror("PC %04x: warning - write %02x to 8910 #%d Port A\n",activecpu_get_pc(),PSG->Regs[AY_PORTA],n); } else { -// logerror("warning: write to 8910 #%d Port A set as input - ignored\n",n); + logerror("warning: write to 8910 #%d Port A set as input - ignored\n",n); } break; case AY_PORTB: if (PSG->Regs[AY_ENABLE] & 0x80) { -// if (PSG->PortBwrite) -// (*PSG->PortBwrite)(0, PSG->Regs[AY_PORTB]); -// else -// logerror("PC %04x: warning - write %02x to 8910 #%d Port B\n",activecpu_get_pc(),PSG->Regs[AY_PORTB],n); + if (PSG->PortBwrite) + (*PSG->PortBwrite)(0, PSG->Regs[AY_PORTB]); + else + logerror("PC %04x: warning - write %02x to 8910 #%d Port B\n",activecpu_get_pc(),PSG->Regs[AY_PORTB],n); } else { -// logerror("warning: write to 8910 #%d Port B set as input - ignored\n",n); + logerror("warning: write to 8910 #%d Port B set as input - ignored\n",n); } - break; + break;*/ } } +//#define DEBUG_AY // /length/ is the number of samples we require -// NB. This should be called at twice the 6522 IRQ rate or (eg) 60Hz if no IRQ. void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed static] { +#ifdef DEBUG_AY +WriteLog("AY8910Update: chip=%d, buffer=%X, length=%d\n", chip, buffer, length); +#endif struct AY8910 * PSG = &AYPSG[chip]; - int16_t * buf1, * buf2, * buf3; - int outn; - - buf1 = buffer[0]; - buf2 = buffer[1]; - buf3 = buffer[2]; - - /* The 8910 has three outputs, each output is the mix of one of the three * - * tone generators and of the (single) noise generator. The two are mixed * - * BEFORE going into the DAC. The formula to mix each channel is: * - * (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). * - * Note that this means that if both tone and noise are disabled, the output * - * is 1, not 0, and can be modulated changing the volume. * - * * - * If the channels are disabled, set their output to 1, and increase the * - * counter, if necessary, so they will not be inverted during this update. * - * Setting the output to 1 is necessary because a disabled channel is locked * - * into the ON state (see above); and it has no effect if the volume is 0. * - * If the volume is 0, increase the counter, but don't touch the output. */ + + int16_t * buf1 = buffer[0]; + int16_t * buf2 = buffer[1]; + int16_t * buf3 = buffer[2]; + + /* The 8910 has three outputs, each output is the mix of one of the three + * tone generators and of the (single) noise generator. The two are mixed + * BEFORE going into the DAC. The formula to mix each channel is: + * (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). + * Note that this means that if both tone and noise are disabled, the + * output is 1, not 0, and can be modulated changing the volume. + * + * If the channels are disabled, set their output to 1, and increase the + * counter, if necessary, so they will not be inverted during this update. + * Setting the output to 1 is necessary because a disabled channel is + * locked into the ON state (see above); and it has no effect if the volume + * is 0. If the volume is 0, increase the counter, but don't touch the + * output. + */ + // N.B.: The bits in AY_ENABLE (0-5) are all active LOW, which means if the + // channel bit is set, it is DISABLED. 5-3 are noise, 2-0 tone. if (PSG->Regs[AY_ENABLE] & 0x01) { if (PSG->CountA <= length * STEP) @@ -323,9 +748,11 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati } else if (PSG->Regs[AY_AVOL] == 0) { - /* note that I do count += length, NOT count = length + 1. You might think * - * it's the same since the volume is 0, but doing the latter could cause * - * interferencies when the program is rapidly modulating the volume. */ + /* note that I do count += length, NOT count = length + 1. You might + * think it's the same since the volume is 0, but doing the latter + * could cause interferencies when the program is rapidly modulating + * the volume. + */ if (PSG->CountA <= length * STEP) PSG->CountA += length * STEP; } @@ -356,33 +783,39 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati PSG->CountC += length * STEP; } - /* for the noise channel we must not touch OutputN - it's also not necessary * - * since we use outn. */ + /* for the noise channel we must not touch OutputN - it's also not + * necessary since we use outn. */ if ((PSG->Regs[AY_ENABLE] & 0x38) == 0x38) /* all off */ if (PSG->CountN <= length * STEP) PSG->CountN += length * STEP; - outn = (PSG->OutputN | PSG->Regs[AY_ENABLE]); + int outn = (PSG->OutputN | PSG->Regs[AY_ENABLE]); +#ifdef DEBUG_AY +WriteLog("AY8910Update: Stepping into while (length)...\n"); +#endif /* buffering loop */ while (length) { - int vola, volb, volc; - int left; - - /* vola, volb and volc keep track of how long each square wave stays * - * in the 1 position during the sample period. */ - vola = volb = volc = 0; - - left = STEP; + /* vola, volb and volc keep track of how long each square wave stays + * in the 1 position during the sample period. + */ + int vola = 0, volb = 0, volc = 0; + int left = STEP; + +#ifdef DEBUG_AY +WriteLog("AY8910Update: Stepping into inner do loop... (length=%d)\n", length); +#endif do { - int nextevent; - - if (PSG->CountN < left) - nextevent = PSG->CountN; - else - nextevent = left; + int nextevent = (PSG->CountN < left ? PSG->CountN : left); +//Note: nextevent is 0 here when first initialized... +//so let's try this: + if (nextevent == 0) + left = 0; +#ifdef DEBUG_AY +WriteLog("AY8910Update: nextevent=$%X, left=$%X\n", nextevent, left); +#endif if (outn & 0x08) { @@ -390,14 +823,14 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati vola += PSG->CountA; PSG->CountA -= nextevent; - /* PeriodA is the half period of the square wave. Here, in each * - * loop I add PeriodA twice, so that at the end of the loop the * - * square wave is in the same status (0 or 1) it was at the start. * - * vola is also incremented by PeriodA, since the wave has been 1 * - * exactly half of the time, regardless of the initial position. * - * If we exit the loop in the middle, OutputA has to be inverted * - * and vola incremented only if the exit status of the square * - * wave is 1. */ + /* PeriodA is the half period of the square wave. Here, in each + * loop I add PeriodA twice, so that at the end of the loop the + * square wave is in the same status (0 or 1) it was at the + * start. vola is also incremented by PeriodA, since the wave + * has been 1 exactly half of the time, regardless of the + * initial position. If we exit the loop in the middle, OutputA + * has to be inverted and vola incremented only if the exit + * status of the square wave is 1. */ while (PSG->CountA <= 0) { PSG->CountA += PSG->PeriodA; @@ -408,6 +841,7 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati if (PSG->OutputA) vola += PSG->PeriodA; + break; } @@ -421,6 +855,7 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati else { PSG->CountA -= nextevent; + while (PSG->CountA <= 0) { PSG->CountA += PSG->PeriodA; @@ -530,21 +965,21 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati if (PSG->CountN <= 0) { /* Is noise output going to change? */ - if ((PSG->RNG + 1) & 0x00002) /* (bit0^bit1)? */ + if ((PSG->RNG + 1) & 0x00002) // (bit0 XOR bit1) == 1? { PSG->OutputN = ~PSG->OutputN; outn = (PSG->OutputN | PSG->Regs[AY_ENABLE]); } - /* The Random Number Generator of the 8910 is a 17-bit shift * - * register. The input to the shift register is bit0 XOR bit3 * - * (bit0 is the output). This was verified on AY-3-8910 and * - * YM2149 chips. * - * * - * The following is a fast way to compute bit17 = bit0^bit3. * - * Instead of doing all the logic operations, we only check * - * bit0, relying on the fact that after three shifts of the * - * register, what now is bit3 will become bit0, and will * + /* The Random Number Generator of the 8910 is a 17-bit shift + * register. The input to the shift register is bit0 XOR bit3 + * (bit0 is the output). This was verified on AY-3-8910 and + * YM2149 chips. + * + * The following is a fast way to compute bit17 = bit0^bit3. + * Instead of doing all the logic operations, we only check + * bit0, relying on the fact that after three shifts of the + * register, what now is bit3 will become bit0, and will * invert, if necessary, bit14, which previously was bit17. */ if (PSG->RNG & 0x00001) PSG->RNG ^= 0x24000; /* This version is called the "Galois configuration". */ @@ -557,6 +992,9 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati } while (left > 0); +#ifdef DEBUG_AY +WriteLog("AY8910Update: About to update envelope...\n"); +#endif /* update envelope */ if (PSG->Holding == 0) { @@ -564,12 +1002,19 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati if (PSG->CountE <= 0) { - do +#ifdef DEBUG_AY +WriteLog("AY8910Update: About to enter do loop... (CountEnv = $%X, CountE =$%X, PeriodE = $%X)\n", PSG->CountEnv, PSG->CountE, PSG->PeriodE); +#endif + // JLH: Sanity check... + if (PSG->PeriodE > 0) { - PSG->CountEnv--; - PSG->CountE += PSG->PeriodE; + do + { + PSG->CountEnv--; + PSG->CountE += PSG->PeriodE; + } + while (PSG->CountE <= 0); } - while (PSG->CountE <= 0); /* check envelope current position */ if (PSG->CountEnv < 0) @@ -584,8 +1029,8 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati } else { - /* if CountEnv has looped an odd number of times (usually 1), * - * invert the output. */ + /* if CountEnv has looped an odd number of times + * (usually 1), invert the output. */ if (PSG->Alternate && (PSG->CountEnv & 0x20)) PSG->Attack ^= 0x1F; @@ -594,6 +1039,7 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati } PSG->VolE = PSG->VolTable[PSG->CountEnv ^ PSG->Attack]; + /* reload volume */ if (PSG->EnvelopeA) PSG->VolA = PSG->VolE; @@ -606,7 +1052,7 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati } } -#if 0 +#if 1 *(buf1++) = (vola * PSG->VolA) / STEP; *(buf2++) = (volb * PSG->VolB) / STEP; *(buf3++) = (volc * PSG->VolC) / STEP; @@ -646,90 +1092,84 @@ void AY8910Update(int chip, int16_t ** buffer, int length) // [TC: Removed stati #endif length--; } +#ifdef DEBUG_AY +WriteLog("AY8910Update: Done.\n"); +#endif } static void AY8910_set_clock(int chip, int clock) { - struct AY8910 * PSG = &AYPSG[chip]; - - /* The step clock for the tone and noise generators is the chip clock * - * divided by 8; for the envelope generator of the AY-3-8910, it is half * - * that much (clock/16), but the envelope of the YM2149 goes twice as * - * fast, therefore again clock/8. * - * Here we calculate the number of steps which happen during one sample * - * at the given sample rate. No. of events = sample rate / (clock/8). * - * STEP is a multiplier used to turn the fraction into a fixed point * - * number. */ - PSG->UpdateStep = (unsigned int)(((double)STEP * PSG->SampleRate * 8 + clock / 2) / clock); // [TC: unsigned int cast] +// struct AY8910 * PSG = &AYPSG[chip]; + + /* The step clock for the tone and noise generators is the chip clock + * divided by 8; for the envelope generator of the AY-3-8910, it is half + * that much (clock/16), but the envelope of the YM2149 goes twice as + * fast, therefore again clock/8. + * Here we calculate the number of steps which happen during one sample + * at the given sample rate. No. of events = sample rate / (clock/8). + * STEP is a multiplier used to turn the fraction into a fixed point + * number. + */ + AYPSG[chip].UpdateStep = (unsigned int)(((double)STEP * AYPSG[chip].SampleRate * 8 + clock / 2) / clock); // [TC: unsigned int cast] } static void build_mixer_table(int chip) { - struct AY8910 * PSG = &AYPSG[chip]; - - /* calculate the volume->voltage conversion table */ - /* The AY-3-8910 has 16 levels, in a logarithmic scale (3dB per step) */ - /* The YM2149 still has 16 levels for the tone generators, but 32 for */ - /* the envelope generator (1.5dB per step). */ + /* calculate the volume->voltage conversion table + * The AY-3-8910 has 16 levels, in a logarithmic scale (3dB per step) + * The YM2149 still has 16 levels for the tone generators, but 32 for + * the envelope generator (1.5dB per step). + */ double out = MAX_OUTPUT; for(int i=31; i>0; i--) { - PSG->VolTable[i] = (unsigned int)(out + 0.5); /* round to nearest */ // [TC: unsigned int cast] + AYPSG[chip].VolTable[i] = (unsigned int)(out + 0.5); /* round to nearest */ // [TC: unsigned int cast] out /= 1.188502227; /* = 10 ^ (1.5/20) = 1.5dB */ } - PSG->VolTable[0] = 0; + AYPSG[chip].VolTable[0] = 0; } void AY8910_reset(int chip) { - int i; - struct AY8910 * PSG = &AYPSG[chip]; - - PSG->register_latch = 0; - PSG->RNG = 1; - PSG->OutputA = 0; - PSG->OutputB = 0; - PSG->OutputC = 0; - PSG->OutputN = 0xFF; - PSG->lastEnable = -1; /* force a write */ - - for(i=0; iSampleRate = sampleRate; + memset(&AYPSG[chip], 0, sizeof(struct AY8910)); + AYPSG[chip].SampleRate = sampleRate; AY8910_set_clock(chip, clock); build_mixer_table(chip); } } + void AY8910_InitClock(int clock) { for(int chip=0; chip= MAX_8910) - return NULL; - - return &AYPSG[chipNum].Regs[0]; -} diff --git a/src/ay8910.h b/src/ay8910.h index 6eb733a..639a567 100644 --- a/src/ay8910.h +++ b/src/ay8910.h @@ -3,14 +3,29 @@ #include +#define USE_NEW_AY8910 + #define MAX_8910 4 +#ifndef USE_NEW_AY8910 void _AYWriteReg(int n, int r, int v); void AY8910_reset(int chip); void AY8910Update(int chip, int16_t ** buffer, int length); void AY8910_InitAll(int clock, int sampleRate); void AY8910_InitClock(int clock); -uint8_t * AY8910_GetRegsPtr(uint16_t chipNum); +#else + +// Exported functions +void AYInit(void); +void AYReset(int chipNum); +void AYWrite(int chipNum, int reg, int value); +uint16_t AYGetSample(int chipNum); +// Exported variables +extern bool logAYInternal; +extern float maxVolume; #endif + +#endif + diff --git a/src/mmu.cpp b/src/mmu.cpp index 8a5b36f..74c80e2 100644 --- a/src/mmu.cpp +++ b/src/mmu.cpp @@ -13,8 +13,10 @@ #include "mmu.h" #include "apple2.h" +#include "ay8910.h" #include "firmware.h" #include "log.h" +#include "mos6522via.h" #include "sound.h" #include "video.h" @@ -69,6 +71,7 @@ uint8_t * mainMemoryHGRW = &ram[0x2000]; // $2000 - $3FFF (write) uint8_t * slotMemory = &rom[0xC100]; // $C100 - $CFFF uint8_t * slot3Memory = &rom[0xC300]; // $C300 - $C3FF +uint8_t * slot4Memory = &rom[0xC400]; // $C400 - $C4FF uint8_t * slot6Memory = &diskROM[0]; // $C600 - $C6FF uint8_t * lcBankMemoryR = &ram[0xD000]; // $D000 - $DFFF (read) uint8_t * lcBankMemoryW = &ram[0xD000]; // $D000 - $DFFF (write) @@ -126,6 +129,8 @@ void SwitchIOUDIS(uint16_t, uint8_t); uint8_t Slot6R(uint16_t); void Slot6W(uint16_t, uint8_t); void HandleSlot6(uint16_t, uint8_t); +uint8_t MBRead(uint16_t); +void MBWrite(uint16_t, uint8_t); uint8_t ReadButton0(uint16_t); uint8_t ReadButton1(uint16_t); uint8_t ReadPaddle0(uint16_t); @@ -196,10 +201,10 @@ AddressMap memoryMap[] = { // This will overlay the slotMemory accessors for slot 6 ROM { 0xC300, 0xC3FF, AM_ROM, &slot3Memory, 0, 0, 0 }, { 0xC600, 0xC6FF, AM_ROM, &slot6Memory, 0, 0, 0 }, + { 0xC400, 0xC4FF, AM_READ_WRITE, 0, 0, MBRead, MBWrite }, { 0xD000, 0xDFFF, AM_BANKED, &lcBankMemoryR, &lcBankMemoryW, 0, 0 }, { 0xE000, 0xFFFF, AM_BANKED, &upperMemoryR, &upperMemoryW, 0, 0 }, -// { 0x0000, 0x0000, AM_END_OF_LIST, 0, 0, 0, 0 } ADDRESS_MAP_END }; @@ -861,6 +866,208 @@ void HandleSlot6(uint16_t address, uint8_t byte) } +uint8_t MBRead(uint16_t address) +{ +#if 1 + // Not sure [Seems to work OK] + if (!slotCXROM) + { + return slot4Memory[address & 0x00FF]; + } +#endif + + uint8_t regNum = address & 0x0F; + uint8_t chipNum = (address & 0x80) >> 7; + +#if 0 + WriteLog("MBRead: address = %X [chip %d, reg %X, clock=$%X]\n", address & 0xFF, chipNum, regNum, GetCurrentV65C02Clock()); +#endif + + switch (regNum) + { + case 0x00: + return mbvia[chipNum].orb & mbvia[chipNum].ddrb; + + case 0x01: + return mbvia[chipNum].ora & mbvia[chipNum].ddra; + + case 0x02: + return mbvia[chipNum].ddrb; + + case 0x03: + return mbvia[chipNum].ddra; + + case 0x04: + return mbvia[chipNum].timer1counter & 0xFF; + + case 0x05: + return (mbvia[chipNum].timer1counter & 0xFF00) >> 8; + + case 0x06: + return mbvia[chipNum].timer1latch & 0xFF; + + case 0x07: + return (mbvia[chipNum].timer1latch & 0xFF00) >> 8; + + case 0x08: + return mbvia[chipNum].timer2counter & 0xFF; + + case 0x09: + return (mbvia[chipNum].timer2counter & 0xFF00) >> 8; + + case 0x0B: + return mbvia[chipNum].acr; + + case 0x0D: + return (mbvia[chipNum].ifr & 0x7F) + | (mbvia[chipNum].ifr & 0x7F ? 0x80 : 0); + + case 0x0E: + return mbvia[chipNum].ier | 0x80; + + default: + WriteLog("Unhandled 6522 register %X read (chip %d)\n", regNum, chipNum); + } + + return 0; +} + + +static uint8_t regLatch[2]; +void MBWrite(uint16_t address, uint8_t byte) +{ + uint8_t regNum = address & 0x0F; + uint8_t chipNum = (address & 0x80) >> 7; +/* +NOTES: +bit 7 = L/R channel select (AY chip 1 versus AY chip 2) + 0 = Left, 1 = Right + +Reg. B is connected to BC1, BDIR, RST' (bits 0, 1, 2) + +Left VIA IRQ line is tied to 6502 IRQ line +Rght VIA IRQ line is tied to 6502 NMI line + +Register Function +-------- ------------------------- +0 Output Register B +1 Output Register A +2 Data Direction Register B +3 Data Direction Register A +4 Timer 1 Low byte counter (& latch) +5 Timer 1 Hgh byte counter (& latch) +6 Timer 1 Low byte latch +7 Timer 1 Hgh byte latch (& reset IRQ flag) +B Aux Control Register +D Interrupt Flag Register +E Interrupt Enable Register + +bit 6 of ACR is like so: +0: Timed interrupt each time Timer 1 is loaded +1: Continuous interrupts + +bit 7 enables PB7 (bit 6 controls output type): +0: One shot output +1: Square wave output + + +*/ +#if 0 + WriteLog("MBWrite: address = %X, byte= %X [clock=$%X]", address & 0xFF, byte, GetCurrentV65C02Clock()); + + if (regNum == 0) + WriteLog("[OUTB -> %s%s%s]\n", (byte & 0x01 ? "BC1" : ""), (byte & 0x02 ? " BDIR" : ""), (byte & 0x04 ? " RST'" : "")); + else if (regNum == 1) + WriteLog("[OUTA -> %02X]\n", byte); + else if (regNum == 2) + WriteLog("[DDRB -> %02X]\n", byte); + else if (regNum == 3) + WriteLog("[DDRA -> %02X]\n", byte); + else + WriteLog("\n"); +#endif + + switch (regNum) + { + case 0x00: + // Control of the AY-3-8912 is thru this port pretty much... + mbvia[chipNum].orb = byte; + + if ((byte & 0x04) == 0) +#ifdef USE_NEW_AY8910 + AYReset(chipNum); +#else + AY8910_reset(chipNum); +#endif + else if ((byte & 0x03) == 0x03) + regLatch[chipNum] = mbvia[chipNum].ora; + else if ((byte & 0x03) == 0x02) +#ifdef USE_NEW_AY8910 + AYWrite(chipNum, regLatch[chipNum], mbvia[chipNum].ora); +#else + _AYWriteReg(chipNum, regLatch[chipNum], mbvia[chipNum].ora); +#endif + + break; + + case 0x01: + mbvia[chipNum].ora = byte; + break; + + case 0x02: + mbvia[chipNum].ddrb = byte; + break; + + case 0x03: + mbvia[chipNum].ddra = byte; + break; + + case 0x04: + mbvia[chipNum].timer1latch = (mbvia[chipNum].timer1latch & 0xFF00) + | byte; + break; + + case 0x05: + mbvia[chipNum].timer1latch = (mbvia[chipNum].timer1latch & 0x00FF) + | (((uint16_t)byte) << 8); + mbvia[chipNum].timer1counter = mbvia[chipNum].timer1latch; + mbvia[chipNum].ifr &= 0x3F; // Clear T1 interrupt flag + break; + + case 0x06: + mbvia[chipNum].timer1latch = (mbvia[chipNum].timer1latch & 0xFF00) + | byte; + break; + + case 0x07: + mbvia[chipNum].timer1latch = (mbvia[chipNum].timer1latch & 0x00FF) + | (((uint16_t)byte) << 8); + mbvia[chipNum].ifr &= 0x3F; // Clear T1 interrupt flag + break; + + case 0x0B: + mbvia[chipNum].acr = byte; + break; + + case 0x0D: + mbvia[chipNum].ifr &= ~byte; + break; + + case 0x0E: + if (byte & 0x80) + // Setting bits in the IER + mbvia[chipNum].ier |= byte; + else + // Clearing bits in the IER + mbvia[chipNum].ier &= ~byte; + + break; + default: + WriteLog("Unhandled 6522 register $%X write $%02X (chip %d)\n", regNum, byte, chipNum); + } +} + + uint8_t ReadButton0(uint16_t) { return (uint8_t)openAppleDown << 7; @@ -900,9 +1107,9 @@ uint8_t ReadDHIRES(uint16_t) // it actually sees the RAM access done by the video generation hardware. Some // programs exploit this, so we emulate it here. -// N.B.: frameCycles will be off by the true amount because this only increments -// by the amount of a speaker cycle, not the cycle count when the access -// happens... !!! FIX !!! +// N.B.: frameCycles will be off by the true amount because this only +// increments by the amount of a speaker cycle, not the cycle count when +// the access happens... !!! FIX !!! uint8_t ReadFloatingBus(uint16_t) { // Get the currently elapsed cycle count for this frame diff --git a/src/mos6522via.cpp b/src/mos6522via.cpp new file mode 100644 index 0000000..6632691 --- /dev/null +++ b/src/mos6522via.cpp @@ -0,0 +1,20 @@ +// Mockingboard support (6522 interface) +// +// by James Hammons +// (C) 2018 Underground Software +// + +#include "mos6522via.h" + +#include // for memset() + + +MOS6522VIA mbvia[4]; + + +void ResetMBVIAs(void) +{ + for(int i=0; i<4; i++) + memset(&mbvia[i], 0, sizeof(MOS6522VIA)); +} + diff --git a/src/mos6522via.h b/src/mos6522via.h new file mode 100644 index 0000000..4d494a9 --- /dev/null +++ b/src/mos6522via.h @@ -0,0 +1,31 @@ +// Mockingboard support +// +// by James Hammons +// (C) 2018 Underground Software +// + +#ifndef __MOS6522VIA_H__ +#define __MOS6522VIA_H__ + +#include + +struct MOS6522VIA +{ + uint8_t orb, ora; // Output Register B, A + uint8_t ddrb, ddra; // Data Direction Register B, A + uint16_t timer1counter; // Timer 1 Counter + uint16_t timer1latch; // Timer 1 Latch + uint16_t timer2counter; // Timer 2 Counter + uint8_t acr; // Auxillary Control Register + uint8_t ifr; // Interrupt Flags Register + uint8_t ier; // Interrupt Enable Register +}; + + +extern MOS6522VIA mbvia[]; + + +void ResetMBVIAs(void); + +#endif // __MOS6522VIA_H__ + diff --git a/src/settings.cpp b/src/settings.cpp index ed34ef4..3939649 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -18,6 +18,7 @@ #include #include "sdlemu_config.h" #include "log.h" +#include "video.h" using namespace std; @@ -53,6 +54,9 @@ void LoadSettings(void) settings.renderType = sdlemu_getval_int("renderType", 0); settings.autoStateSaving = sdlemu_getval_bool("autoSaveState", true); + settings.winX = sdlemu_getval_int("windowX", 250); + settings.winY = sdlemu_getval_int("windowY", 100); + // Keybindings in order of U, D, L, R, C, B, A, Op, Pa, 0-9, #, * settings.p1KeyBindings[0] = sdlemu_getval_int("p1k_up", SDL_SCANCODE_UP); settings.p1KeyBindings[1] = sdlemu_getval_int("p1k_down", SDL_SCANCODE_DOWN); @@ -110,6 +114,7 @@ void LoadSettings(void) // void SaveSettings(void) { + SDL_GetWindowPosition(sdlWindow, &settings.winX, &settings.winY); } diff --git a/src/settings.h b/src/settings.h index eaf3d27..daad84d 100644 --- a/src/settings.h +++ b/src/settings.h @@ -32,6 +32,11 @@ struct Settings uint32_t renderType; bool autoStateSaving; // Auto-state loading/saving on entry/exit + // Window settings + + int winX; + int winY; + // Keybindings in order of U, D, L, R, C, B, A, Op, Pa, 0-9, #, * uint16_t p1KeyBindings[21]; diff --git a/src/sound.cpp b/src/sound.cpp index a689567..df2de2b 100644 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -24,13 +24,13 @@ #include // For memset, memcpy #include +#include "ay8910.h" #include "log.h" // Useful defines //#define DEBUG -#define SAMPLE_RATE (48000.0) #define SAMPLES_PER_FRAME (SAMPLE_RATE / 60.0) #define CYCLES_PER_SAMPLE (1024000.0 / SAMPLE_RATE) // 32K ought to be enough for anybody @@ -45,13 +45,13 @@ static SDL_AudioSpec desired, obtained; static SDL_AudioDeviceID device; static bool soundInitialized = false; static bool speakerState = false; -static int16_t soundBuffer[SOUND_BUFFER_SIZE]; +static uint16_t soundBuffer[SOUND_BUFFER_SIZE]; static uint32_t soundBufferPos; -static uint64_t lastToggleCycles; -static SDL_cond * conditional = NULL; -static SDL_mutex * mutex = NULL; -static SDL_mutex * mutex2 = NULL; -static int16_t sample; +//static uint64_t lastToggleCycles; +//static SDL_cond * conditional = NULL; +//static SDL_mutex * mutex = NULL; +//static SDL_mutex * mutex2 = NULL; +static uint16_t sample; static uint8_t ampPtr = 12; // Start with -2047 - +2047 static int16_t amplitude[17] = { 0, 1, 2, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767 }; @@ -67,10 +67,10 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length); void SoundInit(void) { SDL_zero(desired); - desired.freq = SAMPLE_RATE; // SDL will do conversion on the fly, if it can't get the exact rate. Nice! - desired.format = AUDIO_S16SYS; // This uses the native endian (for portability)... + desired.freq = SAMPLE_RATE; // SDL will do conversion on the fly, if it can't get the exact rate. Nice! + desired.format = AUDIO_U16SYS; // This uses the native endian (for portability)... desired.channels = 1; - desired.samples = 512; // Let's try a 1/2K buffer (can always go lower) + desired.samples = 512; // Let's try a 1/2K buffer desired.callback = SDLSoundCallback; device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0); @@ -81,11 +81,11 @@ void SoundInit(void) return; } - conditional = SDL_CreateCond(); - mutex = SDL_CreateMutex(); - mutex2 = SDL_CreateMutex();// Let's try real signalling... +// conditional = SDL_CreateCond(); +// mutex = SDL_CreateMutex(); +// mutex2 = SDL_CreateMutex();// Let's try real signalling... soundBufferPos = 0; - lastToggleCycles = 0; +// lastToggleCycles = 0; sample = desired.silence; // ? wilwok ? yes SDL_PauseAudioDevice(device, 0); // Start playback! @@ -103,9 +103,9 @@ void SoundDone(void) { SDL_PauseAudioDevice(device, 1); SDL_CloseAudioDevice(device); - SDL_DestroyCond(conditional); - SDL_DestroyMutex(mutex); - SDL_DestroyMutex(mutex2); +// SDL_DestroyCond(conditional); +// SDL_DestroyMutex(mutex); +// SDL_DestroyMutex(mutex2); WriteLog("Sound: Done.\n"); } } @@ -128,28 +128,31 @@ void SoundResume(void) // // Sound card callback handler // +static uint32_t sndFrmCnt = 0; +static uint32_t lastStarve = 0; static void SDLSoundCallback(void * /*userdata*/, Uint8 * buffer8, int length8) { +sndFrmCnt++; //WriteLog("SDLSoundCallback(): begin (soundBufferPos=%i)\n", soundBufferPos); - // The sound buffer should only starve when starting which will cause it to - // lag behind the emulation at most by around 1 frame... - // (Actually, this should never happen since we fill the buffer beforehand.) - // (But, then again, if the sound hasn't been toggled for a while, then this - // makes perfect sense as the buffer won't have been filled at all!) - // (Should NOT starve now, now that we properly handle frame edges...) // Let's try using a mutex for shared resource consumption... //Actually, I think Lock/UnlockAudio() does this already... //WriteLog("SDLSoundCallback: soundBufferPos = %i\n", soundBufferPos); - SDL_mutexP(mutex2); +// SDL_mutexP(mutex2); // Recast this as a 16-bit type... - int16_t * buffer = (int16_t *)buffer8; + uint16_t * buffer = (uint16_t *)buffer8; uint32_t length = (uint32_t)length8 / 2; //WriteLog("SDLSoundCallback(): filling buffer...\n"); if (soundBufferPos < length) { +WriteLog("*** Sound buffer starved (%d short) *** [%d delta %d]\n", length - soundBufferPos, sndFrmCnt, sndFrmCnt - lastStarve); +lastStarve = sndFrmCnt; +#if 1 + for(uint32_t i=0; i= (SOUND_BUFFER_SIZE - 1)) { //WriteLog("WriteSampleToBuffer(): Waiting for sound thread. soundBufferPos=%i, SOUNDBUFFERSIZE-1=%i\n", soundBufferPos, SOUND_BUFFER_SIZE-1); - SDL_mutexV(mutex2); // Release it so sound thread can get it, - SDL_mutexP(mutex); // Must lock the mutex for the cond to work properly... - SDL_CondWait(conditional, mutex); // Sleep/wait for the sound thread - SDL_mutexV(mutex); // Must unlock the mutex for the cond to work properly... - SDL_mutexP(mutex2); // Re-lock it until we're done with it... +// SDL_mutexV(mutex2); // Release it so sound thread can get it, +// SDL_mutexP(mutex); // Must lock the mutex for the cond to work properly... +// SDL_CondWait(conditional, mutex); // Sleep/wait for the sound thread +// SDL_mutexV(mutex); // Must unlock the mutex for the cond to work properly... +// SDL_mutexP(mutex2); // Re-lock it until we're done with it... + SDL_Delay(1); } - soundBuffer[soundBufferPos++] = sample; + SDL_LockAudioDevice(device); + soundBuffer[soundBufferPos++] = sample + adjustedMockingboard; + SDL_UnlockAudioDevice(device); + +// soundBuffer[soundBufferPos++] = sample; //WriteLog("WriteSampleToBuffer(): SDL_mutexV(mutex2)\n"); - SDL_mutexV(mutex2); +// SDL_mutexV(mutex2); } @@ -212,7 +236,7 @@ void ToggleSpeaker(void) return; speakerState = !speakerState; - sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]); + sample = (speakerState ? amplitude[ampPtr] : 0);//-amplitude[ampPtr]); } diff --git a/src/sound.h b/src/sound.h index 17a3dc0..598fe1f 100644 --- a/src/sound.h +++ b/src/sound.h @@ -2,7 +2,7 @@ // SOUND.H // // by James Hammons -// (C) 2004 Underground Software +// (C) 2004-2018 Underground Software // #ifndef __SOUND_H__ @@ -10,6 +10,8 @@ #include +#define SAMPLE_RATE (48000.0) + // Global variables (exported) @@ -26,3 +28,4 @@ void VolumeDown(void); uint8_t GetVolume(void); #endif // __SOUND_H__ + diff --git a/src/v65c02.cpp b/src/v65c02.cpp index 9589b13..43f6189 100644 --- a/src/v65c02.cpp +++ b/src/v65c02.cpp @@ -132,6 +132,7 @@ static uint8_t CPUCycles[256] = { 2, 5, 5, 2, 4, 4, 6, 2, 2, 4, 4, 2, 4, 4, 6, 2 }; #endif +#if 0 static uint8_t _6502Cycles[256] = { 7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 6, 7, @@ -167,6 +168,7 @@ static uint8_t _65C02Cycles[256] = { 2, 5, 5, 2, 4, 4, 6, 2, 2, 4, 3, 2, 4, 4, 6, 2, 2, 6, 2, 2, 3, 3, 5, 2, 2, 2, 2, 2, 4, 4, 6, 2, 2, 5, 5, 2, 4, 4, 6, 2, 2, 4, 4, 2, 4, 4, 6, 2 }; +#endif #if 0 // ExtraCycles: @@ -1128,9 +1130,10 @@ BEQ Relative BEQ Oper F0 2 2 #define HANDLE_BRANCH_TAKEN(m) \ { \ - uint16_t oldpc = regs.pc; \ + uint16_t oldpc = regs.pc; \ regs.pc += m; \ regs.clock++; \ +\ if ((oldpc ^ regs.pc) & 0xFF00) \ regs.clock++; \ } @@ -2888,6 +2891,23 @@ void Execute65C02(V65C02REGS * context, uint32_t cycles) while (regs.clock < endCycles) { #if 0 +static bool weGo = false; +if (regs.pc == 0x80AE) +{ + dumpDis = true; + weGo = true; +} +else if (regs.pc == 0xFCA8 && weGo) +{ + dumpDis = false; + WriteLog("\n*** DELAY (A=$%02X)\n\n", regs.a); +} +else if (regs.pc == 0xFCB3 && weGo) +{ + dumpDis = true; +} +#endif +#if 0 /*if (regs.pc == 0x4007) { dumpDis = true; @@ -3012,12 +3032,22 @@ if (dumpDis) //if (!(regs.cpuFlags & V65C02_STATE_ILLEGAL_INST)) //instCount[opcode]++; - exec_op[opcode](); // Execute that opcode... + // We need this because the opcode execute could add 1 or 2 cycles + uint64_t clockSave = regs.clock; + + // Execute that opcode... + exec_op[opcode](); regs.clock += CPUCycles[opcode]; + + // Tell the timer function how many PHI2s have elapsed + if (regs.Timer) +// regs.Timer(CPUCycles[opcode]); + regs.Timer(regs.clock - clockSave); + #ifdef __DEBUG__ if (dumpDis) - WriteLog(" [PC=%04X, SP=%04X, CC=%s%s.%s%s%s%s%s, A=%02X, X=%02X, Y=%02X]\n", - regs.pc, 0x0100 + regs.sp, + WriteLog(" [PC=%04X, SP=01%02X, CC=%s%s.%s%s%s%s%s, A=%02X, X=%02X, Y=%02X]\n", + regs.pc, regs.sp, (regs.cc & FLAG_N ? "N" : "-"), (regs.cc & FLAG_V ? "V" : "-"), (regs.cc & FLAG_B ? "B" : "-"), (regs.cc & FLAG_D ? "D" : "-"), (regs.cc & FLAG_I ? "I" : "-"), (regs.cc & FLAG_Z ? "Z" : "-"), @@ -3052,11 +3082,9 @@ if (regs.pc == 0xFBD8) { // Not sure about this... regs.sp = 0xFF; - regs.cc = FLAG_B | FLAG_I; // Reset the CC register + regs.cc = FLAG_I; // Reset the CC register regs.pc = RdMemW(0xFFFC); // And load PC with the RESET vector -// context->cpuFlags &= ~V65C02_ASSERT_LINE_RESET; -// regs.cpuFlags &= ~V65C02_ASSERT_LINE_RESET; context->cpuFlags = 0; // Clear CPU flags... regs.cpuFlags = 0; #ifdef __DEBUG__ @@ -3085,6 +3113,8 @@ WriteLog("\n*** NMI ***\n\n"); { #ifdef __DEBUG__ WriteLog("\n*** IRQ ***\n\n"); +WriteLog("Clock=$%X\n", regs.clock); +//dumpDis = true; #endif regs.WrMem(0x0100 + regs.sp--, regs.pc >> 8); // Save PC and CC regs.WrMem(0x0100 + regs.sp--, regs.pc & 0xFF); @@ -3118,3 +3148,12 @@ uint64_t GetCurrentV65C02Clock(void) return regs.clock; } + +// +// Assert 65C02 line in current context +// +void AssertLine(uint16_t flags) +{ + regs.cpuFlags |= flags; +} + diff --git a/src/v65c02.h b/src/v65c02.h index 95c7885..52cb4e8 100644 --- a/src/v65c02.h +++ b/src/v65c02.h @@ -12,14 +12,14 @@ // Useful defines -#define FLAG_N 0x80 // Negative -#define FLAG_V 0x40 // oVerflow -#define FLAG_UNK 0x20 // ??? (always set when read?) -#define FLAG_B 0x10 // Break -#define FLAG_D 0x08 // Decimal -#define FLAG_I 0x04 // Interrupt -#define FLAG_Z 0x02 // Zero -#define FLAG_C 0x01 // Carry +#define FLAG_N 0x80 // Negative +#define FLAG_V 0x40 // oVerflow +#define FLAG_UNK 0x20 // ??? (always set when read?) +#define FLAG_B 0x10 // Break +#define FLAG_D 0x08 // Decimal +#define FLAG_I 0x04 // Interrupt +#define FLAG_Z 0x02 // Zero +#define FLAG_C 0x01 // Carry #define V65C02_ASSERT_LINE_RESET 0x0001 // v65C02 RESET line #define V65C02_ASSERT_LINE_IRQ 0x0002 // v65C02 IRQ line @@ -31,18 +31,18 @@ struct V65C02REGS { - uint16_t pc; // 65C02 PC register - uint8_t cc; // 65C02 Condition Code register - uint8_t sp; // 65C02 System stack pointer (bound to $01xx) - uint8_t a; // 65C02 A register - uint8_t x; // 65C02 X index register - uint8_t y; // 65C02 Y register -// uint32_t clock; // 65C02 clock (@ 1 MHz, wraps at 71.5 minutes) - uint64_t clock; // 65C02 clock (@ 1 MHz, wraps at 570,842 years) + uint16_t pc; // 65C02 PC register + uint8_t cc; // 65C02 Condition Code register + uint8_t sp; // 65C02 System stack pointer (bound to $01xx) + uint8_t a; // 65C02 A register + uint8_t x; // 65C02 X index register + uint8_t y; // 65C02 Y register + uint64_t clock; // 65C02 clock (@ 1 MHz, wraps at 570,842 years) uint8_t (* RdMem)(uint16_t); // Address of BYTE read routine void (* WrMem)(uint16_t, uint8_t); // Address of BYTE write routine - uint16_t cpuFlags; // v65C02 IRQ/RESET flags - uint64_t overflow; // # of cycles we went over last time through + void (* Timer)(uint16_t); // Address of Timer routine + uint16_t cpuFlags; // v65C02 IRQ/RESET flags + uint64_t overflow; // # of cycles we went over last time through }; // Global variables (exported) @@ -53,5 +53,7 @@ extern bool dumpDis; void Execute65C02(V65C02REGS *, uint32_t); // Function to execute 65C02 instructions uint64_t GetCurrentV65C02Clock(void); // Get the clock of the currently executing CPU +void AssertLine(uint16_t); // Assert 65C02 line in current context #endif // __V65C02_H__ + diff --git a/src/video.cpp b/src/video.cpp index 7a28cde..5bc12e0 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -4,7 +4,7 @@ // All the video modes that a real Apple 2 supports are handled here // // by James Hammons -// (c) 2005-2017 Underground Software +// (c) 2005-2018 Underground Software // // JLH = James Hammons // @@ -20,7 +20,8 @@ // like white mono does [DONE] // - Double HiRes [DONE] // - 80 column text [DONE] -// - Fix OSD text display so that it's visible no matter what background is there [DONE] +// - Fix OSD text display so that it's visible no matter what background is +// there [DONE] // // Display routines seem MUCH slower now... !!! INVESTIGATE !!! [not anymore] @@ -74,13 +75,14 @@ bool hiRes = false; bool alternateCharset = false; bool col80Mode = false; SDL_Renderer * sdlRenderer = NULL; +SDL_Window * sdlWindow = NULL; // Local variables -static SDL_Window * sdlWindow = NULL; static SDL_Texture * sdlTexture = NULL; static uint32_t * scrBuffer; static int scrPitch; +static bool showFrameTicks = false; // We set up the colors this way so that they'll be endian safe // when we cast them to a uint32_t. Note that the format is RGBA. @@ -236,7 +238,6 @@ uint16_t appleHiresToMono[0x200] = { 0x207F, 0x387F, 0x267F, 0x3E7F, 0x21FF, 0x39FF, 0x27FF, 0x3FFF // $Fx }; -//static uint8_t blurTable[0x800][8]; // Color TV blur table static uint8_t blurTable[0x80][8]; // Color TV blur table static uint8_t mirrorTable[0x100]; static uint32_t * palette = (uint32_t *)altColors; @@ -262,34 +263,6 @@ void SetupBlurTable(void) // Odd. Doing the bit patterns from 0-$7F doesn't work, but going // from 0-$7FF stepping by 16 does. Hm. // Well, it seems that going from 0-$7F doesn't have enough precision to do the job. -#if 0 -// for(uint16_t bitPat=0; bitPat<0x800; bitPat++) - for(uint16_t bitPat=0; bitPat<0x80; bitPat++) - { -/* uint16_t w3 = bitPat & 0x888; - uint16_t w2 = bitPat & 0x444; - uint16_t w1 = bitPat & 0x222; - uint16_t w0 = bitPat & 0x111;*/ - uint16_t w3 = bitPat & 0x88; - uint16_t w2 = bitPat & 0x44; - uint16_t w1 = bitPat & 0x22; - uint16_t w0 = bitPat & 0x11; - - uint16_t blurred3 = (w3 | (w3 >> 1) | (w3 >> 2) | (w3 >> 3)) & 0x00FF; - uint16_t blurred2 = (w2 | (w2 >> 1) | (w2 >> 2) | (w2 >> 3)) & 0x00FF; - uint16_t blurred1 = (w1 | (w1 >> 1) | (w1 >> 2) | (w1 >> 3)) & 0x00FF; - uint16_t blurred0 = (w0 | (w0 >> 1) | (w0 >> 2) | (w0 >> 3)) & 0x00FF; - - for(int8_t i=7; i>=0; i--) - { - uint8_t color = (((blurred0 >> i) & 0x01) << 3) - | (((blurred1 >> i) & 0x01) << 2) - | (((blurred2 >> i) & 0x01) << 1) - | ((blurred3 >> i) & 0x01); - blurTable[bitPat][7 - i] = color; - } - } -#else for(uint16_t bitPat=0; bitPat<0x800; bitPat+=0x10) { uint16_t w0 = bitPat & 0x111, w1 = bitPat & 0x222, w2 = bitPat & 0x444, w3 = bitPat & 0x888; @@ -308,7 +281,6 @@ void SetupBlurTable(void) blurTable[bitPat >> 4][7 - i] = color; } } -#endif for(int i=0; i<256; i++) { @@ -352,6 +324,12 @@ void CycleScreenTypes(void) } +void ToggleTickDisplay(void) +{ + showFrameTicks = !showFrameTicks; +} + + static uint32_t msgTicks = 0; static char message[4096]; @@ -367,31 +345,39 @@ void SpawnMessage(const char * text, ...) } -static void DrawString2(uint32_t x, uint32_t y, uint32_t color); +static void DrawString2(uint32_t x, uint32_t y, uint32_t color, char * msg); static void DrawString(void) { //This approach works, and seems to be fast enough... Though it probably would //be better to make the oversized font to match this one... for(uint32_t x=7; x<=9; x++) for(uint32_t y=7; y<=9; y++) - DrawString2(x, y, 0x00000000); + DrawString2(x, y, 0x00000000, message); + + DrawString2(8, 8, 0x0020FF20, message); +} + + +static void DrawString(uint32_t x, uint32_t y, uint32_t color, char * msg) +{ +//This approach works, and seems to be fast enough... Though it probably would +//be better to make the oversized font to match this one... + for(uint32_t xx=x-1; xx<=x+1; xx++) + for(uint32_t yy=y-1; yy<=y+1; yy++) + DrawString2(xx, yy, 0x00000000, msg); - DrawString2(8, 8, 0x0020FF20); + DrawString2(x, y, color, msg); } -static void DrawString2(uint32_t x, uint32_t y, uint32_t color) +static void DrawString2(uint32_t x, uint32_t y, uint32_t color, char * msg) { -//uint32_t x = 8, y = 8; - uint32_t length = strlen(message), address = x + (y * VIRTUAL_SCREEN_WIDTH); -// uint32_t color = 0x0020FF20; -//This could be done ahead of time, instead of on each pixel... -//(Now it is!) + uint32_t length = strlen(msg), address = x + (y * VIRTUAL_SCREEN_WIDTH); uint8_t nBlue = (color >> 16) & 0xFF, nGreen = (color >> 8) & 0xFF, nRed = color & 0xFF; for(uint32_t i=0; i