enhanced jitter (resolves #316)

This commit is contained in:
Thomas Jentzsch 2022-06-05 15:14:51 +02:00
parent eb6d854599
commit 3beb5599e2
28 changed files with 367 additions and 150 deletions

View File

@ -45,6 +45,8 @@
* Added another oddball TIA glitch option for score mode color.
* Enhanced TV jitter emulation (TODO: doc).
* Enhanced support for CDFJ+ bankswitching type.
* Added 0FA0 bankswitching for Fotomania ROMs.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -1982,6 +1982,18 @@
<td>Cmd + J</td>
</tr>
<tr>
<td><i>Decrease</i> TV jitter sensitivity</td>
<td>Shift-Control-Alt + J</td>
<td>Shift-Control-Cmd + J</td>
</tr>
<tr>
<td><i>Increase</i> TV jitter sensitivity</td>
<td>Control-Alt + J</td>
<td>Control-Cmd + J</td>
</tr>
<tr>
<td><i>Decrease</i> TV jitter roll time</td>
<td>Shift-Control + J</td>
@ -3812,10 +3824,13 @@
<td><pre>-&lt;plr.|dev.&gt;tv.jitter &lt;1|0&gt;</pre></td>
<td>Enable TV jitter/roll effect, when there are too many or too few scanlines
per frame.</td>
</tr><tr>
<td><pre>-&lt;plr.|dev.&gt;tv.jitter_sense &lt;1 - 10&gt;</pre></td>
<td>When TV jitter/roll effect is enabled, determines the sensitivy to varying frame timings.</td>
</tr><tr>
<td><pre>-&lt;plr.|dev.&gt;tv.jitter_recovery &lt;1 - 20&gt;</pre></td>
<td>When TV jitter/roll effect is enabled, determines how long to delay recovery
time (recovery spread over multiple frames).</td>
<td>When TV jitter/roll effect is enabled, determines the recovery time for screen rolling
(recovery spread over multiple frames).</td>
</tr><tr>
<td><pre>-&lt;plr.|dev.&gt;colorloss &lt;1|0&gt;</pre></td>
<td>Enable/disable the PAL color-loss effect.</td>
@ -4677,6 +4692,7 @@
<table border="1" cellpadding="4">
<tr><th>Item</th><th>Brief description</th><th>For more information,<br>see <a href="#CommandLine">Command Line</a></th></tr>
<tr><td>Jitter/roll effect</td><td>Emulate screen roll with inconsistent scanline count</td><td>-plr.tv.jitter<br/>-dev.tv.jitter</td></tr>
<tr><td>(Jitter/roll) Sensitivity</td><td>Determines sensitivity for screen rolling</td><td><span style="white-space:nowrap">-plr.tv.jitter_sense<br/>-dev.tv.jitter_sense</span></td></tr>
<tr><td>(Jitter/roll) Recovery</td><td>Determines recovery time for screen rolling</td><td><span style="white-space:nowrap">-plr.tv.jitter_recovery<br/>-dev.tv.jitter_recovery</span></td></tr>
<tr><td>PAL color-loss</td><td>Use PAL color-loss effect</td><td>-plr.colorloss<br/>-dev.colorloss</td></tr>
<tr><td>Debug colors</td><td>Use fixed debug colors</td><td>-plr.debugcolors<br/>-dev.debugcolors</td></tr>

View File

@ -78,6 +78,7 @@ void DevSettingsHandler::loadSettings(SettingsSet set)
myColorLoss[set] = settings.getBool(prefix + "colorloss");
// Jitter
myTVJitter[set] = settings.getBool(prefix + "tv.jitter");
myTVJitterSense[set] = settings.getInt(prefix + "tv.jitter_sense");
myTVJitterRec[set] = settings.getInt(prefix + "tv.jitter_recovery");
// States
@ -148,6 +149,7 @@ void DevSettingsHandler::saveSettings(SettingsSet set)
settings.setValue(prefix + "colorloss", myColorLoss[set]);
// Jitter
settings.setValue(prefix + "tv.jitter", myTVJitter[set]);
settings.setValue(prefix + "tv.jitter_sense", myTVJitterSense[set]);
settings.setValue(prefix + "tv.jitter_recovery", myTVJitterRec[set]);
// States
@ -200,6 +202,7 @@ void DevSettingsHandler::applySettings(SettingsSet set)
{
// TV Jitter
myOSystem.console().tia().toggleJitter(myTVJitter[set] ? 1 : 0);
myOSystem.console().tia().setJitterSensitivity(myTVJitterSense[set]);
myOSystem.console().tia().setJitterRecoveryFactor(myTVJitterRec[set]);
// PAL color loss
myOSystem.console().enableColorLoss(myColorLoss[set]);

View File

@ -55,6 +55,7 @@ class DevSettingsHandler
std::array<string, numSets> myRandomizeCPU;
std::array<bool, numSets> myColorLoss;
std::array<bool, numSets> myTVJitter;
std::array<int, numSets> myTVJitterSense;
std::array<int, numSets> myTVJitterRec;
std::array<bool, numSets> myDebugColors;
std::array<bool, numSets> myUndrivenPins;

View File

@ -700,8 +700,10 @@ PhysicalKeyboardHandler::DefaultCommonMapping = {
{ Event::DecreaseSpeed, KBDK_S, KBDM_SHIFT | KBDM_CTRL },
{ Event::IncreaseSpeed, KBDK_S, KBDM_CTRL },
{ Event::ToggleTurbo, KBDK_T, KBDM_CTRL },
{ Event::JitterDecrease, KBDK_J, KBDM_SHIFT | KBDM_CTRL },
{ Event::JitterIncrease, KBDK_J, KBDM_CTRL },
{ Event::JitterSenseDecrease, KBDK_J, KBDM_SHIFT | MOD3 | KBDM_CTRL },
{ Event::JitterSenseIncrease, KBDK_J, MOD3 | KBDM_CTRL },
{ Event::JitterRecDecrease, KBDK_J, KBDM_SHIFT | KBDM_CTRL },
{ Event::JitterRecIncrease, KBDK_J, KBDM_CTRL },
{ Event::ToggleDeveloperSet, KBDK_D, MOD3 },
{ Event::ToggleJitter, KBDK_J, MOD3 },
{ Event::ToggleFrameStats, KBDK_L, MOD3 },

View File

@ -361,8 +361,10 @@ NLOHMANN_JSON_SERIALIZE_ENUM(Event::Type, {
{Event::TogglePhosphor, "TogglePhosphor"},
{Event::ToggleDeveloperSet, "ToggleDeveloperSet"},
{Event::ToggleInter, "ToggleInter"},
{Event::JitterDecrease, "JitterDecrease"},
{Event::JitterIncrease, "JitterIncrease"},
{Event::JitterSenseDecrease, "JitterSenseDecrease"},
{Event::JitterSenseIncrease, "JitterSenseIncrease"},
{Event::JitterRecDecrease, "JitterRecDecrease"},
{Event::JitterRecIncrease, "JitterRecIncrease"},
{Event::ToggleJitter, "ToggleJitter"},
{Event::VolumeDecrease, "VolumeDecrease"},
{Event::VolumeIncrease, "VolumeIncrease"},

View File

@ -1253,7 +1253,40 @@ void Console::toggleJitter(bool toggle) const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Console::changeJitter(int direction) const
void Console::changeJitterSense(int direction) const
{
const string prefix = myOSystem.settings().getBool("dev.settings") ? "dev." : "plr.";
int sensitivity = myOSystem.settings().getInt(prefix + "tv.jitter_sense");
const bool enabled = direction ? sensitivity + direction > 0 : myTIA->toggleJitter(3);
// if disabled, enable before first before increasing recovery
if(!myTIA->toggleJitter(3))
direction = 0;
sensitivity = BSPF::clamp(static_cast<Int32>(sensitivity + direction),
JitterEmulation::MIN_SENSITIVITY, JitterEmulation::MAX_SENSITIVITY);
myOSystem.settings().setValue(prefix + "tv.jitter", enabled);
if(enabled)
{
ostringstream val;
myTIA->toggleJitter(1);
myTIA->setJitterSensitivity(sensitivity);
myOSystem.settings().setValue(prefix + "tv.jitter_sense", sensitivity);
val << sensitivity;
myOSystem.frameBuffer().showGaugeMessage("TV jitter sensitivity", val.str(), sensitivity,
0, JitterEmulation::MAX_SENSITIVITY);
}
else
{
myTIA->toggleJitter(0);
myOSystem.frameBuffer().showTextMessage("TV scanline jitter disabled");
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Console::changeJitterRecovery(int direction) const
{
const string prefix = myOSystem.settings().getBool("dev.settings") ? "dev." : "plr.";
int recovery = myOSystem.settings().getInt(prefix + "tv.jitter_recovery");
@ -1263,7 +1296,8 @@ void Console::changeJitter(int direction) const
if(!myTIA->toggleJitter(3))
direction = 0;
recovery = BSPF::clamp(recovery + direction, 1, 20);
recovery = BSPF::clamp(static_cast<Int32>(recovery + direction),
JitterEmulation::MIN_RECOVERY, JitterEmulation::MAX_RECOVERY);
myOSystem.settings().setValue(prefix + "tv.jitter", enabled);
if(enabled)
@ -1274,7 +1308,8 @@ void Console::changeJitter(int direction) const
myTIA->setJitterRecoveryFactor(recovery);
myOSystem.settings().setValue(prefix + "tv.jitter_recovery", recovery);
val << recovery;
myOSystem.frameBuffer().showGaugeMessage("TV jitter roll", val.str(), recovery, 0, 20);
myOSystem.frameBuffer().showGaugeMessage("TV jitter roll", val.str(), recovery,
0, JitterEmulation::MAX_RECOVERY);
}
else
{

View File

@ -357,10 +357,15 @@ class Console : public Serializable, public ConsoleIO
*/
void toggleJitter(bool toggle = true) const;
/**
Changes the TIA 'scanline jitter' sensitivity.
*/
void changeJitterSense(int direction = +1) const;
/**
Changes the TIA 'scanline jitter' revcovery rate.
*/
void changeJitter(int direction = +1) const;
void changeJitterRecovery(int direction = +1) const;
/**
* Update vcenter

View File

@ -116,7 +116,8 @@ class Event
ScanlinesDecrease, ScanlinesIncrease,
PreviousScanlineMask, NextScanlineMask,
PhosphorDecrease, PhosphorIncrease, TogglePhosphor, ToggleInter,
ToggleDeveloperSet, JitterDecrease, JitterIncrease, ToggleJitter,
ToggleDeveloperSet, JitterRecDecrease, JitterRecIncrease,
JitterSenseDecrease, JitterSenseIncrease, ToggleJitter,
VolumeDecrease, VolumeIncrease, SoundToggle,

View File

@ -976,23 +976,39 @@ void EventHandler::handleEvent(Event::Type event, Int32 value, bool repeated)
if(pressed && !repeated)
{
myOSystem.console().toggleJitter();
myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::JITTER);
myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::JITTER_SENSE);
}
return;
case Event::JitterDecrease:
case Event::JitterSenseDecrease:
if(pressed)
{
myOSystem.console().changeJitter(-1);
myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::JITTER);
myOSystem.console().changeJitterSense(-1);
myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::JITTER_SENSE);
}
return;
case Event::JitterIncrease:
case Event::JitterSenseIncrease:
if(pressed)
{
myOSystem.console().changeJitter(+1);
myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::JITTER);
myOSystem.console().changeJitterSense(+1);
myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::JITTER_SENSE);
}
return;
case Event::JitterRecDecrease:
if(pressed)
{
myOSystem.console().changeJitterRecovery(-1);
myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::JITTER_REC);
}
return;
case Event::JitterRecIncrease:
if(pressed)
{
myOSystem.console().changeJitterRecovery(+1);
myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::JITTER_REC);
}
return;
@ -3041,8 +3057,10 @@ EventHandler::EmulActionList EventHandler::ourEmulActionList = { {
{ Event::ToggleFixedColors, "Toggle TIA 'Fixed Debug Colors' mode", "" },
{ Event::ToggleColorLoss, "Toggle PAL color-loss effect", "" },
{ Event::ToggleJitter, "Toggle TV scanline 'Jitter' effect", "" },
{ Event::JitterDecrease, "Decrease TV 'Jitter' roll", "" },
{ Event::JitterIncrease, "Increase TV 'Jitter' roll", "" },
{ Event::JitterSenseDecrease, "Decrease TV 'Jitter' sensitivity", "" },
{ Event::JitterSenseIncrease, "Increase TV 'Jitter' sensitivity", "" },
{ Event::JitterRecDecrease, "Decrease TV 'Jitter' roll", "" },
{ Event::JitterRecIncrease, "Increase TV 'Jitter' roll", "" },
// Combo
{ Event::Combo1, "Combo 1", "" },
@ -3230,5 +3248,6 @@ const Event::EventSet EventHandler::DebugEvents = {
Event::ToggleBLCollision, Event::ToggleBLBit, Event::TogglePFCollision, Event::TogglePFBit,
Event::ToggleCollisions, Event::ToggleBits, Event::ToggleFixedColors,
Event::ToggleColorLoss,
Event::ToggleJitter, Event::JitterDecrease,Event::JitterIncrease,
Event::ToggleJitter, Event::JitterSenseDecrease,Event::JitterSenseIncrease,
Event::JitterRecDecrease,Event::JitterRecIncrease,
};

View File

@ -524,7 +524,7 @@ class EventHandler
#else
REFRESH_SIZE = 0,
#endif
EMUL_ACTIONLIST_SIZE = 230 + PNG_SIZE + COMBO_SIZE + REFRESH_SIZE,
EMUL_ACTIONLIST_SIZE = 232 + PNG_SIZE + COMBO_SIZE + REFRESH_SIZE,
MENU_ACTIONLIST_SIZE = 19
;

View File

@ -436,7 +436,8 @@ GlobalKeyHandler::SettingData GlobalKeyHandler::getSettingData(const Setting set
{Setting::ALL_CX, {false, std::bind(&Console::toggleCollisions, &myOSystem.console(), _1)}}, // debug, not persisted
{Setting::FIXED_COL, {false, std::bind(&Console::toggleFixedColors, &myOSystem.console(), _1)}}, // debug, not persisted
{Setting::COLOR_LOSS, {false, std::bind(&Console::toggleColorLoss, &myOSystem.console(), _1)}},
{Setting::JITTER, {true, std::bind(&Console::changeJitter, &myOSystem.console(), _1)}},
{Setting::JITTER_SENSE, {true, std::bind(&Console::changeJitterSense, &myOSystem.console(), _1)}},
{Setting::JITTER_REC, {true, std::bind(&Console::changeJitterRecovery, &myOSystem.console(), _1)}},
// *** Following functions are not used when cycling settings, but for "direct only" hotkeys ***
{Setting::STATE, {true, std::bind(&StateManager::changeState, &myOSystem.state(), _1)}}, // temporary, not persisted
{Setting::PALETTE_ATTRIBUTE, {true, std::bind(&PaletteHandler::changeCurrentAdjustable, &myOSystem.frameBuffer().tiaSurface().paletteHandler(), _1)}},

View File

@ -118,7 +118,8 @@ class GlobalKeyHandler
ALL_CX,
FIXED_COL,
COLOR_LOSS,
JITTER,
JITTER_SENSE,
JITTER_REC,
// *** Only used via direct hotkeys ***
STATE,
PALETTE_ATTRIBUTE,
@ -131,7 +132,7 @@ class GlobalKeyHandler
START_INPUT_ADJ = DIGITAL_DEADZONE,
END_INPUT_ADJ = MOUSE_RANGE,
START_DEBUG_ADJ = DEVELOPER,
END_DEBUG_ADJ = JITTER,
END_DEBUG_ADJ = JITTER_REC,
};
public:

View File

@ -43,9 +43,9 @@ void MindLink::update()
myEvent.get(Event::MouseButtonRightValue))
myMindlinkPos = myMindlinkPos | TRIGGER_VALUE; // starts game, calibration and reverse
#ifdef DEBUG_BUILD
cerr << std::hex << myMindlinkPos << endl;
#endif
//#ifdef DEBUG_BUILD
// cerr << std::hex << myMindlinkPos << endl;
//#endif
myMindlinkShift = 1; // start transfer with least significant bit
nextMindlinkBit();

View File

@ -25,6 +25,7 @@
#include "PaletteHandler.hxx"
#include "Joystick.hxx"
#include "Paddles.hxx"
#include "JitterEmulation.hxx"
#ifdef DEBUGGER_SUPPORT
#include "DebuggerDialog.hxx"
@ -225,7 +226,8 @@ Settings::Settings()
setPermanent("plr.tiarandom", "false");
setPermanent("plr.colorloss", "false");
setPermanent("plr.tv.jitter", "true");
setPermanent("plr.tv.jitter_recovery", "10");
setPermanent("plr.tv.jitter_sense", JitterEmulation::PLR_SENSITIVITY);
setPermanent("plr.tv.jitter_recovery", JitterEmulation::PLR_RECOVERY);
setPermanent("plr.debugcolors", "false");
setPermanent("plr.console", "2600"); // 7800
setPermanent("plr.timemachine", true);
@ -245,7 +247,8 @@ Settings::Settings()
setPermanent("dev.tiarandom", "true");
setPermanent("dev.colorloss", "true");
setPermanent("dev.tv.jitter", "true");
setPermanent("dev.tv.jitter_recovery", "2");
setPermanent("dev.tv.jitter_sense", JitterEmulation::DEV_SENSITIVITY);
setPermanent("dev.tv.jitter_recovery", JitterEmulation::DEV_RECOVERY);
setPermanent("dev.debugcolors", "false");
setPermanent("dev.tiadriven", "true");
setPermanent("dev.console", "2600"); // 7800
@ -336,8 +339,13 @@ void Settings::validate()
i = getInt("tv.filter");
if(i < 0 || i > 5) setValue("tv.filter", "0");
i = getInt("dev.tv.jitter_sense");
if(i < JitterEmulation::MIN_SENSITIVITY || i > JitterEmulation::MAX_SENSITIVITY)
setValue("dev.tv.jitter_sense", JitterEmulation::DEV_SENSITIVITY);
i = getInt("dev.tv.jitter_recovery");
if(i < 1 || i > 20) setValue("dev.tv.jitter_recovery", "2");
if(i < JitterEmulation::MIN_RECOVERY || i > JitterEmulation::MAX_RECOVERY)
setValue("dev.tv.jitter_recovery", JitterEmulation::DEV_RECOVERY);
int size = getInt("dev.tm.size");
if(size < 20 || size > 1000)
@ -355,8 +363,12 @@ void Settings::validate()
i = getInt("dev.tm.horizon");
if(i < 0 || i > 6) setValue("dev.tm.horizon", 1);*/
i = getInt("plr.tv.jitter_sense");
if(i < JitterEmulation::MIN_SENSITIVITY || i > JitterEmulation::MAX_SENSITIVITY)
setValue("plr.tv.jitter_sense", JitterEmulation::PLR_SENSITIVITY);
i = getInt("plr.tv.jitter_recovery");
if(i < 1 || i > 20) setValue("plr.tv.jitter_recovery", "10");
if(i < 1 || i > 20) setValue("plr.tv.jitter_recovery", JitterEmulation::PLR_RECOVERY);
size = getInt("plr.tm.size");
if(size < 20 || size > 1000)
@ -713,6 +725,7 @@ void Settings::usage() const
<< " -plr.debugcolors <1|0> Enable debug colors\n"
<< " -plr.colorloss <1|0> Enable PAL color-loss effect\n"
<< " -plr.tv.jitter <1|0> Enable TV jitter effect\n"
<< " -plr.tv.jitter_sense <1-10> Set TV jitter effect sensitivity\n"
<< " -plr.tv.jitter_recovery <1-20> Set recovery time for TV jitter effect\n"
<< " -plr.extaccess <1|0> Enable messages for external access\n"
<< endl
@ -729,8 +742,9 @@ void Settings::usage() const
<< " -dev.debugcolors <1|0> Enable debug colors\n"
<< " -dev.colorloss <1|0> Enable PAL color-loss effect\n"
<< " -dev.tv.jitter <1|0> Enable TV jitter effect\n"
<< " -dev.tv.jitter_sense <1-10> Set TV jitter effect sensitivity\n"
<< " -dev.tv.jitter_recovery <1-20> Set recovery time for TV jitter effect\n"
<< " -dev.tiadriven <1|0> Drive unused TIA pins randomly on a\n"
<< " -dev.tiadriven <1|0> Drive unqused TIA pins randomly on a\n"
<< " read/peek\n"
#ifdef DEBUGGER_SUPPORT
<< " -dev.rwportbreak <1|0> Debugger breaks on reads from write ports\n"

View File

@ -104,7 +104,8 @@ void TIA::setFrameManager(AbstractFrameManager* frameManager)
);
myFrameManager->enableJitter(myEnableJitter);
myFrameManager->setJitterFactor(myJitterFactor);
myFrameManager->setJitterSensitivity(myJitterSensitivity);
myFrameManager->setJitterRecovery(myJitterRecovery);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -544,7 +545,7 @@ bool TIA::poke(uInt16 address, uInt8 value)
break;
case VSYNC:
myFrameManager->setVsync(value & 0x02);
myFrameManager->setVsync(value & 0x02, mySystem->cycles());
myShadowRegisters[address] = value;
break;
@ -985,7 +986,8 @@ void TIA::applyDeveloperSettings()
myTIAPinsDriven = devSettings ? mySettings.getBool("dev.tiadriven") : false;
myEnableJitter = mySettings.getBool(devSettings ? "dev.tv.jitter" : "plr.tv.jitter");
myJitterFactor = mySettings.getInt(devSettings ? "dev.tv.jitter_recovery" : "plr.tv.jitter_recovery");
myJitterSensitivity = mySettings.getInt(devSettings ? "dev.tv.jitter_sense" : "plr.tv.jitter_sense");
myJitterRecovery = mySettings.getInt(devSettings ? "dev.tv.jitter_recovery" : "plr.tv.jitter_recovery");
if(myFrameManager)
enableColorLoss(mySettings.getBool(devSettings ? "dev.colorloss" : "plr.colorloss"));
@ -1281,12 +1283,20 @@ bool TIA::toggleJitter(uInt8 mode)
return myEnableJitter;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIA::setJitterSensitivity(Int32 sensitivity)
{
myJitterSensitivity = sensitivity;
if (myFrameManager) myFrameManager->setJitterSensitivity(sensitivity);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIA::setJitterRecoveryFactor(Int32 factor)
{
myJitterFactor = factor;
myJitterRecovery = factor;
if (myFrameManager) myFrameManager->setJitterFactor(myJitterFactor);
if (myFrameManager) myFrameManager->setJitterRecovery(myJitterRecovery);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -441,6 +441,7 @@ class TIA : public Device
@return Whether the mode was enabled or disabled
*/
bool toggleJitter(uInt8 mode = 2);
void setJitterSensitivity(Int32 sensitivity);
void setJitterRecoveryFactor(Int32 factor);
/**
@ -977,7 +978,8 @@ class TIA : public Device
* The frame manager can change during our lifetime, so we buffer those two.
*/
bool myEnableJitter{false};
uInt8 myJitterFactor{0};
uInt8 myJitterSensitivity{0};
uInt8 myJitterRecovery{0};
static constexpr uInt16
TIA_SIZE = 0x40, TIA_MASK = TIA_SIZE - 1,

View File

@ -72,13 +72,13 @@ void AbstractFrameManager::setVblank(bool vblank)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AbstractFrameManager::setVsync(bool vsync)
void AbstractFrameManager::setVsync(bool vsync, uInt64 cycles)
{
if (vsync == myVsync) return;
myVsync = vsync;
onSetVsync();
onSetVsync(cycles);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -67,7 +67,7 @@ class AbstractFrameManager : public Serializable
/**
* Called by TIA on VSYNC writes.
*/
void setVsync(bool vsync);
void setVsync(bool vsync, uInt64 cycles);
/**
* Should the TIA render its frame? This is buffered in a flag for
@ -123,10 +123,15 @@ class AbstractFrameManager : public Serializable
// required. All of these are irrelevant if nothing is displayed (during
// autodetect).
/**
* The jitter sensitivity determines jitter simulation sensitivity to unstable video signals.
*/
virtual void setJitterSensitivity(uInt8 sensitivity) {}
/**
* The jitter factor determines the time jitter simulation takes to recover.
*/
virtual void setJitterFactor(uInt8 factor) {}
virtual void setJitterRecovery(uInt8 factor) {}
/**
* Is jitter simulation enabled?
@ -207,7 +212,7 @@ class AbstractFrameManager : public Serializable
/**
* Called if vsync changes.
*/
virtual void onSetVsync() {}
virtual void onSetVsync(uInt64 cycles) {}
/**
* Called if the next line is signalled, after the internal bookkeeping has

View File

@ -39,7 +39,7 @@ void FrameLayoutDetector::onReset()
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameLayoutDetector::onSetVsync()
void FrameLayoutDetector::onSetVsync(uInt64)
{
if (myVsync)
setState(State::waitForVsyncEnd);

View File

@ -44,7 +44,7 @@ class FrameLayoutDetector: public AbstractFrameManager
/**
* Hook into vsync changes.
*/
void onSetVsync() override;
void onSetVsync(uInt64) override;
/**
* Hook into reset.

View File

@ -114,10 +114,16 @@ void FrameManager::setAdjustVSize(Int32 adjustVSize)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameManager::onSetVsync()
void FrameManager::onSetVsync(uInt64 cycles)
{
if (myState == State::waitForVsyncEnd) setState(State::waitForFrameStart);
else setState(State::waitForVsyncEnd);
if (myState == State::waitForVsyncEnd) {
myVsyncEnd = cycles;
setState(State::waitForFrameStart);
}
else {
myVsyncStart = cycles;
setState(State::waitForVsyncEnd);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -133,7 +139,7 @@ void FrameManager::setState(FrameManager::State state)
notifyFrameComplete();
if (myTotalFrames > Metrics::initialGarbageFrames)
myJitterEmulation.frameComplete(myCurrentFrameFinalLines);
myJitterEmulation.frameComplete(myCurrentFrameFinalLines, myVsyncEnd - myVsyncStart);
notifyFrameStart();

View File

@ -47,8 +47,9 @@ class FrameManager: public AbstractFrameManager {
FrameManager();
public:
void setJitterSensitivity(uInt8 sensitivity) override { myJitterEmulation.setSensitivity(sensitivity); }
void setJitterFactor(uInt8 factor) override { myJitterEmulation.setJitterFactor(factor); }
void setJitterRecovery(uInt8 factor) override { myJitterEmulation.setRecovery(factor); }
bool jitterEnabled() const override { return myJitterEnabled; }
@ -78,7 +79,7 @@ class FrameManager: public AbstractFrameManager {
void setLayout(FrameLayout mode) override { layout(mode); }
void onSetVsync() override;
void onSetVsync(uInt64 cycles) override;
void onNextLine() override;
@ -122,6 +123,8 @@ class FrameManager: public AbstractFrameManager {
Int32 myMaxVcenter{0};
Int32 myVSizeAdjust{0};
uInt64 myVsyncStart{0};
uInt64 myVsyncEnd{0};
bool myJitterEnabled{false};
JitterEmulation myJitterEmulation;

View File

@ -26,52 +26,103 @@ JitterEmulation::JitterEmulation()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void JitterEmulation::reset()
{
myLastFrameScanlines = 0;
myStableFrameFinalLines = -1;
myStableFrames = 0;
myStabilizationCounter = 0;
myDestabilizationCounter = 0;
myJitter = 0;
setSensitivity(mySensitivity);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void JitterEmulation::frameComplete(uInt32 scanlineCount)
void JitterEmulation::setSensitivity(Int32 sensitivity)
{
if (static_cast<Int32>(scanlineCount) != myStableFrameFinalLines) {
if (myDestabilizationCounter++ > Metrics::framesUntilDestabilization) myStableFrameFinalLines = -1;
myLastFrameScanlines = myLastFrameVsyncCycles = myUnstableCount = myJitter = 0;
mySensitivity = BSPF::clamp(sensitivity, MIN_SENSITIVITY, MAX_SENSITIVITY);
if (scanlineCount == myLastFrameScanlines) {
const float factor = pow(static_cast<float>(mySensitivity - MIN_SENSITIVITY) / (MAX_SENSITIVITY - MIN_SENSITIVITY), 1.5);
if (++myStabilizationCounter >= Metrics::framesForStableHeight) {
if (myStableFrameFinalLines > 0) updateJitter(scanlineCount - myStableFrameFinalLines);
myScanlineDelta = round(MAX_SCANLINE_DELTA - (MAX_SCANLINE_DELTA - MIN_SCANLINE_DELTA) * factor);
myVsyncCycles = round(MIN_VSYNC_CYCLES + (MAX_VSYNC_CYCLES - MIN_VSYNC_CYCLES) * factor);
myVsyncDelta1 = round(MAX_VSYNC_DELTA_1 - (MAX_VSYNC_DELTA_1 - MIN_VSYNC_DELTA_1) * factor);
#ifdef VSYNC_LINE_JITTER
myVsyncDelta2 = round(MIN_VSYNC_DELTA_2 + (MAX_VSYNC_DELTA_2 - MIN_VSYNC_DELTA_2) * factor);
#endif
myUnstableFrames = round(MAX_UNSTABLE_FRAMES - (MAX_UNSTABLE_FRAMES - MIN_UNSTABLE_FRAMES) * factor);
myJitterLines = round(MIN_JITTER_LINES + (MAX_JITTER_LINES - MIN_JITTER_LINES) * factor);
myVsyncLines = round(MIN_VSYNC_LINES + (MAX_VSYNC_LINES - MIN_VSYNC_LINES) * factor);
}
myStableFrameFinalLines = scanlineCount;
myDestabilizationCounter = 0;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void JitterEmulation::frameComplete(Int32 scanlineCount, Int32 vsyncCycles)
{
#ifdef DEBUG_BUILD
const int vsyncLines = round((vsyncCycles - 2) / 76.0);
cerr << "TV jitter " << myJitter << " - " << scanlineCount << ", " << vsyncCycles << ", " << vsyncLines << endl;
#endif
// Check if current frame size is stable compared to previous frame
const bool scanlinesStable = scanlineCount == myLastFrameScanlines;
const bool vsyncCyclesStable = vsyncCycles >= myVsyncCycles;
// Handle inconsistency of vsync cycles and around half lines
#ifdef VSYNC_LINE_JITTER
const Int32 minLines = round((vsyncCycles - 2 - myVsyncDelta2) / 76.0);
const Int32 maxLines = round((vsyncCycles - 2 + myVsyncDelta2) / 76.0);
const Int32 minLastLines = round((myLastFrameVsyncCycles - 2 - myVsyncDelta2) / 76.0);
const Int32 maxLastLines = round((myLastFrameVsyncCycles - 2 + myVsyncDelta2) / 76.0);
const bool vsyncLinesStable = abs(vsyncCycles - myLastFrameVsyncCycles) < myVsyncDelta1
&& minLines == maxLastLines && maxLines == minLastLines;
#else
const bool vsyncLinesStable = abs(vsyncCycles - myLastFrameVsyncCycles) < myVsyncDelta1;
#endif
if(!scanlinesStable || !vsyncCyclesStable || !vsyncLinesStable)
{
if(++myUnstableCount >= myUnstableFrames)
{
if(!scanlinesStable)
{
const Int32 scanlineDifference = scanlineCount - myLastFrameScanlines;
if(abs(scanlineDifference) < myScanlineDelta
&& abs(myJitter) < static_cast<Int32>(myRandom.next() % myJitterLines))
{
// Repeated invalid frames cause randomly repeated jitter
myJitter = std::max(std::min(scanlineDifference, myJitterLines), -myYStart);
}
}
if(!vsyncCyclesStable)
{
// If VSYNC length is too low, the frame rolls permanently down, speed depending on missing cycles
const Int32 jitter = std::max(
std::min<Int32>(round(scanlineCount * (1 - static_cast<float>(vsyncCycles) / myVsyncCycles)),
myJitterLines),
myJitterRecovery + 1); // Roll at least one scanline
myJitter -= jitter;
// Limit jitter to -myYstart..scanlineCount
if(myJitter < -myYStart)
myJitter += 262;
}
if(!vsyncLinesStable)
{
#ifdef VSYNC_LINE_JITTER
myJitter += minLines > maxLastLines ? myVsyncLines : -myVsyncLines;
#else
myJitter += vsyncCycles > myLastFrameVsyncCycles ? myVsyncLines : -myVsyncLines;
#endif
}
myJitter = std::max(myJitter, -myYStart);
}
else myStabilizationCounter = 0;
}
else myDestabilizationCounter = 0;
else
{
myUnstableCount = 0;
// Only recover during stable frames
if(myJitter > 0)
myJitter = std::max(myJitter - myJitterRecovery, 0);
else if(myJitter < 0)
myJitter = std::min(myJitter + myJitterRecovery, 0);
}
myLastFrameScanlines = scanlineCount;
if (myJitter > 0) myJitter = std::max(myJitter - myJitterFactor, 0);
if (myJitter < 0) myJitter = std::min(myJitter + myJitterFactor, 0);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void JitterEmulation::updateJitter(Int32 scanlineDifference)
{
if (static_cast<uInt32>(abs(scanlineDifference)) < Metrics::minDeltaForJitter) return;
Int32 jitter = std::min<Int32>(scanlineDifference, Metrics::maxJitter);
jitter = std::max<Int32>(jitter, -myYStart);
if (jitter > 0) jitter += myJitterFactor;
if (jitter < 0) jitter -= myJitterFactor;
if (abs(jitter) > abs(myJitter)) myJitter = jitter;
myLastFrameVsyncCycles = vsyncCycles;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -79,14 +130,13 @@ bool JitterEmulation::save(Serializer& out) const
{
try
{
out.putInt(myLastFrameScanlines);
out.putInt(myStableFrameFinalLines);
out.putInt(myStableFrames);
out.putInt(myStabilizationCounter);
out.putInt(myDestabilizationCounter);
out.putInt(myJitter);
out.putInt(myJitterFactor);
out.putInt(mySensitivity);
out.putInt(myJitterRecovery);
out.putInt(myYStart);
out.putInt(myLastFrameScanlines);
out.putInt(myLastFrameVsyncCycles);
out.putInt(myUnstableCount);
out.putInt(myJitter);
}
catch(...)
{
@ -103,14 +153,13 @@ bool JitterEmulation::load(Serializer& in)
{
try
{
myLastFrameScanlines = in.getInt();
myStableFrameFinalLines = in.getInt();
myStableFrames = in.getInt();
myStabilizationCounter = in.getInt();
myDestabilizationCounter = in.getInt();
myJitter = in.getInt();
myJitterFactor = in.getInt();
mySensitivity = in.getInt();
myJitterRecovery = in.getInt();
myYStart = in.getInt();
myLastFrameScanlines = in.getInt();
myLastFrameVsyncCycles = in.getInt();
myUnstableCount = in.getInt();
myJitter = in.getInt();
}
catch (...)
{
@ -118,6 +167,7 @@ bool JitterEmulation::load(Serializer& in)
return false;
}
setSensitivity(mySensitivity);
return true;
}

View File

@ -18,27 +18,38 @@
#ifndef TIA_JITTER_EMULATION
#define TIA_JITTER_EMULATION
//#define VSYNC_LINE_JITTER // makes e.g. Alien jitter, but not on my TV (TJ)
#include "bspf.hxx"
#include "Serializable.hxx"
#include "Random.hxx"
class JitterEmulation: public Serializable
class JitterEmulation : public Serializable
{
public:
JitterEmulation();
public:
// sensitivity:
static constexpr Int32 MIN_SENSITIVITY = 1;
static constexpr Int32 MAX_SENSITIVITY = 10;
static constexpr Int32 PLR_SENSITIVITY = 3;
static constexpr Int32 DEV_SENSITIVITY = 8;
// roll speed:
static constexpr Int32 MIN_RECOVERY = 1;
static constexpr Int32 MAX_RECOVERY = 20;
static constexpr Int32 PLR_RECOVERY = 10;
static constexpr Int32 DEV_RECOVERY = 2;
public:
void reset();
void setSensitivity(Int32 sensitivity);
void setRecovery(Int32 recoveryFactor) { myJitterRecovery = recoveryFactor; }
void setYStart(Int32 ystart) { myYStart = ystart; }
void frameComplete(uInt32 scanlineCount);
void setJitterFactor(Int32 factor) { myJitterFactor = factor; }
void frameComplete(Int32 scanlineCount, Int32 vsyncCycles);
Int32 jitter() const { return myJitter; }
void setYStart(uInt32 ystart) { myYStart = ystart; }
/**
* Save state.
*/
@ -50,36 +61,48 @@ class JitterEmulation: public Serializable
bool load(Serializer& in) override;
private:
void updateJitter(Int32 scanlineDifference);
// varying scanlines:
static constexpr Int32 MIN_SCANLINE_DELTA = 1; // was: 3
static constexpr Int32 MAX_SCANLINE_DELTA = 5;
// varying VSYNC timing:
static constexpr Int32 MIN_VSYNC_CYCLES = 76 * 3 / 4;
static constexpr Int32 MAX_VSYNC_CYCLES = 76 * 3;
static constexpr Int32 MIN_VSYNC_DELTA_1 = 1;
static constexpr Int32 MAX_VSYNC_DELTA_1 = 76 / 3;
#ifdef VSYNC_LINE_JITTER
static constexpr Int32 MIN_VSYNC_DELTA_2 = 1;
static constexpr Int32 MAX_VSYNC_DELTA_2 = 10;
#endif
// threshold for jitter:
static constexpr Int32 MIN_UNSTABLE_FRAMES = 1; // was: 10
static constexpr Int32 MAX_UNSTABLE_FRAMES = 10;
// resulting jitter:
static constexpr Int32 MIN_JITTER_LINES = 1; // was: 50
static constexpr Int32 MAX_JITTER_LINES = 200;
static constexpr Int32 MIN_VSYNC_LINES = 1;
static constexpr Int32 MAX_VSYNC_LINES = 5;
private:
Random myRandom;
uInt32 myLastFrameScanlines{0};
Int32 myStableFrameFinalLines{-1};
uInt32 myStableFrames{0};
uInt32 myStabilizationCounter{0};
uInt32 myDestabilizationCounter{0};
Int32 myLastFrameScanlines{0};
Int32 myLastFrameVsyncCycles{0};
Int32 myUnstableCount{0};
Int32 myJitter{0};
Int32 myJitterFactor{0};
uInt32 myYStart{0};
enum Metrics: uInt32 {
framesForStableHeight = 2,
framesUntilDestabilization = 10,
minDeltaForJitter = 3,
maxJitter = 50
};
Int32 myJitterRecovery{0};
Int32 myYStart{0};
Int32 mySensitivity{MIN_SENSITIVITY};
Int32 myScanlineDelta{MAX_SCANLINE_DELTA};
Int32 myVsyncCycles{MIN_VSYNC_CYCLES};
Int32 myVsyncDelta1{MAX_VSYNC_DELTA_1};
#ifdef VSYNC_LINE_JITTER
Int32 myVsyncDelta2{MIN_VSYNC_DELTA_2};
#endif
Int32 myUnstableFrames{MAX_UNSTABLE_FRAMES};
Int32 myJitterLines{MIN_JITTER_LINES};
Int32 myVsyncLines{MIN_VSYNC_LINES};
private:
JitterEmulation(const JitterEmulation&) = delete;
JitterEmulation(JitterEmulation&&) = delete;
JitterEmulation& operator=(const JitterEmulation&) = delete;

View File

@ -33,6 +33,7 @@
#include "Font.hxx"
#include "Console.hxx"
#include "TIA.hxx"
#include "JitterEmulation.hxx"
#include "OSystem.hxx"
#include "EventHandler.hxx"
#include "StateManager.hxx"
@ -372,21 +373,33 @@ void DeveloperDialog::addVideoTab(const GUI::Font& font)
// TV jitter effect
myTVJitterWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1,
"Jitter/roll effect", kTVJitter);
ypos += lineHeight + VGAP;
myTVJitterWidget->setToolTip("Enable to emulate TV loss of sync.", Event::ToggleJitter);
wid.push_back(myTVJitterWidget);
myTVJitterSenseWidget = new SliderWidget(myTab, font,
myTVJitterWidget->getLeft() + CheckboxWidget::prefixSize(font), ypos - 1,
fontWidth * 9, lineHeight,
"Sensitivity ", 0, 0, fontWidth * 2);
myTVJitterSenseWidget->setMinValue(JitterEmulation::MIN_SENSITIVITY);
myTVJitterSenseWidget->setMaxValue(JitterEmulation::MAX_SENSITIVITY);
myTVJitterSenseWidget->setTickmarkIntervals(3);
myTVJitterSenseWidget->setToolTip("Define sensitivity to unstable frames.",
Event::JitterSenseDecrease, Event::JitterSenseIncrease);
wid.push_back(myTVJitterSenseWidget);
myTVJitterRecWidget = new SliderWidget(myTab, font,
myTVJitterWidget->getRight() + fontWidth * 3, ypos - 1,
"Recovery ", 0, kTVJitterChanged);
myTVJitterRecWidget->setMinValue(1); myTVJitterRecWidget->setMaxValue(20);
myTVJitterSenseWidget->getRight() + fontWidth * 2, ypos - 1,
fontWidth * 9, lineHeight,
"Recovery ", 0, 0, fontWidth * 2);
myTVJitterRecWidget->setMinValue(JitterEmulation::MIN_RECOVERY);
myTVJitterRecWidget->setMaxValue(JitterEmulation::MAX_RECOVERY);
myTVJitterRecWidget->setTickmarkIntervals(5);
myTVJitterRecWidget->setToolTip("Define speed of sync recovery.",
Event::JitterDecrease, Event::JitterIncrease);
Event::JitterRecDecrease, Event::JitterRecIncrease);
wid.push_back(myTVJitterRecWidget);
myTVJitterRecLabelWidget = new StaticTextWidget(myTab, font,
myTVJitterRecWidget->getRight() + 4,
myTVJitterRecWidget->getTop() + 2,
5 * fontWidth, fontHeight);
ypos += lineHeight + VGAP;
ypos += lineHeight + VGAP * 2;
myColorLossWidget = new CheckboxWidget(myTab, font, HBORDER + INDENT * 1, ypos + 1,
"PAL color-loss");
@ -728,6 +741,7 @@ void DeveloperDialog::getWidgetStates(SettingsSet set)
myColorLoss[set] = myColorLossWidget->getState();
// Jitter
myTVJitter[set] = myTVJitterWidget->getState();
myTVJitterSense[set] = myTVJitterSenseWidget->getValue();
myTVJitterRec[set] = myTVJitterRecWidget->getValue();
// States
@ -777,9 +791,10 @@ void DeveloperDialog::setWidgetStates(SettingsSet set)
myColorLossWidget->setState(myColorLoss[set]);
// Jitter
myTVJitterWidget->setState(myTVJitter[set]);
myTVJitterSenseWidget->setValue(myTVJitterSense[set]);
myTVJitterRecWidget->setValue(myTVJitterRec[set]);
handleTVJitterChange(myTVJitterWidget->getState());
handleTVJitterChange();
// States
myTimeMachineWidget->setState(myTimeMachine[set]);
@ -931,6 +946,9 @@ void DeveloperDialog::setDefaults()
case 2: // Video
// Jitter
myTVJitter[set] = true;
myTVJitterSense[set] = devSettings
? JitterEmulation::DEV_SENSITIVITY
: JitterEmulation::PLR_SENSITIVITY;
myTVJitterRec[set] = devSettings ? 2 : 10;
// PAL color-loss effect
myColorLoss[set] = devSettings ? true : false;
@ -997,11 +1015,7 @@ void DeveloperDialog::handleCommand(CommandSender* sender, int cmd, int data, in
break;
case kTVJitter:
handleTVJitterChange(myTVJitterWidget->getState());
break;
case kTVJitterChanged:
myTVJitterRecLabelWidget->setValue(myTVJitterRecWidget->getValue());
handleTVJitterChange();
break;
case kTimeMachine:
@ -1101,10 +1115,12 @@ void DeveloperDialog::handleSettings(bool devSettings)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DeveloperDialog::handleTVJitterChange(bool enable)
void DeveloperDialog::handleTVJitterChange()
{
bool enable = myTVJitterWidget->getState();
myTVJitterSenseWidget->setEnabled(enable);
myTVJitterRecWidget->setEnabled(enable);
myTVJitterRecLabelWidget->setEnabled(enable);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -60,7 +60,6 @@ class DeveloperDialog : public Dialog, DevSettingsHandler
kConsole = 'DVco',
kTIAType = 'DVtt',
kTVJitter = 'DVjt',
kTVJitterChanged = 'DVjr',
kTimeMachine = 'DTtm',
kSizeChanged = 'DTsz',
kUncompressedChanged = 'DTuc',
@ -125,6 +124,7 @@ class DeveloperDialog : public Dialog, DevSettingsHandler
CheckboxWidget* myTVJitterWidget{nullptr};
SliderWidget* myTVJitterRecWidget{nullptr};
StaticTextWidget* myTVJitterRecLabelWidget{nullptr};
SliderWidget* myTVJitterSenseWidget{nullptr};
CheckboxWidget* myColorLossWidget{nullptr};
CheckboxWidget* myDebugColorsWidget{nullptr};
std::array<PopUpWidget*, DEBUG_COLORS> myDbgColour{nullptr};
@ -160,7 +160,7 @@ class DeveloperDialog : public Dialog, DevSettingsHandler
void setWidgetStates(SettingsSet set);
void handleSettings(bool devSettings);
void handleTVJitterChange(bool enable);
void handleTVJitterChange();
void handleConsole();
void handleTia();