Support variable emulation speed.

This commit is contained in:
Christian Speckner 2018-06-27 23:12:50 +02:00
parent 674e5f01c0
commit 2b23c81126
5 changed files with 112 additions and 38 deletions

View File

@ -58,6 +58,7 @@
"cstring": "cpp", "cstring": "cpp",
"iostream": "cpp", "iostream": "cpp",
"cstdint": "cpp", "cstdint": "cpp",
"ostream": "cpp" "ostream": "cpp",
"__memory": "cpp"
} }
} }

View File

@ -563,7 +563,8 @@ void Console::initializeAudio()
.updatePlaybackRate(myOSystem.sound().getSampleRate()) .updatePlaybackRate(myOSystem.sound().getSampleRate())
.updatePlaybackPeriod(myOSystem.sound().getFragmentSize()) .updatePlaybackPeriod(myOSystem.sound().getFragmentSize())
.updateAudioQueueExtraFragments(myAudioSettings.bufferSize()) .updateAudioQueueExtraFragments(myAudioSettings.bufferSize())
.updateAudioQueueHeadroom(myAudioSettings.headroom()); .updateAudioQueueHeadroom(myAudioSettings.headroom())
.updateSpeedFactor(myOSystem.settings().getFloat("speed"));
(cout << "sample rate: " << myOSystem.sound().getSampleRate() << std::endl).flush(); (cout << "sample rate: " << myOSystem.sound().getSampleRate() << std::endl).flush();
(cout << "fragment size: " << myOSystem.sound().getFragmentSize() << std::endl).flush(); (cout << "fragment size: " << myOSystem.sound().getFragmentSize() << std::endl).flush();

View File

@ -15,6 +15,8 @@
// this file, and for a DISCLAIMER OF ALL WARRANTIES. // this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================ //============================================================================
#include <cmath>
#include "EmulationTiming.hxx" #include "EmulationTiming.hxx"
namespace { namespace {
@ -32,13 +34,18 @@ EmulationTiming::EmulationTiming(FrameLayout frameLayout) :
myPlaybackRate(44100), myPlaybackRate(44100),
myPlaybackPeriod(512), myPlaybackPeriod(512),
myAudioQueueExtraFragments(1), myAudioQueueExtraFragments(1),
myAudioQueueHeadroom(2) myAudioQueueHeadroom(2),
{} mySpeedFactor(1)
{
recalculate();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationTiming& EmulationTiming::updateFrameLayout(FrameLayout frameLayout) EmulationTiming& EmulationTiming::updateFrameLayout(FrameLayout frameLayout)
{ {
myFrameLayout = frameLayout; myFrameLayout = frameLayout;
recalculate();
return *this; return *this;
} }
@ -46,6 +53,8 @@ EmulationTiming& EmulationTiming::updateFrameLayout(FrameLayout frameLayout)
EmulationTiming& EmulationTiming::updatePlaybackRate(uInt32 playbackRate) EmulationTiming& EmulationTiming::updatePlaybackRate(uInt32 playbackRate)
{ {
myPlaybackRate = playbackRate; myPlaybackRate = playbackRate;
recalculate();
return *this; return *this;
} }
@ -53,6 +62,8 @@ EmulationTiming& EmulationTiming::updatePlaybackRate(uInt32 playbackRate)
EmulationTiming& EmulationTiming::updatePlaybackPeriod(uInt32 playbackPeriod) EmulationTiming& EmulationTiming::updatePlaybackPeriod(uInt32 playbackPeriod)
{ {
myPlaybackPeriod = playbackPeriod; myPlaybackPeriod = playbackPeriod;
recalculate();
return *this; return *this;
} }
@ -60,6 +71,8 @@ EmulationTiming& EmulationTiming::updatePlaybackPeriod(uInt32 playbackPeriod)
EmulationTiming& EmulationTiming::updateAudioQueueExtraFragments(uInt32 audioQueueExtraFragments) EmulationTiming& EmulationTiming::updateAudioQueueExtraFragments(uInt32 audioQueueExtraFragments)
{ {
myAudioQueueExtraFragments = audioQueueExtraFragments; myAudioQueueExtraFragments = audioQueueExtraFragments;
recalculate();
return *this; return *this;
} }
@ -67,85 +80,123 @@ EmulationTiming& EmulationTiming::updateAudioQueueExtraFragments(uInt32 audioQue
EmulationTiming& EmulationTiming::updateAudioQueueHeadroom(uInt32 audioQueueHeadroom) EmulationTiming& EmulationTiming::updateAudioQueueHeadroom(uInt32 audioQueueHeadroom)
{ {
myAudioQueueHeadroom = audioQueueHeadroom; myAudioQueueHeadroom = audioQueueHeadroom;
recalculate();
return *this;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationTiming& EmulationTiming::updateSpeedFactor(float speedFactor)
{
mySpeedFactor = speedFactor;
recalculate();
return *this; return *this;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::maxCyclesPerTimeslice() const uInt32 EmulationTiming::maxCyclesPerTimeslice() const
{ {
return 2 * cyclesPerFrame(); return myMaxCyclesPerTimeslice;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::minCyclesPerTimeslice() const uInt32 EmulationTiming::minCyclesPerTimeslice() const
{ {
return cyclesPerFrame() / 2; return myMinCyclesPerTimeslice;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::linesPerFrame() const uInt32 EmulationTiming::linesPerFrame() const
{ {
switch (myFrameLayout) { return myLinesPerFrame;
case FrameLayout::ntsc:
return 262;
case FrameLayout::pal:
return 312;
default:
throw runtime_error("invalid frame layout");
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::cyclesPerFrame() const uInt32 EmulationTiming::cyclesPerFrame() const
{ {
return 76 * linesPerFrame(); return myCyclesPerFrame;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::framesPerSecond() const uInt32 EmulationTiming::framesPerSecond() const
{ {
switch (myFrameLayout) { return myFramesPerSecond;
case FrameLayout::ntsc:
return 60;
case FrameLayout::pal:
return 50;
default:
throw runtime_error("invalid frame layout");
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::cyclesPerSecond() const uInt32 EmulationTiming::cyclesPerSecond() const
{ {
return cyclesPerFrame() * framesPerSecond(); return myCyclesPerSecond;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::audioFragmentSize() const uInt32 EmulationTiming::audioFragmentSize() const
{ {
return AUDIO_HALF_FRAMES_PER_FRAGMENT * linesPerFrame(); return myAudioFragmentSize;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::audioSampleRate() const uInt32 EmulationTiming::audioSampleRate() const
{ {
return 2 * linesPerFrame() * framesPerSecond(); return myAudioSampleRate;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::audioQueueCapacity() const uInt32 EmulationTiming::audioQueueCapacity() const
{ {
uInt32 minCapacity = discreteDivCeil(maxCyclesPerTimeslice() * audioSampleRate(), audioFragmentSize() * cyclesPerSecond()); return myAudioQueueCapacity;
return std::max(prebufferFragmentCount(), minCapacity) + myAudioQueueExtraFragments;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::prebufferFragmentCount() const uInt32 EmulationTiming::prebufferFragmentCount() const
{ {
return discreteDivCeil(myPlaybackPeriod * audioSampleRate(), audioFragmentSize() * myPlaybackRate) + myAudioQueueHeadroom; return myPrebufferFragmentCount;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationTiming::recalculate()
{
switch (myFrameLayout) {
case FrameLayout::ntsc:
myLinesPerFrame = 262;
break;
case FrameLayout::pal:
myLinesPerFrame = 312;
break;
default:
throw runtime_error("invalid frame layout");
}
switch (myFrameLayout) {
case FrameLayout::ntsc:
myFramesPerSecond = round(mySpeedFactor * 60);
break;
case FrameLayout::pal:
myFramesPerSecond = round(mySpeedFactor * 50);
break;
default:
throw runtime_error("invalid frame layout");
}
myCyclesPerFrame = 76 * myLinesPerFrame;
myMaxCyclesPerTimeslice = round(mySpeedFactor * myCyclesPerFrame * 2);
myMinCyclesPerTimeslice = round(mySpeedFactor * myCyclesPerFrame / 2);
myCyclesPerSecond = myCyclesPerFrame * myFramesPerSecond;
myAudioFragmentSize = round(mySpeedFactor * AUDIO_HALF_FRAMES_PER_FRAGMENT * myLinesPerFrame);
myAudioSampleRate = 2 * myLinesPerFrame * myFramesPerSecond;
myPrebufferFragmentCount = discreteDivCeil(
myPlaybackPeriod * myAudioSampleRate,
myAudioFragmentSize * myPlaybackRate
) + myAudioQueueHeadroom;
myAudioQueueCapacity = std::max(
myPrebufferFragmentCount,
discreteDivCeil(myMaxCyclesPerTimeslice * myAudioSampleRate, myAudioFragmentSize * myCyclesPerSecond)
) + myAudioQueueExtraFragments;
} }

View File

@ -36,6 +36,8 @@ class EmulationTiming {
EmulationTiming& updateAudioQueueHeadroom(uInt32 audioQueueHeadroom); EmulationTiming& updateAudioQueueHeadroom(uInt32 audioQueueHeadroom);
EmulationTiming& updateSpeedFactor(float speedFactor);
uInt32 maxCyclesPerTimeslice() const; uInt32 maxCyclesPerTimeslice() const;
uInt32 minCyclesPerTimeslice() const; uInt32 minCyclesPerTimeslice() const;
@ -56,17 +58,32 @@ class EmulationTiming {
uInt32 prebufferFragmentCount() const; uInt32 prebufferFragmentCount() const;
private:
void recalculate();
private: private:
FrameLayout myFrameLayout; FrameLayout myFrameLayout;
uInt32 myPlaybackRate; uInt32 myPlaybackRate;
uInt32 myPlaybackPeriod; uInt32 myPlaybackPeriod;
uInt32 myAudioQueueExtraFragments; uInt32 myAudioQueueExtraFragments;
uInt32 myAudioQueueHeadroom; uInt32 myAudioQueueHeadroom;
uInt32 myMaxCyclesPerTimeslice;
uInt32 myMinCyclesPerTimeslice;
uInt32 myLinesPerFrame;
uInt32 myCyclesPerFrame;
uInt32 myFramesPerSecond;
uInt32 myCyclesPerSecond;
uInt32 myAudioFragmentSize;
uInt32 myAudioSampleRate;
uInt32 myAudioQueueCapacity;
uInt32 myPrebufferFragmentCount;
float mySpeedFactor;
private: private:
EmulationTiming(const EmulationTiming&) = delete; EmulationTiming(const EmulationTiming&) = delete;

View File

@ -35,7 +35,7 @@ Settings::Settings(OSystem& osystem)
{ {
// Video-related options // Video-related options
setInternal("video", ""); setInternal("video", "");
setInternal("framerate", "0"); setInternal("speed", "1.0");
setInternal("vsync", "true"); setInternal("vsync", "true");
setInternal("fullscreen", "false"); setInternal("fullscreen", "false");
setInternal("center", "false"); setInternal("center", "false");
@ -303,6 +303,10 @@ void Settings::validate()
{ {
string s; string s;
int i; int i;
float f;
f = getFloat("speed");
if (f <= 0) setInternal("speed", "1.0");
s = getString("timing"); s = getString("timing");
if(s != "sleep" && s != "busy") setInternal("timing", "sleep"); if(s != "sleep" && s != "busy") setInternal("timing", "sleep");
@ -439,7 +443,7 @@ void Settings::usage() const
<< " -palette <standard| Use the specified color palette\n" << " -palette <standard| Use the specified color palette\n"
<< " z26|\n" << " z26|\n"
<< " user>\n" << " user>\n"
<< " -framerate <number> Display the given number of frames per second (0 to auto-calculate)\n" << " -speed <number> Run emulation at the given speed\n"
<< " -timing <sleep|busy> Use the given type of wait between frames\n" << " -timing <sleep|busy> Use the given type of wait between frames\n"
<< " -uimessages <1|0> Show onscreen UI messages for different events\n" << " -uimessages <1|0> Show onscreen UI messages for different events\n"
<< endl << endl