Changes to sound system relating to the new threaded CPU core. It works,

mostly, but still plays back too fast. Dunno why. :-/
This commit is contained in:
Shamus Hammons 2009-02-16 16:22:33 +00:00
parent 71981eae69
commit d0e4cf72ab
3 changed files with 196 additions and 41 deletions

View File

@ -54,13 +54,19 @@
#include "gui/draggablewindow2.h"
#include "gui/textedit.h"
//using namespace std;
// Debug and misc. defines
#define THREADED_65C02
//#define CPU_THREAD_OVERFLOW_COMPENSATION
//#define DEBUG_LC
#define CPU_CLOCK_CHECKING
// Global variables
uint8 ram[0x10000], rom[0x10000]; // RAM & ROM spaces
uint8 ram2[0x10000];
uint8 diskRom[0x100]; // Disk ROM space
V65C02REGS mainCPU;
V65C02REGS mainCPU; // v65C02 execution context
uint8 appleType = APPLE_TYPE_II;
FloppyDrive floppyDrive;
@ -95,7 +101,6 @@ static bool LoadApple2State(const char * filename);
static void FrameCallback(void);
static void BlinkTimer(void);
#define THREADED_65C02
#ifdef THREADED_65C02
// Test of threaded execution of 6502
static SDL_Thread * cpuThread = NULL;
@ -115,18 +120,41 @@ int CPUThreadFunc(void * data)
// Also, must be created in the thread that uses it...
SDL_mutex * cpuMutex = SDL_CreateMutex();
#ifdef CPU_THREAD_OVERFLOW_COMPENSATION
float overflow = 0.0;
#endif
do
{
if (cpuSleep)
SDL_CondWait(cpuCond, cpuMutex);
Execute65C02(&mainCPU, 17066); // how much? 1 frame (after 1 s, off by 40 cycles)
uint32 cycles = 17066;
#ifdef CPU_THREAD_OVERFLOW_COMPENSATION
// ODD! It's closer *without* this overflow compensation. ??? WHY ???
overflow += 0.666666667;
if (overflow > 1.0)
{
overflow -= 1.0;
cycles++;
}
#endif
Execute65C02(&mainCPU, cycles); // how much? 1 frame (after 1 s, off by 40 cycles) not any more--it's off by as much as 240 now!
SDL_mutexP(cpuMutex);
// Adjust the sound routine's last cycle toggled time base
// Also, since we're finished executing, .clock is now valid
AdjustLastToggleCycles(mainCPU.clock);
#if 0
if (SDL_CondWait(cpuCond, cpuMutex) != 0)
{
printf("SDL_CondWait != 0! (Error: '%s')\n", SDL_GetError());
exit(-1);
}
#else
SDL_CondWait(cpuCond, cpuMutex);
#endif
SDL_mutexV(cpuMutex);
}
while (!cpuFinished);
@ -189,11 +217,11 @@ if (addr >= 0xC080 && addr <= 0xC08F)
WriteLog("\n*** Read at I/O address %04X\n", addr);
#endif
if ((addr & 0xFFF0) == 0xC000)
if ((addr & 0xFFF0) == 0xC000) // Read $C000-$C00F
{
return lastKeyPressed | (keyDown ? 0x80 : 0x00);
}
else if ((addr & 0xFFF0) == 0xC010)
else if ((addr & 0xFFF0) == 0xC010) // Read $C010-$C01F
{
//This is bogus: keyDown is set to false, so return val NEVER is set...
//Fixed...
@ -202,7 +230,7 @@ if (addr >= 0xC080 && addr <= 0xC08F)
keyDown = false;
return retVal;
}
else if ((addr & 0xFFF0) == 0xC030)
else if ((addr & 0xFFF0) == 0xC030) // Read $C030-$C03F
{
/*
This is problematic, mainly because the v65C02 removes actual cycles run after each call.
@ -225,35 +253,35 @@ deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer
//should it return something else here???
return 0x00;
}
else if (addr == 0xC050)
else if (addr == 0xC050) // Read $C050
{
textMode = false;
}
else if (addr == 0xC051)
else if (addr == 0xC051) // Read $C051
{
textMode = true;
}
else if (addr == 0xC052)
else if (addr == 0xC052) // Read $C052
{
mixedMode = false;
}
else if (addr == 0xC053)
else if (addr == 0xC053) // Read $C053
{
mixedMode = true;
}
else if (addr == 0xC054)
else if (addr == 0xC054) // Read $C054
{
displayPage2 = false;
}
else if (addr == 0xC055)
else if (addr == 0xC055) // Read $C055
{
displayPage2 = true;
}
else if (addr == 0xC056)
else if (addr == 0xC056) // Read $C056
{
hiRes = false;
}
else if (addr == 0xC057)
else if (addr == 0xC057) // Read $C057
{
hiRes = true;
}
@ -297,7 +325,6 @@ deltaT to zero. In the sound IRQ, if deltaT > buffer size, then subtract buffer
A = PEEK($C082)
*/
//#define DEBUG_LC
else if ((addr & 0xFFFB) == 0xC080)
{
#ifdef DEBUG_LC
@ -560,6 +587,8 @@ if (addr == 0x7F47)
WriteLog("\n*** Byte %02X written at address %04X\n", b, addr);
#endif
/*
I think this is IIc/IIe only...
CLR80STORE=$C000 ;80STORE Off- disable 80-column memory mapping (Write)
SET80STORE=$C001 ;80STORE On- enable 80-column memory mapping (WR-only)
@ -727,6 +756,7 @@ WriteLog("LC(R): $C08B 49291 OECG RR Read/Write RAM bank 1\n");
else if (addr == 0xC0EC)
{
//change this to Write()? (and the other to Read()?) Dunno. Seems to work OK, but still...
//or DoIO
floppyDrive.ReadWrite();
}
else if (addr == 0xC0ED)
@ -794,10 +824,10 @@ static bool LoadApple2State(const char * filename)
return false;
}
//#define CPU_CLOCK_CHECKING
#ifdef CPU_CLOCK_CHECKING
uint8 counter = 0;
uint32 totalCPU = 0;
uint64 lastClock = 0;
#endif
//
// Main loop
@ -812,6 +842,7 @@ int main(int /*argc*/, char * /*argv*/[])
//Need to bankify this stuff for the IIe emulation...
memset(ram, 0, 0x10000);
memset(rom, 0, 0x10000);
memset(ram2, 0, 0x10000);
// Set up V65C02 execution context
memset(&mainCPU, 0, sizeof(V65C02REGS));
@ -914,19 +945,8 @@ memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
startTicks = SDL_GetTicks();
#ifdef THREADED_65C02
//cpuMutex = SDL_CreateMutex();
//mutex must be locked for conditional to work...
//if (SDL_mutexP(cpuMutex) == -1)
//{
// printf("Couldn't lock CPU mutex!\n");
// exit(-1);
//}
cpuCond = SDL_CreateCond();
//printf("mutex=$%08X, cond=$%08X\n", cpuMutex, cpuCond);
//cpuSleep = true;
cpuThread = SDL_CreateThread(CPUThreadFunc, NULL);
//cpuSleep = false;
//SDL_CondSignal(cpuCond);
#endif
WriteLog("Entering main loop...\n");
@ -941,7 +961,9 @@ cpuThread = SDL_CreateThread(CPUThreadFunc, NULL);
//Fixed, but mainCPU.clock is destroyed in the bargain. Oh well.
// mainCPU.clock -= USEC_TO_M6502_CYCLES(timeToNextEvent);
#ifdef CPU_CLOCK_CHECKING
#ifndef THREADED_65C02
totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
#endif
#endif
// Handle CPU time delta for sound...
//Don't need this anymore now that we use absolute time...
@ -951,10 +973,10 @@ totalCPU += USEC_TO_M6502_CYCLES(timeToNextEvent);
#ifdef THREADED_65C02
cpuFinished = true;
SDL_CondSignal(cpuCond);//thread is asleep, wake it up
SDL_CondSignal(cpuCond);//thread is probably asleep, wake it up
SDL_WaitThread(cpuThread, NULL);
//nowok:SDL_WaitThread(CPUThreadFunc, NULL);
SDL_DestroyCond(cpuCond);
//SDL_DestroyMutex(cpuMutex);
#endif
if (settings.autoStateSaving)
@ -1119,6 +1141,10 @@ else if (event.key.keysym.sym == SDLK_F10)
SetCallbackTime(FrameCallback, 16666.66666667);
#ifdef CPU_CLOCK_CHECKING
//We know it's stopped, so we can get away with this...
uint64 clock = GetCurrentV65C02Clock();
totalCPU += (uint32)(clock - lastClock);
lastClock = clock;
counter++;
if (counter == 60)
{
@ -1152,4 +1178,21 @@ of the threads?
o Have the CPU thread manage the timer mechanism? (need to have a method of carrying
remainder CPU cycles over...)
One way would be to use a fractional accumulator, then subtract 1 every
time it overflows. Like so:
double overflow = 0;
uint32 time = 20;
while (!done)
{
Execute6808(&soundCPU, time);
overflow += 0.289115646;
if (overflow > 1.0)
{
overflow -= 1.0;
time = 21;
}
else
time = 20;
}
*/

View File

@ -26,13 +26,21 @@
#include <SDL.h>
#include "log.h"
// Useful defines
//#define DEBUG
//#define SAMPLE_RATE (44100.0)
#define SAMPLE_RATE (48000.0)
#define SAMPLES_PER_FRAME (SAMPLE_RATE / 60.0)
#define CYCLES_PER_SAMPLE (1024000.0 / SAMPLE_RATE)
#define SOUND_BUFFER_SIZE (8192)
//#define AMPLITUDE (16) // -32 - +32 seems to be plenty loud!
// ~ 21
//#define CYCLES_PER_SAMPLE (1024000.0 / SAMPLE_RATE)
// ~ 17 (lower pitched than above...!)
// Makes sense, as this is the divisor for # of cycles passed
#define CYCLES_PER_SAMPLE (800000.0 / SAMPLE_RATE)
//nope, too high #define CYCLES_PER_SAMPLE (960000.0 / SAMPLE_RATE)
//#define SOUND_BUFFER_SIZE (8192)
#define SOUND_BUFFER_SIZE (16384)
// Global variables
@ -50,7 +58,8 @@ static uint64 samplePosition;
static SDL_cond * conditional = NULL;
static SDL_mutex * mutex = NULL;
static SDL_mutex * mutex2 = NULL;
static uint8 ampPtr = 5;
static int8 sample;
static uint8 ampPtr = 5; // Start with -16 - +16
static uint16 amplitude[17] = { 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
4096, 8192, 16384, 32768 };
@ -63,7 +72,7 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
//
void SoundInit(void)
{
#if 1
#if 0
// To weed out problems for now...
return;
#endif
@ -91,6 +100,7 @@ return;
sampleBase = 0;
lastToggleCycles = 0;
samplePosition = 0;
sample = desired.silence; // ? wilwok ? yes
SDL_PauseAudio(false); // Start playback!
soundInitialized = true;
@ -123,6 +133,7 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
// (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...
SDL_mutexP(mutex2);
@ -135,8 +146,8 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
buffer[i] = soundBuffer[i];
// Fill buffer with last value
memset(buffer + soundBufferPos, (uint8)(speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]), length - soundBufferPos);
soundBufferPos = 0; // Reset soundBufferPos to start of buffer...
// memset(buffer + soundBufferPos, (uint8)(speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]), length - soundBufferPos);
memset(buffer + soundBufferPos, (uint8)sample, length - soundBufferPos); soundBufferPos = 0; // Reset soundBufferPos to start of buffer...
sampleBase = 0; // & sampleBase...
//Ick. This should never happen!
//Actually, this probably happens a lot. (?)
@ -218,10 +229,21 @@ Could check current CPU clock, take delta. If delta > 1024, then ...
Could add # of cycles in IRQ to lastToggleCycles, then currentPos will be guaranteed
to fall within acceptable limits.
This *should* work, but if the IRQ isn't scheduled & etc, could screw timing up.
Need to have a way to suspend IRQ thread as well as CPU thread when in the GUI,
for example
Another method would be to add to lastToggleCycles on every timeslice of the CPU,
just like we used to.
Or, run the CPU for CYCLES_PER_SAMPLE and take a sample, then copy the buffer over
at the end of the timeslice. That way, we could just fill the buffer and let the
IRQ handle draining it. No muss, no fuss.
*/
if (currentPos > SOUND_BUFFER_SIZE - 1)
if ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1))
{
#if 0
WriteLog("ToggleSpeaker() about to go into spinlock at time: %08X (%u) (sampleBase=%u)!\n", time, time, sampleBase);
@ -242,23 +264,30 @@ Seems like it's OK now that I've fixed the buffer-less-than-length bug...
// SDL_CondWait(conditional, mutex);
// SDL_LockAudio();
// Hm.
// This might not empty the buffer enough, causing hash and trash. !!! FIX !!!
SDL_mutexV(mutex2);//Release it so sound thread can get it,
SDL_CondWait(conditional, mutex);//Sleep/wait for the sound thread
SDL_mutexP(mutex2);//Re-lock it until we're done with it...
currentPos = sampleBase + (uint32)((double)elapsedCycles / CYCLES_PER_SAMPLE);
// currentPos = sampleBase + (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
#if 0
WriteLog("--> after spinlock (sampleBase=%u)...\n", sampleBase);
#endif
}
int8 sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
// currentPos is position from "zero" or soundBufferPos...
currentPos += soundBufferPos;
while (soundBufferPos < currentPos)
soundBuffer[soundBufferPos++] = (uint8)sample;
// This is done *after* in case the buffer had a long dead spot (I think...)
speakerState = !speakerState;
sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
lastToggleCycles = elapsedCycles;
SDL_mutexV(mutex2);
// SDL_UnlockAudio();
}
@ -275,6 +304,88 @@ void AddToSoundTimeBase(uint32 cycles)
// SDL_UnlockAudio();
}
void AdjustLastToggleCycles(uint64 elapsedCycles)
{
#if 0
if (!soundInitialized)
return;
SDL_mutexP(mutex2);
lastToggleCycles += elapsedCycles;
SDL_mutexV(mutex2);
// We should also fill the buffer here as well, even if the speaker
// didn't toggle... !!! FIX !!!
#else
/*
BOOKKEEPING
We need to know the following:
o Where in the sound buffer the base or "zero" time is
o At what CPU timestamp the speaker was last toggled
NOTE: we keep things "right" by advancing this number every frame, even
if nothing happened! That way, we can keep track without having
to detect whether or not several frames have gone by without any
activity.
How to do it:
Every time the speaker is toggled, we move the base or "zero" time to the
current spot in the buffer. We also backfill the buffer up to that point with
the old toggle value. The next time the speaker is toggled, we measure the
difference in time between the last time it was toggled (the "zero") and now,
and repeat the cycle.
We handle dead spots by backfilling the buffer with the current toggle value
every frame--this way we don't have to worry about keeping current time and
crap like that. So, we have to move the "zero" the right amount, just like
in ToggleSpeaker(), and backfill only without toggling.
*/
#warning "This is VERY similar to ToggleSpeaker(); merge into common function. !!! FIX !!!"
if (!soundInitialized)
return;
#ifdef DEBUG
printf("SOUND: AdjustLastToggleCycles() start...\n");
#endif
// Step 1: Calculate delta time
uint64 deltaCycles = elapsedCycles - lastToggleCycles;
// Step 2: Calculate new buffer position
uint32 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
// Step 3: Make sure there's room for it
// We need to lock since we touch both soundBuffer and soundBufferPos
SDL_mutexP(mutex2);
while ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1))
{
// Hm.
// This might not empty the buffer enough, causing hash and trash. !!! FIX !!! [DONE]
SDL_mutexV(mutex2);//Release it so sound thread can get it,
SDL_CondWait(conditional, mutex);//Sleep/wait for the sound thread
SDL_mutexP(mutex2);//Re-lock it until we're done with it...
//HMM, this doesn't need to lock or recalculate this value
// currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
}
// Step 4: Backfill and adjust lastToggleCycles
// currentPos is position from "zero" or soundBufferPos...
currentPos += soundBufferPos;
// Backfill with current toggle state
while (soundBufferPos < currentPos)
soundBuffer[soundBufferPos++] = (uint8)sample;
SDL_mutexV(mutex2);
lastToggleCycles = elapsedCycles;
#ifdef DEBUG
printf("SOUND: AdjustLastToggleCycles() end...\n");
#endif
#endif
}
void VolumeUp(void)
{
// Currently set for 8-bit samples

View File

@ -18,7 +18,8 @@
void SoundInit(void);
void SoundDone(void);
void ToggleSpeaker(uint64 elapsedCycles);
void AddToSoundTimeBase(uint64 cycles);
//void AddToSoundTimeBase(uint64 cycles);
void AdjustLastToggleCycles(uint64 elapsedCycles);
void VolumeUp(void);
void VolumeDown(void);
uint8 GetVolume(void);