- Nik's new decoupled audio code with fixes to minimize overruns.

- VBlank timing: increased to 20% of a frame.
This commit is contained in:
Bart Trzynadlowski 2011-09-12 05:43:37 +00:00
parent 4c18925679
commit cf73207c98
9 changed files with 1322 additions and 983 deletions

View file

@ -509,6 +509,10 @@ void CDSB1::SaveState(CBlockFile *StateFile)
// Z80 CPU state // Z80 CPU state
Z80.SaveState(StateFile, "DSB1 Z80"); Z80.SaveState(StateFile, "DSB1 Z80");
//DEBUG
//printf("usingMPEGStart=%X usingMPEGEnd=%X\n", usingMPEGStart, usingMPEGEnd);
//printf("usingLoopStart=%X usingLoopEnd=%X\n", usingLoopStart, usingLoopEnd);
} }
void CDSB1::LoadState(CBlockFile *StateFile) void CDSB1::LoadState(CBlockFile *StateFile)
@ -554,6 +558,10 @@ void CDSB1::LoadState(CBlockFile *StateFile)
} }
else else
MPEG_StopPlaying(); MPEG_StopPlaying();
//DEBUG
//printf("usingMPEGStart=%X usingMPEGEnd=%X\n", usingMPEGStart, usingMPEGEnd);
//printf("usingLoopStart=%X usingLoopEnd=%X\n", usingLoopStart, usingLoopEnd);
} }
// Offsets of memory regions within DSB1's pool // Offsets of memory regions within DSB1's pool
@ -1065,6 +1073,11 @@ void CDSB2::SaveState(CBlockFile *StateFile)
// 68K CPU state // 68K CPU state
M68KSetContext(&M68K); M68KSetContext(&M68K);
M68KSaveState(StateFile, "DSB2 68K"); M68KSaveState(StateFile, "DSB2 68K");
//DEBUG
//printf("mpegStart=%X, mpegEnd=%X\n", mpegStart, mpegEnd);
//printf("usingMPEGStart=%X, usingMPEGEnd=%X\n", usingMPEGStart, usingMPEGEnd);
//printf("usingLoopStart=%X, usingLoopEnd=%X\n", usingLoopStart, usingLoopEnd);
} }
void CDSB2::LoadState(CBlockFile *StateFile) void CDSB2::LoadState(CBlockFile *StateFile)

View file

@ -1917,8 +1917,8 @@ void CModel3::RunFrame(void)
if (!StartThreads()) if (!StartThreads())
goto ThreadError; goto ThreadError;
// Wake sound board and drive board threads so they can process a frame // Wake threads for sound board (if sync'd) and drive board (if attached) so they can process a frame
if (!sndBrdThreadSync->Post() || DriveBoard.IsAttached() && !drvBrdThreadSync->Post()) if (syncSndBrdThread && !sndBrdThreadSync->Post() || DriveBoard.IsAttached() && !drvBrdThreadSync->Post())
goto ThreadError; goto ThreadError;
// At the same time, process a single frame for main board (PPC) in this thread // At the same time, process a single frame for main board (PPC) in this thread
@ -1929,7 +1929,7 @@ void CModel3::RunFrame(void)
goto ThreadError; goto ThreadError;
// Wait for sound board and drive board threads to finish their work (if they haven't done so already) // Wait for sound board and drive board threads to finish their work (if they haven't done so already)
while (!sndBrdThreadDone || DriveBoard.IsAttached() && !drvBrdThreadDone) while (syncSndBrdThread && !sndBrdThreadDone || DriveBoard.IsAttached() && !drvBrdThreadDone)
{ {
if (!notifySync->Wait(notifyLock)) if (!notifySync->Wait(notifyLock))
goto ThreadError; goto ThreadError;
@ -1957,7 +1957,7 @@ ThreadError:
g_Config.multiThreaded = false; g_Config.multiThreaded = false;
} }
bool CModel3::StartThreads() bool CModel3::StartThreads(void)
{ {
if (startedThreads) if (startedThreads)
return true; return true;
@ -1966,6 +1966,12 @@ bool CModel3::StartThreads()
sndBrdThreadSync = CThread::CreateSemaphore(1); sndBrdThreadSync = CThread::CreateSemaphore(1);
if (sndBrdThreadSync == NULL) if (sndBrdThreadSync == NULL)
goto ThreadError; goto ThreadError;
sndBrdNotifyLock = CThread::CreateMutex();
if (sndBrdNotifyLock == NULL)
goto ThreadError;
sndBrdNotifySync = CThread::CreateCondVar();
if (sndBrdNotifySync == NULL)
goto ThreadError;
if (DriveBoard.IsAttached()) if (DriveBoard.IsAttached())
{ {
drvBrdThreadSync = CThread::CreateSemaphore(1); drvBrdThreadSync = CThread::CreateSemaphore(1);
@ -1979,19 +1985,26 @@ bool CModel3::StartThreads()
if (notifySync == NULL) if (notifySync == NULL)
goto ThreadError; goto ThreadError;
// Create sound board thread // Create sound board thread (sync'd or unsync'd)
if (syncSndBrdThread)
sndBrdThread = CThread::CreateThread(StartSoundBoardThreadSyncd, this);
else
sndBrdThread = CThread::CreateThread(StartSoundBoardThread, this); sndBrdThread = CThread::CreateThread(StartSoundBoardThread, this);
if (sndBrdThread == NULL) if (sndBrdThread == NULL)
goto ThreadError; goto ThreadError;
// Create drive board thread, if drive board is attached // Create drive board thread (sync'd), if drive board is attached
if (DriveBoard.IsAttached()) if (DriveBoard.IsAttached())
{ {
drvBrdThread = CThread::CreateThread(StartDriveBoardThread, this); drvBrdThread = CThread::CreateThread(StartDriveBoardThreadSyncd, this);
if (drvBrdThread == NULL) if (drvBrdThread == NULL)
goto ThreadError; goto ThreadError;
} }
// Set audio callback if unsync'd
if (!syncSndBrdThread)
SetAudioCallback(AudioCallback, this);
startedThreads = true; startedThreads = true;
return true; return true;
@ -2002,16 +2015,55 @@ ThreadError:
return false; return false;
} }
void CModel3::StopThreads() bool CModel3::PauseThreads(void)
{
if (!startedThreads)
return true;
// Enter notify critical section
if (!notifyLock->Lock())
goto ThreadError;
// Wait for all threads to finish their processing
pausedThreads = true;
while (sndBrdThreadRunning || drvBrdThreadRunning)
{
if (!notifySync->Wait(notifyLock))
goto ThreadError;
}
// Leave notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
return true;
ThreadError:
ErrorLog("Threading error in CModel3::PauseThreads: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false;
return false;
}
void CModel3::ResumeThreads(void)
{
// No need to use any locking here
pausedThreads = false;
return;
}
void CModel3::StopThreads(void)
{ {
if (!startedThreads) if (!startedThreads)
return; return;
// Remove callback
if (!syncSndBrdThread)
SetAudioCallback(NULL, NULL);
DeleteThreadObjects(); DeleteThreadObjects();
startedThreads = false; startedThreads = false;
} }
void CModel3::DeleteThreadObjects() void CModel3::DeleteThreadObjects(void)
{ {
// Delete (which in turn kills) sound board and drive board threads // Delete (which in turn kills) sound board and drive board threads
// Note that can do so here safely because threads will always be waiting on their semaphores when this method is called // Note that can do so here safely because threads will always be waiting on their semaphores when this method is called
@ -2037,6 +2089,16 @@ void CModel3::DeleteThreadObjects()
delete drvBrdThreadSync; delete drvBrdThreadSync;
drvBrdThreadSync = NULL; drvBrdThreadSync = NULL;
} }
if (sndBrdNotifyLock != NULL)
{
delete sndBrdNotifyLock;
sndBrdNotifyLock = NULL;
}
if (sndBrdNotifySync != NULL)
{
delete sndBrdNotifySync;
sndBrdNotifySync = NULL;
}
if (notifyLock != NULL) if (notifyLock != NULL)
{ {
delete notifyLock; delete notifyLock;
@ -2051,28 +2113,145 @@ void CModel3::DeleteThreadObjects()
int CModel3::StartSoundBoardThread(void *data) int CModel3::StartSoundBoardThread(void *data)
{ {
// Call sound board thread method on CModel3 // Call method on CModel3 to run unsync'd sound board thread
CModel3 *model3 = (CModel3*)data; CModel3 *model3 = (CModel3*)data;
model3->RunSoundBoardThread(); model3->RunSoundBoardThread();
return 0; return 0;
} }
int CModel3::StartDriveBoardThread(void *data) int CModel3::StartSoundBoardThreadSyncd(void *data)
{ {
// Call drive board thread method on CModel3 // Call method on CModel3 to run sync'd sound board thread
CModel3 *model3 = (CModel3*)data; CModel3 *model3 = (CModel3*)data;
model3->RunDriveBoardThread(); model3->RunSoundBoardThreadSyncd();
return 0; return 0;
} }
void CModel3::RunSoundBoardThread() int CModel3::StartDriveBoardThreadSyncd(void *data)
{
// Call method on CModel3 to run sync'd drive board thread
CModel3 *model3 = (CModel3*)data;
model3->RunDriveBoardThreadSyncd();
return 0;
}
void CModel3::AudioCallback(void *data)
{
// Call method on CModel3 to wake sound board thread
CModel3 *model3 = (CModel3*)data;
model3->WakeSoundBoardThread();
}
void CModel3::WakeSoundBoardThread(void)
{
// Enter sound board notify critical section
if (!sndBrdNotifyLock->Lock())
goto ThreadError;
// Signal to sound board that it should start processing again
if (!sndBrdNotifySync->Signal())
goto ThreadError;
// Exit sound board notify critical section
if (!sndBrdNotifyLock->Unlock())
goto ThreadError;
return;
ThreadError:
ErrorLog("Threading error in WakeSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false;
}
void CModel3::RunSoundBoardThread(void)
{ {
for (;;) for (;;)
{
bool wait = true;
while (wait)
{
// Enter sound board notify critical section
if (!sndBrdNotifyLock->Lock())
goto ThreadError;
// Wait for notification from audio callback
if (!sndBrdNotifySync->Wait(sndBrdNotifyLock))
goto ThreadError;
// Exit sound board notify critical section
if (!sndBrdNotifyLock->Unlock())
goto ThreadError;
// Enter main notify critical section
if (!notifyLock->Lock())
goto ThreadError;
// Check threads not paused
if (!pausedThreads)
{
wait = false;
sndBrdThreadRunning = true;
}
// Leave main notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
}
// Keep processing frames until audio buffer is half full
bool repeat = true;
// NOTE - performs an unlocked read of pausedThreads here, but this is okay
while (!pausedThreads && !SoundBoard.RunFrame())
{
//printf("Rerunning sound board\n");
}
// Enter main notify critical section
if (!notifyLock->Lock())
goto ThreadError;
// Let other threads know processing has finished
sndBrdThreadRunning = false;
sndBrdThreadDone = true;
if (!notifySync->SignalAll())
goto ThreadError;
// Leave main notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
}
ThreadError:
ErrorLog("Threading error in RunSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false;
}
void CModel3::RunSoundBoardThreadSyncd(void)
{
for (;;)
{
bool wait = true;
while (wait)
{ {
// Wait on sound board thread semaphore // Wait on sound board thread semaphore
if (!sndBrdThreadSync->Wait()) if (!sndBrdThreadSync->Wait())
goto ThreadError; goto ThreadError;
// Enter notify critical section
if (!notifyLock->Lock())
goto ThreadError;
// Check threads not paused
if (!pausedThreads)
{
wait = false;
sndBrdThreadRunning = true;
}
// Leave notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
}
// Process a single frame for sound board // Process a single frame for sound board
SoundBoard.RunFrame(); SoundBoard.RunFrame();
@ -2080,9 +2259,10 @@ void CModel3::RunSoundBoardThread()
if (!notifyLock->Lock()) if (!notifyLock->Lock())
goto ThreadError; goto ThreadError;
// Let main thread know processing has finished // Let other threads know processing has finished
sndBrdThreadRunning = false;
sndBrdThreadDone = true; sndBrdThreadDone = true;
if (!notifySync->Signal()) if (!notifySync->SignalAll())
goto ThreadError; goto ThreadError;
// Leave notify critical section // Leave notify critical section
@ -2091,18 +2271,37 @@ void CModel3::RunSoundBoardThread()
} }
ThreadError: ThreadError:
ErrorLog("Threading error in sound board thread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); ErrorLog("Threading error in RunSoundBoardThreadSyncd: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false; g_Config.multiThreaded = false;
} }
void CModel3::RunDriveBoardThread() void CModel3::RunDriveBoardThreadSyncd(void)
{ {
for (;;) for (;;)
{
bool wait = true;
while (wait)
{ {
// Wait on drive board thread semaphore // Wait on drive board thread semaphore
if (!drvBrdThreadSync->Wait()) if (!drvBrdThreadSync->Wait())
goto ThreadError; goto ThreadError;
// Enter notify critical section
if (!notifyLock->Lock())
goto ThreadError;
// Check threads not paused
if (!pausedThreads)
{
wait = false;
drvBrdThreadRunning = true;
}
// Leave notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
}
// Process a single frame for drive board // Process a single frame for drive board
DriveBoard.RunFrame(); DriveBoard.RunFrame();
@ -2110,9 +2309,10 @@ void CModel3::RunDriveBoardThread()
if (!notifyLock->Lock()) if (!notifyLock->Lock())
goto ThreadError; goto ThreadError;
// Let main thread know processing has finished // Let other threads know processing has finished
drvBrdThreadRunning = false;
drvBrdThreadDone = true; drvBrdThreadDone = true;
if (!notifySync->Signal()) if (!notifySync->SignalAll())
goto ThreadError; goto ThreadError;
// Leave notify critical section // Leave notify critical section
@ -2121,13 +2321,19 @@ void CModel3::RunDriveBoardThread()
} }
ThreadError: ThreadError:
ErrorLog("Threading error in drive board thread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError()); ErrorLog("Threading error in RunDriveBoardThreadSyncd: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false; g_Config.multiThreaded = false;
} }
void CModel3::RunMainBoardFrame(void) void CModel3::RunMainBoardFrame(void)
{ {
// Run the PowerPC for a frame // Compute display and VBlank timings
ppc_execute(g_Config.GetPowerPCFrequency()*1000000/60-10000); unsigned frameCycles = g_Config.GetPowerPCFrequency()*1000000/60;
unsigned vblCycles = (unsigned) ((float) frameCycles * 20.0f/100.0f); // 20% vblank (just a guess; probably too long)
unsigned dispCycles = frameCycles - vblCycles;
// Run the PowerPC for the active display part of the frame
ppc_execute(dispCycles);
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr()); //printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
// VBlank // VBlank
@ -2135,7 +2341,7 @@ void CModel3::RunMainBoardFrame(void)
GPU.BeginFrame(); GPU.BeginFrame();
GPU.RenderFrame(); GPU.RenderFrame();
IRQ.Assert(0x02); IRQ.Assert(0x02);
ppc_execute(10000); // TO-DO: Vblank probably needs to be longer. Maybe that's why some games run too fast/slow ppc_execute(vblCycles);
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr()); //printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
/* /*
@ -2736,10 +2942,14 @@ CModel3::CModel3(void)
securityPtr = 0; securityPtr = 0;
startedThreads = false; startedThreads = false;
pausedThreads = false;
sndBrdThread = NULL; sndBrdThread = NULL;
drvBrdThread = NULL; drvBrdThread = NULL;
sndBrdThreadRunning = false;
sndBrdThreadDone = false; sndBrdThreadDone = false;
drvBrdThreadRunning = false;
drvBrdThreadDone = false; drvBrdThreadDone = false;
syncSndBrdThread = false;
sndBrdThreadSync = NULL; sndBrdThreadSync = NULL;
drvBrdThreadSync = NULL; drvBrdThreadSync = NULL;
notifyLock = NULL; notifyLock = NULL;

View file

@ -291,6 +291,9 @@ public:
CModel3(void); CModel3(void);
~CModel3(void); ~CModel3(void);
bool PauseThreads(void);
void ResumeThreads(void);
/* /*
* Private Property. * Private Property.
* Tresspassers will be shot! ;) * Tresspassers will be shot! ;)
@ -306,16 +309,21 @@ private:
void WriteSystemRegister(unsigned reg, UINT8 data); void WriteSystemRegister(unsigned reg, UINT8 data);
void Patch(void); void Patch(void);
void RunMainBoardFrame(); // Runs the main board (PPC) for a frame void RunMainBoardFrame(void); // Runs the main board (PPC) for a frame
bool StartThreads(); // Starts all threads bool StartThreads(void); // Starts all threads
void StopThreads(); // Stops all threads void StopThreads(void); // Stops all threads
void DeleteThreadObjects(); // Deletes all threads and synchronization objects void DeleteThreadObjects(void); // Deletes all threads and synchronization objects
static int StartSoundBoardThread(void *data); // Callback to start sound board thread static int StartSoundBoardThread(void *data); // Callback to start unsync'd sound board thread
static int StartDriveBoardThread(void *data); // Callback to start drive board thread static int StartSoundBoardThreadSyncd(void *data); // Callback to start sync'd sound board thread
static int StartDriveBoardThreadSyncd(void *data); // Callback to start sync'd drive board thread
void RunSoundBoardThread(); // Runs sound board thread static void AudioCallback(void *data); // Audio buffer callback
void RunDriveBoardThread(); // Runs drive board thread
void WakeSoundBoardThread(void); // Used by audio callback to wake sound board thread when not sync'd with PPC thread
void RunSoundBoardThread(void); // Runs sound board thread unsync'd with PPC thread, ie at full speed
void RunSoundBoardThreadSyncd(void); // Runs sound board thread sync'd in step with PPC thread
void RunDriveBoardThreadSyncd(void); // Runs drive board thread sync'd in step with PPC thread
// Game and hardware information // Game and hardware information
const struct GameInfo *Game; const struct GameInfo *Game;
@ -357,13 +365,19 @@ private:
// Multiple threading // Multiple threading
bool startedThreads; // True if threads have been created and started bool startedThreads; // True if threads have been created and started
bool pausedThreads; // True if threads are currently paused
CThread *sndBrdThread; // Sound board thread CThread *sndBrdThread; // Sound board thread
CThread *drvBrdThread; // Drive board thread CThread *drvBrdThread; // Drive board thread
bool sndBrdThreadDone; // Flag to indicate sound board thread has finished processing for current frame bool sndBrdThreadRunning; // Flag to indicate sound board thread is currently processing
bool drvBrdThreadDone; // Flag to indicate drive board thread has finished processing for current frame bool sndBrdThreadDone; // Flag to indicate sound board thread has finished processing
bool drvBrdThreadRunning; // Flag to indicate drive board thread is currently processing
bool drvBrdThreadDone; // Flag to indicate drive board thread has finished processing
// Thread synchronization objects // Thread synchronization objects
bool syncSndBrdThread;
CSemaphore *sndBrdThreadSync; CSemaphore *sndBrdThreadSync;
CMutex *sndBrdNotifyLock;
CCondVar *sndBrdNotifySync;
CSemaphore *drvBrdThreadSync; CSemaphore *drvBrdThreadSync;
CMutex *notifyLock; CMutex *notifyLock;
CCondVar *notifySync; CCondVar *notifySync;

View file

@ -1,4 +1,3 @@
//TODO: before release, comment out printf()'s
/** /**
** Supermodel ** Supermodel
** A Sega Model 3 Arcade Emulator. ** A Sega Model 3 Arcade Emulator.
@ -358,7 +357,7 @@ void CSoundBoard::WriteMIDIPort(UINT8 data)
DSB->SendCommand(data); DSB->SendCommand(data);
} }
void CSoundBoard::RunFrame(void) bool CSoundBoard::RunFrame(void)
{ {
#ifdef SUPERMODEL_SOUND #ifdef SUPERMODEL_SOUND
// Run sound board first to generate SCSP audio // Run sound board first to generate SCSP audio
@ -379,7 +378,7 @@ void CSoundBoard::RunFrame(void)
DSB->RunFrame(audioL, audioR); DSB->RunFrame(audioL, audioR);
// Output the audio buffers // Output the audio buffers
OutputAudio(44100/60, audioL, audioR); bool bufferFull = OutputAudio(44100/60, audioL, audioR);
#ifdef SUPERMODEL_LOG_AUDIO #ifdef SUPERMODEL_LOG_AUDIO
// Output to binary file // Output to binary file
@ -391,8 +390,10 @@ void CSoundBoard::RunFrame(void)
s = audioR[i]; s = audioR[i];
fwrite(&s, sizeof(INT16), 1, soundFP); // right channel fwrite(&s, sizeof(INT16), 1, soundFP); // right channel
} }
#endif #endif // SUPERMODEL_LOG_AUDIO
#endif #endif // SUPERMODEL_SOUND
return bufferFull;
} }
void CSoundBoard::Reset(void) void CSoundBoard::Reset(void)

View file

@ -126,7 +126,7 @@ public:
* *
* Runs the sound board for one frame, updating sound in the process. * Runs the sound board for one frame, updating sound in the process.
*/ */
void RunFrame(void); bool RunFrame(void);
/* /*
* Reset(void): * Reset(void):

View file

@ -7,6 +7,12 @@
* Function-based interface for audio output. * Function-based interface for audio output.
*/ */
typedef void (*AudioCallbackFPtr)(void *data);
extern void SetAudioCallback(AudioCallbackFPtr callback, void *data);
extern void SetAudioEnabled(bool enabled);
/* /*
* OpenAudio() * OpenAudio()
* *
@ -19,7 +25,7 @@ extern bool OpenAudio();
* *
* Sends a chunk of two-channel audio with the given number of samples to the audio system. * Sends a chunk of two-channel audio with the given number of samples to the audio system.
*/ */
extern void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer); extern bool OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer);
/* /*
* CloseAudio() * CloseAudio()

View file

@ -38,6 +38,25 @@ static bool writeWrapped = false; // True if write position has wrapped around
static unsigned underRuns = 0; // Number of buffer under-runs that have occured static unsigned underRuns = 0; // Number of buffer under-runs that have occured
static unsigned overRuns = 0; // Number of buffer over-runs that have occured static unsigned overRuns = 0; // Number of buffer over-runs that have occured
static AudioCallbackFPtr callback = NULL;
static void *callbackData = NULL;
void SetAudioCallback(AudioCallbackFPtr newCallback, void *newData)
{
// Lock audio whilst changing callback pointers
SDL_LockAudio();
callback = newCallback;
callbackData = newData;
SDL_UnlockAudio();
}
void SetAudioEnabled(bool newEnabled)
{
enabled = newEnabled;
}
static void PlayCallback(void *data, Uint8 *stream, int len) static void PlayCallback(void *data, Uint8 *stream, int len)
{ {
//printf("PlayCallback(%d) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n", //printf("PlayCallback(%d) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n",
@ -128,6 +147,8 @@ static void PlayCallback(void *data, Uint8 *stream, int len)
// Move play position forward for next time // Move play position forward for next time
playPos += len; playPos += len;
bool halfEmpty = adjWritePos + audioBufferSize / 2 - BYTES_PER_FRAME / 2 < playPos + audioBufferSize;
// Check if play position has moved past end of buffer // Check if play position has moved past end of buffer
if (playPos >= audioBufferSize) if (playPos >= audioBufferSize)
{ {
@ -135,6 +156,10 @@ static void PlayCallback(void *data, Uint8 *stream, int len)
playPos -= audioBufferSize; playPos -= audioBufferSize;
writeWrapped = false; writeWrapped = false;
} }
// If buffer is more than half empty then call callback
if (callback && halfEmpty)
callback(callbackData);
} }
static void MixChannels(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer, void *dest) static void MixChannels(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer, void *dest)
@ -227,11 +252,13 @@ bool OpenAudio()
return OKAY; return OKAY;
} }
void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer) bool OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer)
{ {
//printf("OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n", //printf("OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n",
// numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize); // numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize);
bool halfFull = false;
UINT32 bytesRemaining; UINT32 bytesRemaining;
UINT32 bytesToCopy; UINT32 bytesToCopy;
INT16 *src; INT16 *src;
@ -296,6 +323,9 @@ void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer)
// Check if write position has caught up with play region and now overlaps it (ie buffer over-run) // Check if write position has caught up with play region and now overlaps it (ie buffer over-run)
bool overRun = writePos + numBytes > playPos + audioBufferSize; bool overRun = writePos + numBytes > playPos + audioBufferSize;
if (writePos + audioBufferSize / 2 + BYTES_PER_FRAME / 2 > playPos + audioBufferSize)
halfFull = true;
// Move write position back to within buffer // Move write position back to within buffer
if (writePos >= audioBufferSize) if (writePos >= audioBufferSize)
writePos -= audioBufferSize; writePos -= audioBufferSize;
@ -308,6 +338,8 @@ void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer)
//printf("Audio buffer over-run #%u in OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u, numBytes = %u]\n", //printf("Audio buffer over-run #%u in OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u, numBytes = %u]\n",
// overRuns, numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize, numBytes); // overRuns, numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize, numBytes);
halfFull = true;
// Discard current chunk of data // Discard current chunk of data
goto Finish; goto Finish;
} }
@ -366,6 +398,9 @@ void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer)
Finish: Finish:
// Unlock SDL audio callback // Unlock SDL audio callback
SDL_UnlockAudio(); SDL_UnlockAudio();
// Return whether buffer is half full
return halfFull;
} }
void CloseAudio() void CloseAudio()

View file

@ -762,16 +762,24 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
quit = 1; quit = 1;
#ifdef SUPERMODEL_DEBUGGER #ifdef SUPERMODEL_DEBUGGER
bool processUI = true;
if (Debugger != NULL) if (Debugger != NULL)
{ {
Debugger->Poll(); Debugger->Poll();
// Check if debugger requests exit or pause // Check if debugger requests exit or pause
if (Debugger->CheckExit()) if (Debugger->CheckExit())
{
quit = 1; quit = 1;
processUI = false;
}
else if (Debugger->CheckPause()) else if (Debugger->CheckPause())
{
paused = 1; paused = 1;
else processUI = false;
}
}
if (processUI)
{ {
#endif // SUPERMODEL_DEBUGGER #endif // SUPERMODEL_DEBUGGER
@ -783,6 +791,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
} }
else if (Inputs->uiReset->Pressed()) else if (Inputs->uiReset->Pressed())
{ {
if (!paused)
{
Model3->PauseThreads();
SetAudioEnabled(false);
}
// Reset emulator // Reset emulator
Model3->Reset(); Model3->Reset();
@ -792,17 +806,46 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
Debugger->Reset(); Debugger->Reset();
#endif // SUPERMODEL_DEBUGGER #endif // SUPERMODEL_DEBUGGER
if (!paused)
{
Model3->ResumeThreads();
SetAudioEnabled(true);
}
puts("Model 3 reset."); puts("Model 3 reset.");
} }
else if (Inputs->uiPause->Pressed()) else if (Inputs->uiPause->Pressed())
{ {
// Toggle emulator paused flag // Toggle emulator paused flag
paused = !paused; paused = !paused;
if (paused)
{
Model3->PauseThreads();
SetAudioEnabled(false);
}
else
{
Model3->ResumeThreads();
SetAudioEnabled(true);
}
} }
else if (Inputs->uiSaveState->Pressed()) else if (Inputs->uiSaveState->Pressed())
{ {
if (!paused)
{
Model3->PauseThreads();
SetAudioEnabled(false);
}
// Save game state // Save game state
SaveState(Model3); SaveState(Model3);
if (!paused)
{
Model3->ResumeThreads();
SetAudioEnabled(true);
}
} }
else if (Inputs->uiChangeSlot->Pressed()) else if (Inputs->uiChangeSlot->Pressed())
{ {
@ -813,6 +856,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
} }
else if (Inputs->uiLoadState->Pressed()) else if (Inputs->uiLoadState->Pressed())
{ {
if (!paused)
{
Model3->PauseThreads();
SetAudioEnabled(false);
}
// Load game state // Load game state
LoadState(Model3); LoadState(Model3);
@ -821,6 +870,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
if (Debugger != NULL) if (Debugger != NULL)
Debugger->Reset(); Debugger->Reset();
#endif // SUPERMODEL_DEBUGGER #endif // SUPERMODEL_DEBUGGER
if (!paused)
{
Model3->ResumeThreads();
SetAudioEnabled(true);
}
} }
else if (Inputs->uiMusicVolUp->Pressed()) else if (Inputs->uiMusicVolUp->Pressed())
{ {
@ -904,7 +959,7 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
printf("Frame limiting: %s\n", g_Config.throttle?"On":"Off"); printf("Frame limiting: %s\n", g_Config.throttle?"On":"Off");
} }
#ifdef SUPERMODEL_DEBUGGER #ifdef SUPERMODEL_DEBUGGER
else if (Inputs->uiEnterDebugger->Pressed()) else if (Debugger != NULL && Inputs->uiEnterDebugger->Pressed())
{ {
// Break execution and enter debugger // Break execution and enter debugger
Debugger->ForceBreak(true); Debugger->ForceBreak(true);

View file

@ -122,6 +122,11 @@ bool CCondVar::Signal()
return SDL_CondSignal((SDL_cond*)m_impl) == 0; return SDL_CondSignal((SDL_cond*)m_impl) == 0;
} }
bool CCondVar::SignalAll()
{
return SDL_CondBroadcast((SDL_cond*)m_impl) == 0;
}
CMutex::CMutex(void *impl) : m_impl(impl) CMutex::CMutex(void *impl) : m_impl(impl)
{ {
// //