TBH I dont know what exactly do you mean by “interpolated”, I didnt know in that post too, but I will try to add a feature to only enable motion providers when there is a gap.
There is no way to export axis motion data at the moment, but ability to record to a file is not a bad idea.
Perhaps I am mistaken on what is occurring. I am simply referring to other Axes inferring movement based upon L0. If it’s randomized in movement; But not Speed it’s still a sort of interpolation imo.
If you link axes its basically a copy of the script.
If you add motion provider to an axis with a script you basically have two “tracks”, one is the script, the second one is the motion generated from motion provider. Then those two tracks are blended/mixes together based on the blend slider to produce the output value, which is I think what you call interpolation?
I call it “blending” since they are separate tracks, kinda like you blend two images together. It technically does use linear interpolation under the hood, but I would call it “interpolation” when interpolating between two points in the same data set, for example the interpolation of script points to make smooth curves with something like pchip/makima or interpolating between two pixels on an image.
But it doesn’t matter, just different words with similar meaning, I just couldn’t perfectly understand what you meant.
Hi. I use DeoVR with MFP v1.20.2.0 and a TCode device controlled directly from MFP. I noticed that the syncing is inaccurate, the device regularly responds too late or too early. I verified with a camera that the sync seems to oscillate between 300ms ahead and 300ms behind with a period of about 20 seconds.
I looked at wireshark data, comparing the packet time with the “current Time” field in the json api, the jitter is only ~30ms max, which doesn’t explain this problem.
With OpenFunScripter, the device follows the script movements accurately (also verified with camera). MFP + MPC-HC on the same computer also works fine. I tested with low and high serial refresh rates.
In DeoVRVideoSourceViewModel.cs the code attempts to read the propery “currentTime”, but the DeoVR API mixes between “current Time” and “currentTime”. Perhaps this is part of the problem?
So any other player works correctly, only DeoVR has this issue?
Are the “Axis Values” bars also out of sync?
Such time difference shouldnt really happen unless playback rate gets out of sync.
Never had the api return “current Time”, are you sure its not just wireshark text formatting?
If you switch log level to trace you should see Received VideoPositionMessage
messages every 1 second.
I did some more digging…
I setup a video camera with the following elements visible:
MPC-HC playing a video of a metronome.
DeoVR playing the same video.
MFP pointed at MPC-HC with the axis value clearly visible, interpolation Step
MFP pointed at DroVR with the axis value clearly visible, interpolation Step.
I created a simple funscript and recorded a video (60fps) of this setup. I carefully inspected the video writing down how many frames ahead/behind each thing was relative to MPC-HC, once per second.
The orange line shows that DeoVR plays the video at a slightly slower rate than MPC-HC. We can probably ignore this here.
The Grey line shows that MFP (deovr) axis value moves between -8 frames earlier to +15 frames later than expected (-130ms, +250ms).
The orange line shows that MFP (MPC-HC) axis value moves between -1 frames earlier and +8 frames later than expected (-20ms, +130ms).
Both DeoVR and MPC-HC show a very clear sine wave.
I also checked the logs, everything looks normal. If I compare the log timestamp with the VideoPositionMessage, DeoVR has a jitter <30ms with no noticeable pattern or drift, MPC-HC has a jitter of <60ms with no noticeable pattern but a small drift (0.06s over 60 seconds, probably 30/29.97fps crap).
I suspect that:
OFP does some sort of signal filtering that results in unstable behavior.
MPC-HC sends 5 position updates per second and DeoVR only 1 per second. As a result the unstable behavior is less bad with MPC-HC.
This code looks like a prime candidate for causing the observed behavior:
ScriptViewModel.cs
var error = float.IsFinite(CurrentPosition) ? newPosition - CurrentPosition : 0;
_playbackSpeedCorrection = MathUtils.Clamp(_playbackSpeedCorrection + error * 0.1f, 0.9f, 1.1f);
That sure looks unstable.
You’re right, my bad.
Thanks for a nice analysis!
I guess thats what happens when I only test with MPV…
Yup, basically a full PID will be needed.
Added this issue to github.
Actually knowing that each video player has different update rate PID will not work well.
So for now I dont have any good ideas on how to solve that properly.
The solution depends on the problem you’re trying to solve.
If you think the API is misreporting the playback speed, then calculate the playback speed based on timing information in the packets: (last_packet_position - new_position) / (last_packet_timestamp - new_packet_timestamp)
plus an exponential filter, this would eliminate any feedback loops. Did you observe this to be a problem? I know many players are off by 1000/1001 due to 30/29.97fps crap, but that really shouldn’t be noticeable.
If you think the packets have a large amount of jitter or the player stutters, then it’s a good idea to keep the playback speed at 1 and micro-adjust the position instead. For example:
float expectedPosition = currentPosition;
float error = newPosition - expectedPosition;
UpdateCurrentPosition(expectedPosition + error * .1);
This should work well if the packet jitter is below 100ms.
If that doesn’t work I have a few more ideas, but they take a bit more effort to implement.
There appears to be some timer creep in this code:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
float ElapsedSeconds() => stopwatch.ElapsedTicks / (float)Stopwatch.Frequency;
stopwatch.Start();
while (!token.IsCancellationRequested)
{
... update
stopwatch.Restart();
Thread.Sleep(IsPlaying || dirty ? 2 : 10);
}
I suggest this modification:
float elapsedSeconds = 0;
long lastUpdateTicks = 0;
stopwatch.Start();
while (!token.IsCancellationRequested)
{
long currentTicks = stopwatch.ElapsedTicks;
elapsedSeconds = (lastUpdateTicks - currentTicks) / (float)stopwatch.Frequency;
lastUpdateTicks = currentTicks;
... update
Thread.Sleep(IsPlaying || dirty ? 2 : 10);
}
Don’t reset the stopwatch. Beware that elapsedSeconds might be zero. Don’t divide by zero when calculating the speed
The playback speed and position from video players should be more or less fine, its just that there will be processing delays after the video player sends its current position which needs to be compensated.
IIRC I did try something like that but the smoothing was causing slow convergence with DeoVR/HereSphere since it only updates every second. But might have to try this again.
That will work as thats how it more or less was before but since the position is received from a different thread, if there is a negative error then the time can jump back causing micro stutters. The playback speed correction was a fix for that. Script time has to always move forward.
Actually stopwatch reset is pretty much free, i measured about 10ms difference over 60seconds for stopwatch always running vs stopwatch reset and accumulate elapsed ticks.
If we assume the playback speed is accurate then there is not much point in doing arithmetic with packet timestamps.
In that case, modify:
_playbackSpeedCorrection = MathUtils.Clamp(_playbackSpeedCorrection + error * 0.1f, 0.9f, 1.1f);
to:
_playbackSpeedCorrection = MathUtils.Clamp(1 + error * 0.1f, 0.9f, 1.1f);
I don’t think there is much point in accumulating the error over multiple packets if we’re just trying to correct for a little bit of network jitter. The accumulating part makes it difficult to get stable over a wide range of conditions. It’s okay if it takes a few seconds to converge as long as the algorithm is stable.
I was concerned with ticks getting lost between calls to ElapsedSeconds() and stopwatch.reset(). I’ve worked on some time-critical code and these small things really make it a lot harder to debug issues.
I did some additional tests. It appears that with MPC-HC, the initial VideoPositionMessage is quite jank, often being early by ~100ms. It appears that MPC-HC sends the VideoPositionMessage as soon as the user presses the play button, but the player itself needs ~100ms to actually start playing.
The same problem happens with deoVR, when the user presses the play button the API immediately sends the play signal, but the player itself buffers for ~1s.
This results in poor performance with my suggested code, my hypothesis that we’re just correcting for network jank isn’t correct. I’ll spend some time investigating further and report back with a patch that works well under these circumstances…
@GoonerScriptz added File
output target so you can save funscripts of motion generated by MFP, and added Fill gaps
option to motion providers
@galw86 changed offset sliders to numeric input so the offset range is now infinite
Core code had to change a bit to support the gap fills so hopefully I didnt break much. Please report any issues you encounter.
https://nightly.link/Yoooi0/MultiFunPlayer/actions/runs/2367864698
I’m trying to use controller to add shortcuts and only about half the buttons are able to be captured. Also, the majority of the buttons I’m able to capture are of a yellow controller which barely have any actions. Is this intended?
Im assuming you enabled controller buttons in the filters? Its the right most button where you capture gestures.
Also you are using xbox controller?
Yellow gestures are controler axes iirc, they can only be assigned actions that smoothly control some value, so mostly values controlled by sliders.
I unfortunetly dont have an xbox controller so its hard for me to test, I had to use an emulator.
This is awesome, it looks like the settings will do exactly what I’m looking for. I’ll have to make some custom mutli-axis scripts to test this later this week. Thanks a lot!
^ Hopefully this will bring more creators to script very specific motions within a scene that should only take a few minutes and we’ll have generally enjoyable blended motion for the rest of it
Also I was looking for the file output to save the motion generated by MFP and cannot locate it. Am I simply just not seeing it?
I want to preface this with the caveat that Multi-Axis scripting is new to me and the error may lie in something I’ve done on the script end.
I’ve edited a roll axis for one of my scripts (Original can be found here: https://discuss.eroscripts.com/t/lia-lor-let-me-swallow/59845?u=goonerscriptz)
Roll Axis:
Lia Lor - Let Me Swallow.roll.funscript (1.4 KB)
Scripted Motions are for: 9:48-9:50, 12:00-12:01, 12:13-12:14, 14:54-14:56
They don’t seem to play while the smart limiter is active on the R1 axis. I’m not sure what the smart limiter is limiting based on (Real Numbers; Not Conceptual). Perhaps this script has too intense of a roll for it’s depth and the smart limiter is booting it?
You have to add it:
Smart limit limits based on the stroke/L0 position, if stroke is up then R1/R2 will be limited to not move.
Im planning to redesign smart limit so its a bit more configurable.
Added smart limit configuration: nightly.link | Repository Yoooi0/MultiFunPlayer | Run #2417176945
Double click to add/remove points.
Dunno if it’s just a problem with XBVR, heresphere, or something else on my end, but I could not get MFP to match video files with periods (.) in the titles.
example:
“3760 - L.E.N.A…funscript” had issues.
I had to change the title in XBVR and reexport the scripts
“3760 - LENA.funscript” would work.
Or another example
“11453 - St. Patricks Awesome Foursome.funscript”
Again, I had to change the title and reexport.
“11453 - St Patricks Awesome Foursome.funscript”