]> Shamusworld >> Repos - apple2/commitdiff
First stab at adding Mockingboard support.
authorShamus Hammons <jlhamm@acm.org>
Mon, 10 Sep 2018 03:47:51 +0000 (22:47 -0500)
committerShamus Hammons <jlhamm@acm.org>
Mon, 10 Sep 2018 03:47:51 +0000 (22:47 -0500)
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).

17 files changed:
.gitignore
Makefile
src/apple2.cpp
src/apple2.h
src/ay8910.cpp
src/ay8910.h
src/mmu.cpp
src/mos6522via.cpp [new file with mode: 0644]
src/mos6522via.h [new file with mode: 0644]
src/settings.cpp
src/settings.h
src/sound.cpp
src/sound.h
src/v65c02.cpp
src/v65c02.h
src/video.cpp
src/video.h

index a7dd4bf0d65ff349c2b7046829efc001ddec014d..539de2008c39eba29851cb93377c998e651f5bc7 100644 (file)
@@ -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/
index 11e3fd8e13c5eda7b539dc954651384d30d6d04b..e6515e8941cb2f261b507fa4348f37819cec0bd1 100644 (file)
--- 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        \
index 246c737c377f7ca077b3acd6873f6a23ba61e0c4..cb61084056185613c55a04fbae39954d2e094209 100644 (file)
@@ -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.
 // 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:
 //
 #include <stdlib.h>
 #include <string>
 #include <time.h>
+#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);
index 6627bbef73420022bb892cf52a12264fa89f492b..3e8559e49f61aa15925d5639ba76f29d2953bb71 100644 (file)
@@ -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;
 
index 5549248b09b918ab48b9b99a1efdb2db1cdc3193..cfb293d8e5528159d17affe85c12afd88b5e0468 100644 (file)
@@ -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 <string.h>                    // 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; i<fullCycles; i++)
+       {
+               for(int j=0; j<3; j++)
+               {
+                       // Tone generators only run if the corresponding voice is enabled.
+                       // N.B.: We also reject any period set that is less than 2.
+                       if (chip->toneEnable[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
 
 // JLH: Commented out MAME specific crap
 
-#include "ay8910.h"
-#include <string.h>                                                            // 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; i<AY_PORTA; i++)
-               _AYWriteReg(chip, i, 0);        /* AYWriteReg() uses the timer system; we cannot */
-                                                                       /* call it at this time because the timer system */
-                                                                       /* has not been initialized.                     */
+       AYPSG[chip].register_latch = 0;
+       AYPSG[chip].RNG = 1;
+       AYPSG[chip].OutputA = 0;
+       AYPSG[chip].OutputB = 0;
+       AYPSG[chip].OutputC = 0;
+       AYPSG[chip].OutputN = 0xFF;
+
+       for(int i=0; i<=AY_ESHAPE; i++)
+               _AYWriteReg(chip, i, 0);        /* AYWriteReg() uses the timer system; we
+                                                                        * cannot call it at this time because the
+                                                                        * timer system has not been initialized.                     */
 }
 
 // This stuff looks like Tom's code, so let's streamline and un-MSHungarianize this shit:
 // [DONE]
+// N.B.: Looks like 'clock' is the 65C02 clock rate, and 'sampleRate' is the
+//       sample rate set by the audio subsystem.
 
 void AY8910_InitAll(int clock, int sampleRate)
 {
        for(int chip=0; chip<MAX_8910; chip++)
        {
-               struct AY8910 * PSG = &AYPSG[chip];
-
-               memset(PSG, 0, sizeof(struct AY8910));
-               PSG->SampleRate = 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; chip++)
                AY8910_set_clock(chip, clock);
 }
+#endif
 
-uint8_t * AY8910_GetRegsPtr(uint16_t chipNum)
-{
-       if (chipNum >= MAX_8910)
-               return NULL;
-
-       return &AYPSG[chipNum].Regs[0];
-}
index 6eb733a70e6ea8c62ea0192f11ecb9f97137f95e..639a56789084ebadb05371351f8d446f3cd44b01 100644 (file)
@@ -3,14 +3,29 @@
 
 #include <stdint.h>
 
+#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
+
index 8a5b36f8547b3297b6967097a13b121665e94f31..74c80e2841aaadce4784d93b616f2263da28f529 100644 (file)
 
 #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 (file)
index 0000000..6632691
--- /dev/null
@@ -0,0 +1,20 @@
+// Mockingboard support (6522 interface)
+//
+// by James Hammons
+// (C) 2018 Underground Software
+//
+
+#include "mos6522via.h"
+
+#include <string.h>                                                            // 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 (file)
index 0000000..4d494a9
--- /dev/null
@@ -0,0 +1,31 @@
+// Mockingboard support
+//
+// by James Hammons
+// (C) 2018 Underground Software
+//
+
+#ifndef __MOS6522VIA_H__
+#define __MOS6522VIA_H__
+
+#include <stdint.h>
+
+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__
+
index ed34ef482dd2154f4742918ca2b03774d7edd8b7..3939649aa056382e22be6751b4c5ae361bd0352f 100644 (file)
@@ -18,6 +18,7 @@
 #include <SDL2/SDL.h>
 #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);
 }
 
 
index eaf3d274bbd079a0070368d867d0e415495002ae..daad84d89cd0ce1c9321228e366046012a43e692 100644 (file)
@@ -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];
index a68956788fa118e84150efcaf95477809230ba1d..df2de2b4ab59d3c64b80cbc49eb8002ee68734cf 100644 (file)
 
 #include <string.h>                    // For memset, memcpy
 #include <SDL2/SDL.h>
+#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<length; i++)
+                       buffer[i] = desired.silence;
+#else
                // The sound buffer is starved...
                for(uint32_t i=0; i<soundBufferPos; i++)
                        buffer[i] = soundBuffer[i];
@@ -160,6 +163,7 @@ static void SDLSoundCallback(void * /*userdata*/, Uint8 * buffer8, int length8)
 
                // Reset soundBufferPos to start of buffer...
                soundBufferPos = 0;
+#endif
        }
        else
        {
@@ -176,9 +180,9 @@ static void SDLSoundCallback(void * /*userdata*/, Uint8 * buffer8, int length8)
 
        // Free the mutex...
 //WriteLog("SDLSoundCallback(): SDL_mutexV(mutex2)\n");
-       SDL_mutexV(mutex2);
+//     SDL_mutexV(mutex2);
        // Wake up any threads waiting for the buffer to drain...
-       SDL_CondSignal(conditional);
+//     SDL_CondSignal(conditional);
 //WriteLog("SDLSoundCallback(): end\n");
 }
 
@@ -186,23 +190,43 @@ static void SDLSoundCallback(void * /*userdata*/, Uint8 * buffer8, int length8)
 // This is called by the main CPU thread every ~21.666 cycles.
 void WriteSampleToBuffer(void)
 {
+#ifdef USE_NEW_AY8910
+       uint16_t s1 = AYGetSample(0);
+       uint16_t s2 = AYGetSample(1);
+       uint16_t adjustedMockingboard = s1 + s2;
+#else
+       int16_t s1, s2, s3, s4, s5, s6;
+       int16_t * bufPtrs[6] = { &s1, &s2, &s3, &s4, &s5, &s6 };
+       AY8910Update(0, bufPtrs, 1);
+       AY8910Update(1, &bufPtrs[3], 1);
+       int16_t adjustedMockingboard = (s1 / 8) + (s2 / 8) + (s3 / 8)
+               + (s4 / 8) + (s5 / 8) + (s6 / 8);
+#endif
+//need to do this *before* mixing, as by this time, it's too late and the sample is probably already oversaturated
+//     adjustedMockingboard /= 8;
+
 //WriteLog("WriteSampleToBuffer(): SDL_mutexP(mutex2)\n");
-       SDL_mutexP(mutex2);
+//     SDL_mutexP(mutex2);
 
        // This should almost never happen, but, if it does...
        while (soundBufferPos >= (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]);
 }
 
 
index 17a3dc0c837272607c7bd91225357bdd5a6f81cd..598fe1f8f63657bdbef9244df0cc11163d33f47d 100644 (file)
@@ -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 <stdint.h>
 
+#define SAMPLE_RATE            (48000.0)
+
 // Global variables (exported)
 
 
@@ -26,3 +28,4 @@ void VolumeDown(void);
 uint8_t GetVolume(void);
 
 #endif // __SOUND_H__
+
index 9589b13916a9183898e61ebd653f8877cd34c173..43f6189d93705351d4e6f3700dab85c6478136ce 100644 (file)
@@ -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;
+}
+
index 95c7885d7ca0c16cd599c8bb45da5a8d94a4b45d..52cb4e8694646d0eb0534fd24b16fd3c7ee5f5d1 100644 (file)
 
 // 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
 
 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__
+
index 7a28cde86d5563e6c9313ec56f68501df5fde16e..5bc12e0b6bcaf3107d5662646cd4abaf710df1b9 100644 (file)
@@ -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 <jlhamm@acm.org>
 //
@@ -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<length; i++)
        {
-               uint8_t c = message[i];
+               uint8_t c = msg[i];
                c = (c < 32 ? 0 : c - 32);
                uint32_t fontAddr = (uint32_t)c * FONT_WIDTH * FONT_HEIGHT;
 
@@ -399,14 +385,6 @@ static void DrawString2(uint32_t x, uint32_t y, uint32_t color)
                {
                        for(uint32_t xx=0; xx<FONT_WIDTH; xx++)
                        {
-/*                             uint8_t fontTrans = font1[fontAddr++];
-//                             uint32_t newTrans = (fontTrans * transparency / 255) << 24;
-                               uint32_t newTrans = fontTrans << 24;
-                               uint32_t pixel = newTrans | color;
-
-                               *(scrBuffer + address + xx + (yy * VIRTUAL_SCREEN_WIDTH)) = pixel;//*/
-
-//                             uint8_t trans = font1[fontAddr++];
                                uint8_t trans = font2[fontAddr++];
 
                                if (trans)
@@ -438,6 +416,65 @@ static void DrawString2(uint32_t x, uint32_t y, uint32_t color)
 }
 
 
+static void DrawFrameTicks(void)
+{
+       uint32_t color = 0x00FF2020;
+       uint32_t address = 8 + (24 * VIRTUAL_SCREEN_WIDTH);
+
+       for(uint32_t i=0; i<17; i++)
+       {
+               for(uint32_t yy=0; yy<5; yy++)
+               {
+                       for(uint32_t xx=0; xx<9; xx++)
+                       {
+//THIS IS NOT ENDIAN SAFE
+//NB: Setting the alpha channel here does nothing.
+                               *(scrBuffer + address + xx + (yy * VIRTUAL_SCREEN_WIDTH)) = 0x7F000000;
+                       }
+               }
+
+               address += (5 * VIRTUAL_SCREEN_WIDTH);
+       }
+
+       address = 8 + (24 * VIRTUAL_SCREEN_WIDTH);
+
+       // frameTicks is the amount of time remaining; so to show the amount
+       // consumed, we subtract it from 17.
+       uint32_t bars = 17 - frameTicks;
+
+       if (bars & 0x80000000)
+               bars = 0;
+
+       for(uint32_t i=0; i<17; i++)
+       {
+               for(uint32_t yy=1; yy<4; yy++)
+               {
+                       for(uint32_t xx=1; xx<8; xx++)
+                       {
+//THIS IS NOT ENDIAN SAFE
+//NB: Setting the alpha channel here does nothing.
+                               *(scrBuffer + address + xx + (yy * VIRTUAL_SCREEN_WIDTH)) = (i < bars ? color : 0x003F0000);
+                       }
+               }
+
+               address += (5 * VIRTUAL_SCREEN_WIDTH);
+       }
+
+       static char msg[32];
+
+       if ((frameTimePtr % 15) == 0)
+       {
+//             uint32_t prevClock = (frameTimePtr + 1) % 60;
+               uint64_t prevClock = (frameTimePtr + 1) % 60;
+//             float fps = 59.0f / (((float)frameTime[frameTimePtr] - (float)frameTime[prevClock]) / 1000.0f);
+               double fps = 59.0 / ((double)(frameTime[frameTimePtr] - frameTime[prevClock]) / (double)SDL_GetPerformanceFrequency());
+               sprintf(msg, "%.1lf FPS", fps);
+       }
+
+       DrawString(20, 24, color, msg);
+}
+
+
 static void Render40ColumnTextLine(uint8_t line)
 {
        uint32_t pixelOn = (screenType == ST_GREEN_MONO ? 0xFF61FF61 : 0xFFFFFFFF);
@@ -1090,6 +1127,9 @@ void RenderVideoFrame(void)
                DrawString();
                msgTicks--;
        }
+
+       if (showFrameTicks)
+               DrawFrameTicks();
 }
 
 
@@ -1104,19 +1144,35 @@ bool InitVideo(void)
                return false;
        }
 
-//     int retVal = SDL_CreateWindowAndRenderer(VIRTUAL_SCREEN_WIDTH * 2, VIRTUAL_SCREEN_HEIGHT * 2, SDL_WINDOW_OPENGL, &sdlWindow, &sdlRenderer);
+#if 0
        int retVal = SDL_CreateWindowAndRenderer(VIRTUAL_SCREEN_WIDTH * 2, VIRTUAL_SCREEN_HEIGHT * 2, 0, &sdlWindow, &sdlRenderer);
 //     int retVal = SDL_CreateWindowAndRenderer(VIRTUAL_SCREEN_WIDTH * 1, VIRTUAL_SCREEN_HEIGHT * 1, 0, &sdlWindow, &sdlRenderer);
 
        if (retVal != 0)
        {
-               WriteLog("Video: Could not window and/or renderer: %s\n", SDL_GetError());
+               WriteLog("Video: Could not create window and/or renderer: %s\n", SDL_GetError());
                return false;
        }
+#else
+       sdlWindow = SDL_CreateWindow("Apple2", settings.winX, settings.winY, VIRTUAL_SCREEN_WIDTH * 2, VIRTUAL_SCREEN_HEIGHT * 2, 0);
+
+       if (sdlWindow == NULL)
+       {
+               WriteLog("Video: Could not create window: %s\n", SDL_GetError());
+               return false;
+       }
+
+       sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
+
+       if (sdlRenderer == NULL)
+       {
+               WriteLog("Video: Could not create renderer: %s\n", SDL_GetError());
+               return false;
+       }
+#endif
 
-       // Make the scaled rendering look smoother.
+       // Make sure what we put there is what we get:
        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
-//     SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
        SDL_RenderSetLogicalSize(sdlRenderer, VIRTUAL_SCREEN_WIDTH, VIRTUAL_SCREEN_HEIGHT);
 
        // Set the application's icon & title...
index 3adff990c3517a4025f8cbc0bb84dd63a20653e8..8d70a90b6f333298988a3bf68b64a0b2a0f8a1a9 100644 (file)
@@ -23,6 +23,7 @@ extern bool hiRes;
 extern bool alternateCharset;
 extern bool col80Mode;
 extern SDL_Renderer * sdlRenderer;
+extern SDL_Window * sdlWindow;
 
 // Functions (exported)
 
@@ -34,7 +35,7 @@ bool InitVideo(void);
 void VideoDone(void);
 void RenderAppleScreen(SDL_Renderer *);
 void ToggleFullScreen(void);
-
+void ToggleTickDisplay(void);
 
 // Exported crap