mirror of
https://github.com/robmcmullen/apple2.git
synced 2024-06-09 10:29:27 +00:00
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:
parent
f67224d89f
commit
a2e007c1e0
|
@ -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?
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, ®s, sizeof(V65C02REGS));
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Get the clock of the currently executing CPU
|
||||
//
|
||||
|
@ -3034,3 +3031,4 @@ uint64_t GetCurrentV65C02Clock(void)
|
|||
{
|
||||
return regs.clock;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user