About the "Scale" Slider in Multifunplayer

How it works


The “Scale” slider in MultiFunPlayer scales the script towards / away from the middle position (position 50). It does not take the middle position of your strokes into account.

Essentially, the scale slider does this:
50ezgif-6-96110e2534

Rather than this.
0
The latter can be achieved by adjusting the maximum range of the output range sliders. Which is the recommended way of adjusting stroke length.

The current consensus is for scripters to exaggerate the stroke length by a little, and users can reduce them by adjusting the output sliders if desired.

This is how things work as of now (v1.30). Though MFP is being actively improved by Yoooi, so things are subject to change.


Why “Scale” Doesn’t Work for Stroke Length Adjustments

I’m seeing a peculiar advice given by one scripter that users should adjust the “scale” slider in MultiFunPlayer in accordance to their penis length.

I get that they are doing this for safety concern, and it’s a good thing to feel responsible for the scripts you are selling. However, this over-complicated usage guide that emphasizes the “scale” slider simply doesn’t meet its intended effect.

As an example, here is one of their script.

This is what happens when the scale is set to 50%.


If a user experienced slippage at peaks, this does not change that. Which is unless the user adjust the position of their OSR2 to make it lower.

This is what happens when a user sets a 130% scale:


It does not extend the stroke length because the strokes are ranged between 0-50. The actions will be shifted downwards, getting clipped off the floor and ends up fast and erratic. Actions close to 0 will be made non-existence.


In a nutshell, use the “output range” L0 slider to adjust the stroke range according your preferences. Don’t bother adjusting the scale for L0 script, for now.

10 Likes

and what about scriptplayer does the range extender bar changes the script ? movement ?

The scale was made mainly for L1/L2/R0/R1/R2 multi axis scripts since they will always hover around 50%.
If the scripter assumes large physical range, like +/- 45 degrees for pitch/roll while the device can only do like +/- 20, the script will be very flat and the scale can help boost those type of scripts.

It does not really work that well for stroke scripts, unless they are centered around 50% which would be rare.
Scale definitely should not be used for adjusting range, use output range sliders for that.

I already have a rework of scaling planned so there will be more usable scaling methods for stroke axis in the future.
If you have suggestions on how the scaling should work please let me know.

3 Likes

Only mainly use it for vibrations. I put points on a straight line and move them slightly up and down and then use this to create a "zig zag pattern of my choosing.

Like this

image

image

But I honestly have never found another use for it

Plus it doesn’t even work for vibrations on a stroke either :confused:

I guess it’s mor eof a tool if you want to change the range of the entire script in one go but, still.

I’ve been thinking of a uniform scaling for a while now.

For me, the best scaling to maintain the “original intent” of the scripter would be to change the distance traveled by a percentage (i.e. 80% scaling would mean that a distance of 10 would be changed to 8 and a distance of 70 would be changed to 56), while trying to stay near ‘the center of the original wave’ (i.e. if the original script was moving near the top, the transformed script should also be near that position).

Something like the red line here:

I came up with this (with some help of Claude 3.5 Sonnet):

using System;
using System.Collections.Generic;
using System.Linq;

namespace FunscriptToolbox.Core
{
    public static class FunscriptAdjuster
    {
        public static FunscriptAction[] AdjustFunscript(ICollection<FunscriptAction> originalActions, double percentage)
        {
            var draft = AdjustFunscriptDraft(originalActions, percentage, 2);
            return OptimizeFunscriptActions(originalActions, draft, percentage, 100, 5);
        }

        public static FunscriptAction[] AdjustFunscriptDraft(ICollection<FunscriptAction> originalActions, double percentage, int windowSize = 2)
        {
            if (originalActions.Count < 2)
            {
                return originalActions.ToArray();
            }

            var adjustedActions = new List<FunscriptAction>();
            var movingAverage = CalculateMovingAverage(originalActions, windowSize);

            int i = 0;
            foreach (var original in originalActions)
            {
                var average = movingAverage[i];

                // Calculate relative position (0 to 1) of the original action compared to the moving average
                double relativePos = (original.Pos - average) / 100.0;

                // Adjust the relative position based on the percentage
                double adjustedRelativePos = relativePos * (percentage / 100.0);

                // Calculate new position, ensuring it stays within 0-100 range
                int newPos = (int)Math.Round(average + (adjustedRelativePos * 100));
                newPos = Math.Max(0, Math.Min(100, newPos));

                adjustedActions.Add(new FunscriptAction { At = original.At, Pos = newPos });
                i++;
            }

            // Post-processing to preserve extreme points and overall shape
            PreserveExtremePoints(adjustedActions, originalActions.ToList());

            return adjustedActions.ToArray();
        }

        private static List<double> CalculateMovingAverage(ICollection<FunscriptAction> actions, int windowSize)
        {
            var positions = actions.Select(a => (double)a.Pos).ToList();
            var movingAverage = new List<double>();

            for (int i = 0; i < positions.Count; i++)
            {
                int start = Math.Max(0, i - windowSize / 2);
                int end = Math.Min(positions.Count, i + windowSize / 2);
                double average = positions.GetRange(start, end - start).Average();
                movingAverage.Add(average);
            }

            return movingAverage;
        }

        private static void PreserveExtremePoints(List<FunscriptAction> adjustedActions, List<FunscriptAction> originalActions)
        {
            for (int i = 1; i < adjustedActions.Count - 1; i++)
            {
                var prev = adjustedActions[i - 1];
                var current = adjustedActions[i];
                var next = adjustedActions[i + 1];
                var original = originalActions[i];

                // Preserve local maxima
                if (original.Pos > originalActions[i - 1].Pos && original.Pos > originalActions[i + 1].Pos)
                {
                    current.Pos = Math.Max(current.Pos, Math.Max(prev.Pos, next.Pos));
                }
                // Preserve local minima
                else if (original.Pos < originalActions[i - 1].Pos && original.Pos < originalActions[i + 1].Pos)
                {
                    current.Pos = Math.Min(current.Pos, Math.Min(prev.Pos, next.Pos));
                }
            }
        }

        public static int[] CalculateDistanceErrors(ICollection<FunscriptAction> originalActions,
                                                    ICollection<FunscriptAction> adjustedActions,
                                                    double percentage)
        {
            if (originalActions.Count != adjustedActions.Count)
                throw new ArgumentException("Original and adjusted actions must have the same count.");

            var errors = new List<int>();
            var originalList = originalActions.ToList();
            var adjustedList = adjustedActions.ToList();

            for (int i = 1; i < originalActions.Count; i++)
            {
                double originalDistance = Math.Abs(originalList[i].Pos - originalList[i - 1].Pos);
                double expectedDistance = Math.Min(100, originalDistance * (percentage / 100.0));  // Cap the expected distance at 100
                double actualDistance = Math.Abs(adjustedList[i].Pos - adjustedList[i - 1].Pos);
                double error = actualDistance - expectedDistance;
                errors.Add((int)error);
            }

            return errors.ToArray();
        }

        public static FunscriptAction[] OptimizeFunscriptActions(
            ICollection<FunscriptAction> originalActions,
            ICollection<FunscriptAction> adjustedActions,
            double percentage,
            int maxIterations = 1000,
            int windowSize = 5)
        {
            List<FunscriptAction> optimizedActions = new List<FunscriptAction>(adjustedActions);            
            for (var iteration = 0; iteration < maxIterations; iteration++)
            {
                bool improved = false;

                for (int i = windowSize; i < optimizedActions.Count - windowSize; i++)
                {
                    double currentWindowError = CalculateWindowError(originalActions.ToList(), optimizedActions, percentage, i - windowSize, i + windowSize);

                    // Try nudging the point up and down
                    for (int direction = -1; direction <= 1; direction += 2)
                    {
                        int newPos = Clamp(optimizedActions[i].Pos + direction, 0, 100);

                        List<FunscriptAction> tempActions = new List<FunscriptAction>(optimizedActions);
                        tempActions[i] = new FunscriptAction { At = optimizedActions[i].At, Pos = newPos };

                        double newWindowError = CalculateWindowError(originalActions.ToList(), tempActions, percentage, i - windowSize, i + windowSize);

                        if (newWindowError < currentWindowError)
                        {
                            optimizedActions[i] = tempActions[i];
                            improved = true;
                            break;  // Break the inner loop if improvement is found
                        }
                    }
                }

                if (!improved)
                {
                    // If no improvement was made in this iteration, we can stop
                    break;
                }
            }
            return optimizedActions.ToArray();
        }

        private static double CalculateWindowError(
            List<FunscriptAction> originalActions,
            List<FunscriptAction> adjustedActions,
            double percentage,
            int startIndex,
            int endIndex)
        {
            double totalError = 0;

            for (int i = startIndex; i < endIndex; i++)
            {
                double originalDistance = Math.Abs(originalActions[i + 1].Pos - originalActions[i].Pos);
                double expectedDistance = Math.Min(100, originalDistance * (percentage / 100.0));
                double adjustedDistance = Math.Abs(adjustedActions[i + 1].Pos - adjustedActions[i].Pos);
                totalError += Math.Abs(adjustedDistance - expectedDistance);
            }

            return totalError;
        }

        // Custom Clamp method
        private static int Clamp(int value, int min, int max)
        {
            if (value < min) return min;
            if (value > max) return max;
            return value;
        }
}

Here an example of a script scaled to 50%, 80%, 100% (baseline), 120%, 150%, 200%:

And the same script during the more intense actions:

EveSweet-FindingTheSweetSpot.50.funscript (963.5 KB)
EveSweet-FindingTheSweetSpot.80.funscript (962.6 KB)
EveSweet-FindingTheSweetSpot.100.funscript (962.3 KB)
EveSweet-FindingTheSweetSpot.120.funscript (962.8 KB)
EveSweet-FindingTheSweetSpot.150.funscript (963.5 KB)
EveSweet-FindingTheSweetSpot.200.funscript (963.6 KB)

It’s far from perfect but its a starts.

For example, with a percentage of 50%, it seems to be limited to positions 15 to 85 instead of the full range (but it’s still better than 50% of the full range). I can also see that it can inverse the direction of some moves:
image

3 Likes

I’ve always followed this rule, use output range for L0, scale for the rest as necessary.
If MFP were to be changed, it would probably be a good idea to make that “rule” more apparent in the UI. These two functions are similar in purpose, but are separated.
The way things are organized is fairly modular at the moment, and it accommodates custom devices well. But the vast majority of multi-axis users are using SR6/OSR2, it might make sense to change the UI to more directly cater to this use. Other multi-axis strokers follow the same principles (because they have to in order to use these scripts). Users of other devices will be making their own custom device config anyway.

It’s also important for end users to keep in mind what the scripting style was. Exact position frame by frame scripting does not scale the same way as a beat scripted PMV. But that’s on the end user, not MFP.

Thanks falafel for the careful research

  1. The scale and output effects described by falafel are correct.
  2. When 50%, your screenshot is exactly the effect I want because I require that the initial insertion is also 50% of the maximum length of the penis. This needs to be achieved by adjusting the height of the OSR or adjusting the height of the chair. As for why it is 50%, it is because OSR designed the initial position to be 50. In fact, it is also possible to design it to 0.Tempest should have its own considerations
  3. A ratio of 130% is not allowed and can only be 100% at most.
  4. The purpose of my setting up this way is to map the actions to the same proportions of penises of different lengths to maintain a consistent experience with the video.
  5. The advantage is that any script settings are unified and do not need to be changed. The disadvantage is that users who exceed the maximum stroke of the device may not have a good experience. It is also required that every high and low point when making the script must be consistent with the video, and the deviation is within ±5
  6. Another disadvantage is that if the penis is not long enough, if the action is 0-20, it may be scaled too small.
  7. In oral sex, this setting can make the movements very precise and stimulate the same proportion of the penis of different lengths.
  8. When I test all my scripts on different devices OSR2+ (maximum stroke 136mm) and SR6 (maximum stroke 120mm), I don’t need to make any settings based on different scripts because the scale or output is determined based on the length of my penis.
    Of course, the scale values ​​​​of the two devices are different. One is 100mm/136mm=73% and the other is 100mm/120mm=83%


Assume that the rectangular paralleleAssume that the cuboid I drew is the user’s penis, so it is stimulated to the position of 0%-50%. At this time, assuming it is SR6 and the maximum stroke is 120mm, then the length of the user’s penis at this time is 120mm x 0.5 = 60mm.

  1. In my opinion, scale is a smarter method than output. It may only need to be optimized. Points that can be optimized at present:
    1.1 Scaling causes movement less than 10 to be difficult to perceive
    1.2. Scales exceeding 100% require special care or should not be allowed to exceed 100%
  2. For end users, it is best for them to have unified standards for settings without having to modify settings frequently. If I need to adjust the scale or output to switch a video, it will definitely be too troublesome.
  3. According to my understanding, 0-100 of funscript describes a percentage, so it is important to set these points accurately.
  4. At present, many script creators do not carefully check whether the high and low points of the script are correct. It takes a lot of time to check whether each high and low point is correct, especially in real-life oral sex.

Okay, I understand your thought process better now. Looks like I haven’t been able to understand your diagram. So:

  • You are not suggesting to extend the scale over 100%.

  • You want the user to adjust their initial insertion depth (by adjusting their seat and device) according to their penis - stroke length ratio.

That said, I’m still not in favour of this approach. Partially because I don’t think it is intrinsically easier for the user to adjust the position of their seat and device.

The points you’ve made in support of using “scale”, as well as your pursuit for accuracy can be also achieved by using the “output range” slider. The only difference here is that with “output range” the user has more flexibility to control the top and bottom value. Whereas with “scale” they don’t, and would have to adjust themselves to accommodate a fixed value.

Yes, although it may be a little troublesome at first, but once it is fixed, you don’t need to change it anymore.Besides, I can’t think of a better way to unify devices with different travel ranges. The current method can unify OSR2+ and SR6. I have tested it many times.

My diagram may not be good enough. I am planning to write a step-by-step diagram.

I’ve made my method public in this free script, you can take a look at it. To make different devices have consistent performance, the point setting is very demanding.
This is also the reason why my efficiency is so low. Although this requires a lot of time for the creator, it does follow the funscript standard that the points represent the percentage of insertion.

https://discuss.eroscripts.com/t/juicyneko-morning-cowgirl-tifa-aerith-multi-axis/174460/12

You can test my script on different devices according to my settings. Thank you for your question. You are very good at thinking and asking questions. :clap:

To set the output range, you need to set both. The effect is the same as setting the scale. If you don’t set both sides when setting the output, then my precise script may be biased (in fact, the key factor here is to keep the 50 position in the middle.)

I think we both were missing each other’s point but appreciated the input :saluting_face:
This really isn’t going anywhere.

I for one am still confused af.

A scale of 60% is exactly the same as setting the output range to be 20% → 80% but with scale you are not able to fine adjust the middle point, for example to 10% → 70%.

With range sliders you still set the range size once but you dont adjust the chair height, you adjust the middle point of the range to match the chair height. You are also able to only adjust the top or bottom position, while with scale you adjust both at the same time.

@codeScripter Your calculations might be fine but instead of setting the scale to 73% I would advice users to set the range to 0% - 73% and then adjust the middle position to match their chair height.

scale an ouput

scale = 73%
ouput = 13% - 87%

Falafel’s picture can express this intuitively.

According to my current test situation, the scale setting in your MFP has the same effect as the output setting on both sides.

After setting up one side and matching the height, I think I should test it again.

I thought about it for a moment and set the output to 0-73% and 13% - 87%. Maybe it’s just a difference in OSR highs and lows. I think I’ll have to actually test the differences in the scripts, especially the oral sex scripts.
What is the initial insertion percentage of the sexual organ after you ask your users to set 0-73%? Is it also 50%?

If the user is initially allowed to insert 50% of the maximum length of the penis, then the scale design is correct, so that the 50 up and down movements are closer to 50.

Yes, but with scale you cant move the center point from device 50%.

Why do you want everything to be centered around 50%.

With 0-73% range, funscript 0% is mapped to device 0%, funscript 50% is mapped to device 36.5%, funscript 100% is mapped to device 73%.
With 20-93% range, funscript 0% is mapped to device 20%, funscript 50% is mapped to device 56.5%, funscript 100% is mapped to device 93%.

The script type does not matter. In both cases the movement is the same, its just the center is moved up so you dont have to adjust your chair height.

So, we only need to keep output max - output min = 73%. Then we don’t even need to adjust the OSR or chair height. However, if the setting of 0-73% is still too high and can’t keep the insertion at 50%, we may only be able to adjust the OSR height or chair height

Adjusting the OSR height or chair height is usually a one-time thing and will not change after adjustment. Essentially this is a 50% initial alignment based on the user’s sexual organs.

I’ll test the setting of 0-73% to see.

As for the advantage of scale over output, I think it is that you can handle edge cases. For example, if the action is scaled too small, we can keep it at 20.Movements that are too small will not be felt.The length of the sex organ varies greatly, with the smallest being 60 mm and the largest being 200 mm.

I’m confused af. I’ve never had to do these calculations to ensure my member stay in place. Nor do I keep a ruler on me to measure how much percentage of myself is inside (that’s kinky).

Just play with the “output range” sliders like this and set a range that’s suitable for you and your physical set-up.
MultiFunPlayer_WboEaK3VJc

In case you’ve missed this - it’s possible to default the OSR to 0% by setting the “target value” in “auto-home”, or the “default value” in the “devices” menu. And this is what I recommend - start with a full insertion and adjust the maximum output accordingly (like with the Handy).

It seems that i have complicated simple things. :rofl:

  1. First, let the user match the lowest and highest points of the OSR with the user’s sexual organs according to Falafel’s method, so that the middle position is exactly 50%
  2. If the high and low points do not match, the OSR or the seat needs to be adjusted appropriately

There is literally no advantage to scale because its the same as range sliders but without the freedom of moving the center of the range.

Red = device physical range
Green = output range
Black = script
Green dash line is script/output range 50%, or your insertion point

1st is range set to 0%-100%.

2nd is range set to 13%-87% and is exactly the same as scale set to 73%

3rd is range set to 0%-73%, the script is still exactly the same as 2nd, but you basically move the whole script within available physical device range. You cant do that with scale, instead you have to physically move the device or adjust chair height.

4 Likes

Very clear explanation. I think I understand it now.