mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-25 23:25:40 +00:00
- Nik's new decoupled audio code with fixes to minimize overruns.
- VBlank timing: increased to 20% of a frame.
This commit is contained in:
parent
4c18925679
commit
cf73207c98
|
@ -509,6 +509,10 @@ void CDSB1::SaveState(CBlockFile *StateFile)
|
|||
|
||||
// Z80 CPU state
|
||||
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)
|
||||
|
@ -554,6 +558,10 @@ void CDSB1::LoadState(CBlockFile *StateFile)
|
|||
}
|
||||
else
|
||||
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
|
||||
|
@ -1065,6 +1073,11 @@ void CDSB2::SaveState(CBlockFile *StateFile)
|
|||
// 68K CPU state
|
||||
M68KSetContext(&M68K);
|
||||
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)
|
||||
|
|
|
@ -1917,8 +1917,8 @@ void CModel3::RunFrame(void)
|
|||
if (!StartThreads())
|
||||
goto ThreadError;
|
||||
|
||||
// Wake sound board and drive board threads so they can process a frame
|
||||
if (!sndBrdThreadSync->Post() || DriveBoard.IsAttached() && !drvBrdThreadSync->Post())
|
||||
// Wake threads for sound board (if sync'd) and drive board (if attached) so they can process a frame
|
||||
if (syncSndBrdThread && !sndBrdThreadSync->Post() || DriveBoard.IsAttached() && !drvBrdThreadSync->Post())
|
||||
goto ThreadError;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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))
|
||||
goto ThreadError;
|
||||
|
@ -1957,7 +1957,7 @@ ThreadError:
|
|||
g_Config.multiThreaded = false;
|
||||
}
|
||||
|
||||
bool CModel3::StartThreads()
|
||||
bool CModel3::StartThreads(void)
|
||||
{
|
||||
if (startedThreads)
|
||||
return true;
|
||||
|
@ -1966,6 +1966,12 @@ bool CModel3::StartThreads()
|
|||
sndBrdThreadSync = CThread::CreateSemaphore(1);
|
||||
if (sndBrdThreadSync == NULL)
|
||||
goto ThreadError;
|
||||
sndBrdNotifyLock = CThread::CreateMutex();
|
||||
if (sndBrdNotifyLock == NULL)
|
||||
goto ThreadError;
|
||||
sndBrdNotifySync = CThread::CreateCondVar();
|
||||
if (sndBrdNotifySync == NULL)
|
||||
goto ThreadError;
|
||||
if (DriveBoard.IsAttached())
|
||||
{
|
||||
drvBrdThreadSync = CThread::CreateSemaphore(1);
|
||||
|
@ -1979,19 +1985,26 @@ bool CModel3::StartThreads()
|
|||
if (notifySync == NULL)
|
||||
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);
|
||||
if (sndBrdThread == NULL)
|
||||
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())
|
||||
{
|
||||
drvBrdThread = CThread::CreateThread(StartDriveBoardThread, this);
|
||||
drvBrdThread = CThread::CreateThread(StartDriveBoardThreadSyncd, this);
|
||||
if (drvBrdThread == NULL)
|
||||
goto ThreadError;
|
||||
}
|
||||
|
||||
// Set audio callback if unsync'd
|
||||
if (!syncSndBrdThread)
|
||||
SetAudioCallback(AudioCallback, this);
|
||||
|
||||
startedThreads = true;
|
||||
return true;
|
||||
|
||||
|
@ -2002,16 +2015,55 @@ ThreadError:
|
|||
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)
|
||||
return;
|
||||
|
||||
// Remove callback
|
||||
if (!syncSndBrdThread)
|
||||
SetAudioCallback(NULL, NULL);
|
||||
|
||||
DeleteThreadObjects();
|
||||
startedThreads = false;
|
||||
}
|
||||
|
||||
void CModel3::DeleteThreadObjects()
|
||||
void CModel3::DeleteThreadObjects(void)
|
||||
{
|
||||
// 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
|
||||
|
@ -2037,6 +2089,16 @@ void CModel3::DeleteThreadObjects()
|
|||
delete drvBrdThreadSync;
|
||||
drvBrdThreadSync = NULL;
|
||||
}
|
||||
if (sndBrdNotifyLock != NULL)
|
||||
{
|
||||
delete sndBrdNotifyLock;
|
||||
sndBrdNotifyLock = NULL;
|
||||
}
|
||||
if (sndBrdNotifySync != NULL)
|
||||
{
|
||||
delete sndBrdNotifySync;
|
||||
sndBrdNotifySync = NULL;
|
||||
}
|
||||
if (notifyLock != NULL)
|
||||
{
|
||||
delete notifyLock;
|
||||
|
@ -2051,28 +2113,145 @@ void CModel3::DeleteThreadObjects()
|
|||
|
||||
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;
|
||||
model3->RunSoundBoardThread();
|
||||
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;
|
||||
model3->RunDriveBoardThread();
|
||||
model3->RunSoundBoardThreadSyncd();
|
||||
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 (;;)
|
||||
{
|
||||
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
|
||||
if (!sndBrdThreadSync->Wait())
|
||||
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
|
||||
SoundBoard.RunFrame();
|
||||
|
||||
|
@ -2080,9 +2259,10 @@ void CModel3::RunSoundBoardThread()
|
|||
if (!notifyLock->Lock())
|
||||
goto ThreadError;
|
||||
|
||||
// Let main thread know processing has finished
|
||||
// Let other threads know processing has finished
|
||||
sndBrdThreadRunning = false;
|
||||
sndBrdThreadDone = true;
|
||||
if (!notifySync->Signal())
|
||||
if (!notifySync->SignalAll())
|
||||
goto ThreadError;
|
||||
|
||||
// Leave notify critical section
|
||||
|
@ -2091,18 +2271,37 @@ void CModel3::RunSoundBoardThread()
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void CModel3::RunDriveBoardThread()
|
||||
void CModel3::RunDriveBoardThreadSyncd(void)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
bool wait = true;
|
||||
while (wait)
|
||||
{
|
||||
// Wait on drive board thread semaphore
|
||||
if (!drvBrdThreadSync->Wait())
|
||||
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
|
||||
DriveBoard.RunFrame();
|
||||
|
||||
|
@ -2110,9 +2309,10 @@ void CModel3::RunDriveBoardThread()
|
|||
if (!notifyLock->Lock())
|
||||
goto ThreadError;
|
||||
|
||||
// Let main thread know processing has finished
|
||||
// Let other threads know processing has finished
|
||||
drvBrdThreadRunning = false;
|
||||
drvBrdThreadDone = true;
|
||||
if (!notifySync->Signal())
|
||||
if (!notifySync->SignalAll())
|
||||
goto ThreadError;
|
||||
|
||||
// Leave notify critical section
|
||||
|
@ -2121,13 +2321,19 @@ void CModel3::RunDriveBoardThread()
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void CModel3::RunMainBoardFrame(void)
|
||||
{
|
||||
// Run the PowerPC for a frame
|
||||
ppc_execute(g_Config.GetPowerPCFrequency()*1000000/60-10000);
|
||||
// Compute display and VBlank timings
|
||||
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());
|
||||
|
||||
// VBlank
|
||||
|
@ -2135,7 +2341,7 @@ void CModel3::RunMainBoardFrame(void)
|
|||
GPU.BeginFrame();
|
||||
GPU.RenderFrame();
|
||||
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());
|
||||
|
||||
/*
|
||||
|
@ -2736,10 +2942,14 @@ CModel3::CModel3(void)
|
|||
securityPtr = 0;
|
||||
|
||||
startedThreads = false;
|
||||
pausedThreads = false;
|
||||
sndBrdThread = NULL;
|
||||
drvBrdThread = NULL;
|
||||
sndBrdThreadRunning = false;
|
||||
sndBrdThreadDone = false;
|
||||
drvBrdThreadRunning = false;
|
||||
drvBrdThreadDone = false;
|
||||
syncSndBrdThread = false;
|
||||
sndBrdThreadSync = NULL;
|
||||
drvBrdThreadSync = NULL;
|
||||
notifyLock = NULL;
|
||||
|
|
|
@ -291,6 +291,9 @@ public:
|
|||
CModel3(void);
|
||||
~CModel3(void);
|
||||
|
||||
bool PauseThreads(void);
|
||||
void ResumeThreads(void);
|
||||
|
||||
/*
|
||||
* Private Property.
|
||||
* Tresspassers will be shot! ;)
|
||||
|
@ -306,16 +309,21 @@ private:
|
|||
void WriteSystemRegister(unsigned reg, UINT8 data);
|
||||
void Patch(void);
|
||||
|
||||
void RunMainBoardFrame(); // Runs the main board (PPC) for a frame
|
||||
bool StartThreads(); // Starts all threads
|
||||
void StopThreads(); // Stops all threads
|
||||
void DeleteThreadObjects(); // Deletes all threads and synchronization objects
|
||||
void RunMainBoardFrame(void); // Runs the main board (PPC) for a frame
|
||||
bool StartThreads(void); // Starts all threads
|
||||
void StopThreads(void); // Stops all threads
|
||||
void DeleteThreadObjects(void); // Deletes all threads and synchronization objects
|
||||
|
||||
static int StartSoundBoardThread(void *data); // Callback to start sound board thread
|
||||
static int StartDriveBoardThread(void *data); // Callback to start drive board thread
|
||||
static int StartSoundBoardThread(void *data); // Callback to start unsync'd sound 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
|
||||
void RunDriveBoardThread(); // Runs drive board thread
|
||||
static void AudioCallback(void *data); // Audio buffer callback
|
||||
|
||||
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
|
||||
const struct GameInfo *Game;
|
||||
|
@ -357,13 +365,19 @@ private:
|
|||
|
||||
// Multiple threading
|
||||
bool startedThreads; // True if threads have been created and started
|
||||
bool pausedThreads; // True if threads are currently paused
|
||||
CThread *sndBrdThread; // Sound board thread
|
||||
CThread *drvBrdThread; // Drive board thread
|
||||
bool sndBrdThreadDone; // Flag to indicate sound board thread has finished processing for current frame
|
||||
bool drvBrdThreadDone; // Flag to indicate drive board thread has finished processing for current frame
|
||||
bool sndBrdThreadRunning; // Flag to indicate sound board thread is currently processing
|
||||
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
|
||||
bool syncSndBrdThread;
|
||||
CSemaphore *sndBrdThreadSync;
|
||||
CMutex *sndBrdNotifyLock;
|
||||
CCondVar *sndBrdNotifySync;
|
||||
CSemaphore *drvBrdThreadSync;
|
||||
CMutex *notifyLock;
|
||||
CCondVar *notifySync;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
//TODO: before release, comment out printf()'s
|
||||
/**
|
||||
** Supermodel
|
||||
** A Sega Model 3 Arcade Emulator.
|
||||
|
@ -358,7 +357,7 @@ void CSoundBoard::WriteMIDIPort(UINT8 data)
|
|||
DSB->SendCommand(data);
|
||||
}
|
||||
|
||||
void CSoundBoard::RunFrame(void)
|
||||
bool CSoundBoard::RunFrame(void)
|
||||
{
|
||||
#ifdef SUPERMODEL_SOUND
|
||||
// Run sound board first to generate SCSP audio
|
||||
|
@ -379,7 +378,7 @@ void CSoundBoard::RunFrame(void)
|
|||
DSB->RunFrame(audioL, audioR);
|
||||
|
||||
// Output the audio buffers
|
||||
OutputAudio(44100/60, audioL, audioR);
|
||||
bool bufferFull = OutputAudio(44100/60, audioL, audioR);
|
||||
|
||||
#ifdef SUPERMODEL_LOG_AUDIO
|
||||
// Output to binary file
|
||||
|
@ -391,8 +390,10 @@ void CSoundBoard::RunFrame(void)
|
|||
s = audioR[i];
|
||||
fwrite(&s, sizeof(INT16), 1, soundFP); // right channel
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif // SUPERMODEL_LOG_AUDIO
|
||||
#endif // SUPERMODEL_SOUND
|
||||
|
||||
return bufferFull;
|
||||
}
|
||||
|
||||
void CSoundBoard::Reset(void)
|
||||
|
|
|
@ -126,7 +126,7 @@ public:
|
|||
*
|
||||
* Runs the sound board for one frame, updating sound in the process.
|
||||
*/
|
||||
void RunFrame(void);
|
||||
bool RunFrame(void);
|
||||
|
||||
/*
|
||||
* Reset(void):
|
||||
|
|
|
@ -7,6 +7,12 @@
|
|||
* Function-based interface for audio output.
|
||||
*/
|
||||
|
||||
typedef void (*AudioCallbackFPtr)(void *data);
|
||||
|
||||
extern void SetAudioCallback(AudioCallbackFPtr callback, void *data);
|
||||
|
||||
extern void SetAudioEnabled(bool enabled);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
extern void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer);
|
||||
extern bool OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer);
|
||||
|
||||
/*
|
||||
* CloseAudio()
|
||||
|
|
|
@ -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 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)
|
||||
{
|
||||
//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
|
||||
playPos += len;
|
||||
|
||||
bool halfEmpty = adjWritePos + audioBufferSize / 2 - BYTES_PER_FRAME / 2 < playPos + audioBufferSize;
|
||||
|
||||
// Check if play position has moved past end of buffer
|
||||
if (playPos >= audioBufferSize)
|
||||
{
|
||||
|
@ -135,6 +156,10 @@ static void PlayCallback(void *data, Uint8 *stream, int len)
|
|||
playPos -= audioBufferSize;
|
||||
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)
|
||||
|
@ -227,11 +252,13 @@ bool OpenAudio()
|
|||
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",
|
||||
// numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize);
|
||||
|
||||
bool halfFull = false;
|
||||
|
||||
UINT32 bytesRemaining;
|
||||
UINT32 bytesToCopy;
|
||||
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)
|
||||
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
|
||||
if (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",
|
||||
// overRuns, numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize, numBytes);
|
||||
|
||||
halfFull = true;
|
||||
|
||||
// Discard current chunk of data
|
||||
goto Finish;
|
||||
}
|
||||
|
@ -366,6 +398,9 @@ void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer)
|
|||
Finish:
|
||||
// Unlock SDL audio callback
|
||||
SDL_UnlockAudio();
|
||||
|
||||
// Return whether buffer is half full
|
||||
return halfFull;
|
||||
}
|
||||
|
||||
void CloseAudio()
|
||||
|
|
|
@ -762,16 +762,24 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
|
|||
quit = 1;
|
||||
|
||||
#ifdef SUPERMODEL_DEBUGGER
|
||||
bool processUI = true;
|
||||
if (Debugger != NULL)
|
||||
{
|
||||
Debugger->Poll();
|
||||
|
||||
// Check if debugger requests exit or pause
|
||||
if (Debugger->CheckExit())
|
||||
{
|
||||
quit = 1;
|
||||
processUI = false;
|
||||
}
|
||||
else if (Debugger->CheckPause())
|
||||
{
|
||||
paused = 1;
|
||||
else
|
||||
processUI = false;
|
||||
}
|
||||
}
|
||||
if (processUI)
|
||||
{
|
||||
#endif // SUPERMODEL_DEBUGGER
|
||||
|
||||
|
@ -783,6 +791,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
|
|||
}
|
||||
else if (Inputs->uiReset->Pressed())
|
||||
{
|
||||
if (!paused)
|
||||
{
|
||||
Model3->PauseThreads();
|
||||
SetAudioEnabled(false);
|
||||
}
|
||||
|
||||
// Reset emulator
|
||||
Model3->Reset();
|
||||
|
||||
|
@ -792,17 +806,46 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
|
|||
Debugger->Reset();
|
||||
#endif // SUPERMODEL_DEBUGGER
|
||||
|
||||
if (!paused)
|
||||
{
|
||||
Model3->ResumeThreads();
|
||||
SetAudioEnabled(true);
|
||||
}
|
||||
|
||||
puts("Model 3 reset.");
|
||||
}
|
||||
else if (Inputs->uiPause->Pressed())
|
||||
{
|
||||
// Toggle emulator paused flag
|
||||
paused = !paused;
|
||||
|
||||
if (paused)
|
||||
{
|
||||
Model3->PauseThreads();
|
||||
SetAudioEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Model3->ResumeThreads();
|
||||
SetAudioEnabled(true);
|
||||
}
|
||||
}
|
||||
else if (Inputs->uiSaveState->Pressed())
|
||||
{
|
||||
if (!paused)
|
||||
{
|
||||
Model3->PauseThreads();
|
||||
SetAudioEnabled(false);
|
||||
}
|
||||
|
||||
// Save game state
|
||||
SaveState(Model3);
|
||||
|
||||
if (!paused)
|
||||
{
|
||||
Model3->ResumeThreads();
|
||||
SetAudioEnabled(true);
|
||||
}
|
||||
}
|
||||
else if (Inputs->uiChangeSlot->Pressed())
|
||||
{
|
||||
|
@ -813,6 +856,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
|
|||
}
|
||||
else if (Inputs->uiLoadState->Pressed())
|
||||
{
|
||||
if (!paused)
|
||||
{
|
||||
Model3->PauseThreads();
|
||||
SetAudioEnabled(false);
|
||||
}
|
||||
|
||||
// Load game state
|
||||
LoadState(Model3);
|
||||
|
||||
|
@ -821,6 +870,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
|
|||
if (Debugger != NULL)
|
||||
Debugger->Reset();
|
||||
#endif // SUPERMODEL_DEBUGGER
|
||||
|
||||
if (!paused)
|
||||
{
|
||||
Model3->ResumeThreads();
|
||||
SetAudioEnabled(true);
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
#ifdef SUPERMODEL_DEBUGGER
|
||||
else if (Inputs->uiEnterDebugger->Pressed())
|
||||
else if (Debugger != NULL && Inputs->uiEnterDebugger->Pressed())
|
||||
{
|
||||
// Break execution and enter debugger
|
||||
Debugger->ForceBreak(true);
|
||||
|
|
|
@ -122,6 +122,11 @@ bool CCondVar::Signal()
|
|||
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)
|
||||
{
|
||||
//
|
||||
|
|
Loading…
Reference in a new issue