Convert sound driving method to direct sampling.

Before it was doing some complex timestamp thing, now it simple runs the
CPU for ~21 cycles and then stuff the sample into the sound buffer.
Sound still lags behind by several frames though, just like with the
previous sound setup. Still not sure why. :-/
This commit is contained in:
Shamus Hammons 2013-09-11 10:00:36 -05:00
parent f67224d89f
commit a2e007c1e0
5 changed files with 96 additions and 27 deletions

View File

@ -113,6 +113,7 @@ static SDL_sem * mainSem = NULL;
static bool cpuFinished = false;
static bool cpuSleep = false;
// Let's try a thread...
/*
Here's how it works: Execute 1 frame's worth, then sleep.
@ -141,6 +142,9 @@ WriteLog("CPU: SDL_SemWait(mainSem);\n");
#endif
SDL_SemWait(mainSem);
// There are exactly 800 slices of 21.333 cycles per frame, so it works out
// evenly.
#if 0
uint32_t cycles = 17066;
#ifdef CPU_THREAD_OVERFLOW_COMPENSATION
// ODD! It's closer *without* this overflow compensation. ??? WHY ???
@ -164,6 +168,26 @@ WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
WriteLog("CPU: AdjustLastToggleCycles(mainCPU.clock);\n");
#endif
AdjustLastToggleCycles(mainCPU.clock);
#else
#ifdef THREAD_DEBUGGING
WriteLog("CPU: Execute65C02(&mainCPU, cycles);\n");
#endif
for(int i=0; i<800; i++)
{
uint32_t cycles = 21;
overflow += 0.333333334;
if (overflow > 1.0)
{
cycles++;
overflow -= 1.0;
}
Execute65C02(&mainCPU, cycles);
WriteSampleToBuffer();
}
#endif
#ifdef THREAD_DEBUGGING
WriteLog("CPU: SDL_mutexP(cpuMutex);\n");
@ -202,6 +226,7 @@ WriteLog("CPU: SDL_mutexV(cpuMutex);\n");
}
#endif
// Test GUI function
Element * TestWindow(void)
@ -212,6 +237,7 @@ Element * TestWindow(void)
return win;
}
Element * QuitEmulator(void)
{
gui->Stop();
@ -220,6 +246,7 @@ Element * QuitEmulator(void)
return NULL;
}
/*
Small Apple II memory map:
@ -845,6 +872,7 @@ if (addr >= 0xD000 && addr <= 0xD00F)
ram[addr] = b;
}
//
// Load a file into RAM/ROM image space
//
@ -861,15 +889,18 @@ bool LoadImg(char * filename, uint8_t * ram, int size)
return true;
}
static void SaveApple2State(const char * filename)
{
}
static bool LoadApple2State(const char * filename)
{
return false;
}
#ifdef CPU_CLOCK_CHECKING
uint8_t counter = 0;
uint32_t totalCPU = 0;
@ -996,9 +1027,9 @@ memcpy(ram + 0xD000, ram + 0xC000, 0x1000);
#ifdef THREADED_65C02
cpuCond = SDL_CreateCond();
mainSem = SDL_CreateSemaphore(1);
cpuThread = SDL_CreateThread(CPUThreadFunc, NULL, NULL);
//Hmm... CPU does POST (+1), wait, then WAIT (-1)
mainSem = SDL_CreateSemaphore(1);
// SDL_sem * mainMutex = SDL_CreateMutex();
#endif
@ -1068,6 +1099,7 @@ floppyDrive.SaveImage();
return 0;
}
/*
Apple II keycodes
-----------------
@ -1300,12 +1332,14 @@ if (counter == 60)
#endif
}
static void BlinkTimer(void)
{
flash = !flash;
SetCallbackTime(BlinkTimer, 250000); // Set up blinking at 1/4 sec intervals
}
/*
Next problem is this: How to have events occur and synchronize with the rest
of the threads?

View File

@ -23,7 +23,6 @@
static FILE * log_stream = NULL;
static uint32_t logSize = 0;
static bool logDone = false;
bool InitLog(const char * path)
@ -50,7 +49,7 @@ void LogDone(void)
//
void WriteLog(const char * text, ...)
{
if (!log_stream || logDone)
if (!log_stream)
return;
va_list arg;
@ -65,7 +64,6 @@ void WriteLog(const char * text, ...)
{
fclose(log_stream);
log_stream = NULL;
logDone = true;
}
}

View File

@ -61,6 +61,7 @@
// Local variables
static SDL_AudioSpec desired, obtained;
static SDL_AudioDeviceID device;
static bool soundInitialized = false;
static bool speakerState = false;
static int16_t soundBuffer[SOUND_BUFFER_SIZE];
@ -70,7 +71,7 @@ static SDL_cond * conditional = NULL;
static SDL_mutex * mutex = NULL;
static SDL_mutex * mutex2 = NULL;
static int16_t sample;
static uint8_t ampPtr = 14; // Start with -16 - +16
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 };
#ifdef WRITE_OUT_WAVE
@ -81,6 +82,7 @@ static FILE * fp = NULL;
static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
//
// Initialize the SDL sound system
//
@ -90,20 +92,16 @@ void SoundInit(void)
// To weed out problems for now...
return;
#endif
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_S8; // This uses the native endian (for portability)...
desired.format = AUDIO_S16SYS; // This uses the native endian (for portability)...
desired.channels = 1;
// desired.samples = 4096; // Let's try a 4K buffer (can always go lower)
// desired.samples = 2048; // Let's try a 2K buffer (can always go lower)
// desired.samples = 1024; // Let's try a 1K buffer (can always go lower)
desired.samples = 512; // Let's try a 1/2K buffer (can always go lower)
desired.callback = SDLSoundCallback;
// if (SDL_OpenAudio(&desired, NULL) < 0) // NULL means SDL guarantees what we want
//When doing it this way, we need to check to see if we got what we asked for...
if (SDL_OpenAudio(&desired, &obtained) < 0)
device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
if (device == 0)
{
WriteLog("Sound: Failed to initialize SDL sound.\n");
return;
@ -116,7 +114,7 @@ return;
lastToggleCycles = 0;
sample = desired.silence; // ? wilwok ? yes
SDL_PauseAudio(false); // Start playback!
SDL_PauseAudioDevice(device, 0); // Start playback!
soundInitialized = true;
WriteLog("Sound: Successfully initialized.\n");
@ -125,6 +123,7 @@ return;
#endif
}
//
// Close down the SDL sound subsystem
//
@ -132,8 +131,10 @@ void SoundDone(void)
{
if (soundInitialized)
{
SDL_PauseAudio(true);
SDL_CloseAudio();
// SDL_PauseAudio(true);
SDL_PauseAudioDevice(device, 1);
// SDL_CloseAudio();
SDL_CloseAudioDevice(device);
SDL_DestroyCond(conditional);
SDL_DestroyMutex(mutex);
SDL_DestroyMutex(mutex2);
@ -145,11 +146,13 @@ void SoundDone(void)
}
}
//
// Sound card callback handler
//
static void SDLSoundCallback(void * userdata, Uint8 * buffer8, int length8)
static void SDLSoundCallback(void * /*userdata*/, Uint8 * buffer8, int length8)
{
//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.)
@ -159,12 +162,14 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer8, int length8)
// Let's try using a mutex for shared resource consumption...
//Actually, I think Lock/UnlockAudio() does this already...
//WriteLog("SDLSoundCallback(): SDL_mutexP(mutex2)\n");
SDL_mutexP(mutex2);
// Recast this as a 16-bit type...
int16_t * buffer = (int16_t *)buffer8;
uint32_t length = (uint32_t)length8 / 2;
//WriteLog("SDLSoundCallback(): filling buffer...\n");
if (soundBufferPos < length) // The sound buffer is starved...
{
for(uint32_t i=0; i<soundBufferPos; i++)
@ -173,7 +178,8 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer8, int length8)
// Fill buffer with last value
// memset(buffer + soundBufferPos, (uint8_t)sample, length - soundBufferPos);
for(uint32_t i=soundBufferPos; i<length; i++)
buffer[i] = (uint16_t)sample;
buffer[i] = sample;
soundBufferPos = 0; // Reset soundBufferPos to start of buffer...
}
else
@ -182,6 +188,7 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer8, int length8)
// memcpy(buffer, soundBuffer, length);
for(uint32_t i=0; i<length; i++)
buffer[i] = soundBuffer[i];
soundBufferPos -= length;
// Move current buffer down to start
@ -190,11 +197,37 @@ static void SDLSoundCallback(void * userdata, Uint8 * buffer8, int length8)
}
// Free the mutex...
//WriteLog("SDLSoundCallback(): SDL_mutexV(mutex2)\n");
SDL_mutexV(mutex2);
// Wake up any threads waiting for the buffer to drain...
SDL_CondSignal(conditional);
//WriteLog("SDLSoundCallback(): end\n");
}
// This is called by the main CPU thread every ~21.333 cycles.
void WriteSampleToBuffer(void)
{
//WriteLog("WriteSampleToBuffer(): SDL_mutexP(mutex2)\n");
SDL_mutexP(mutex2);
// This should almost never happen, but...
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...
}
soundBuffer[soundBufferPos++] = sample;
//WriteLog("WriteSampleToBuffer(): SDL_mutexV(mutex2)\n");
SDL_mutexV(mutex2);
}
// Need some interface functions here to take care of flipping the
// waveform at the correct time in the sound stream...
@ -222,6 +255,7 @@ void HandleBuffer(uint64_t elapsedCycles)
// 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))
{
SDL_mutexV(mutex2); // Release it so sound thread can get it,
@ -240,7 +274,7 @@ void HandleBuffer(uint64_t elapsedCycles)
#endif
// Backfill with current toggle state
while (soundBufferPos < currentPos)
soundBuffer[soundBufferPos++] = (uint16_t)sample;
soundBuffer[soundBufferPos++] = sample;
#ifdef WRITE_OUT_WAVE
fwrite(&soundBuffer[sbpSave], sizeof(int16_t), currentPos - sbpSave, fp);
@ -250,16 +284,18 @@ void HandleBuffer(uint64_t elapsedCycles)
lastToggleCycles = elapsedCycles;
}
void ToggleSpeaker(uint64_t elapsedCycles)
{
if (!soundInitialized)
return;
HandleBuffer(elapsedCycles);
// HandleBuffer(elapsedCycles);
speakerState = !speakerState;
sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
}
void AdjustLastToggleCycles(uint64_t elapsedCycles)
{
if (!soundInitialized)
@ -292,20 +328,22 @@ in ToggleSpeaker(), and backfill only without toggling.
HandleBuffer(elapsedCycles);
}
void VolumeUp(void)
{
// Currently set for 8-bit samples
// Now 16
// Currently set for 16-bit samples
if (ampPtr < 16)
ampPtr++;
}
void VolumeDown(void)
{
if (ampPtr > 0)
ampPtr--;
}
uint8_t GetVolume(void)
{
return ampPtr;

View File

@ -18,6 +18,7 @@
void SoundInit(void);
void SoundDone(void);
void ToggleSpeaker(uint64_t elapsedCycles);
void WriteSampleToBuffer(void);
//void AddToSoundTimeBase(uint64_t cycles);
void AdjustLastToggleCycles(uint64_t elapsedCycles);
void VolumeUp(void);

View File

@ -2807,6 +2807,7 @@ void (* exec_op[256])() = {
OpF0, OpF1, OpF2, Op__, Op__, OpF5, OpF6, OpF7, OpF8, OpF9, OpFA, Op__, Op__, OpFD, OpFE, OpFF
};
//
// Internal "memcpy" (so we don't have to link with any external libraries!)
//
@ -3019,14 +3020,10 @@ WriteLog("\n*** IRQ ***\n\n");
}
}
//This is a lame way of doing it, but in the end the simplest--however, it destroys any
//record of elasped CPU time. Not sure that it's important to keep track, but there it is.
// Now we use a 64-bit integer, so it won't wrap for about 500 millenia. ;-)
// regs.clock -= cycles;
myMemcpy(context, &regs, sizeof(V65C02REGS));
}
//
// Get the clock of the currently executing CPU
//
@ -3034,3 +3031,4 @@ uint64_t GetCurrentV65C02Clock(void)
{
return regs.clock;
}