Android中产生强度不等的振动模式的算法?

时间:2022-10-07 21:27:56

I am attempting to programmatically generate Android vibration patterns with 'micro pulses' on and off to control how strong the vibration feels to the end user. This is the solution I've seen recommended in a few similar topics, to the problem of the API not providing an interface for controlling the vibration strength (because of how the hardware functions, as I understand it).

我正在尝试用编程的方式生成带有“微脉冲”的Android振动模式,以控制对最终用户的振动强度。这是我在一些类似主题中看到的解决方案,针对API不提供控制振动强度的接口的问题(根据我的理解,这是因为硬件是如何工作的)。

The algorithm for generating these patterns, however, seems to only be hinted at, but no actual algorithm posted.

然而,生成这些模式的算法似乎只被提示,而没有实际的算法发布。

What I would like to do is, given an input intensity between 0.0f and 1.0f, generate an array following a pattern something like this:

我想做的是,在输入强度为0.0f到1.0f之间的情况下,根据以下模式生成一个数组:

(zero intensity)
[20,0]

[9,1,9,1]
...

[3,1,3,1,3,1,3,1,3,1]

[2,1,2,1,2,1,2,1,2,1,2,1,2]

(half intensity)
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]

[1,2,1,2,1,2,1,2,1,2,1,2,1,1]

[1,3,1,3,1,3,1,3,1,3]
...

[1,9,1,9]

(full intensity)
[0,20]

Any help with writing such an algorithm (or suggestions for a better strategy to meet the same goal)?

任何帮助编写这样的算法(或建议更好的策略以达到相同的目标)?

Edit: I've added a bounty of 100 reputation to the mix :)

编辑:我已经为这个组合增加了100个声望:)

3 个解决方案

#1


10  

After looking at the problem for a while, and not being not very mathematically talented, I came up with an overly simplified algorithm (compared to some of the PWM formulas I found after Dithermaster pointed me in that direction). A couple of assumptions I made were first that the short pulse width is always 1, and the long pulse width is an integer between 1 and the vibration duration. I also assumed the long pulse width is a linear function of the vibration strength. In particular, the latter assumption is not accurate. I'm guessing the function should be something more like a decibel calculation ("strength" of vibration is akin to "loudness" of a sound).

在看了这个问题一段时间后,我发现自己在数学上不是很有天赋,于是我想出了一个过于简化的算法(与我在迪瑟马斯特(Dithermaster)给我指明方向后找到的一些PWM公式相比)。我首先做了几个假设,短脉冲宽度总是1,长脉冲宽度是1和振动持续时间之间的整数。我还假设长脉冲宽度是振动强度的线性函数。特别是,后者的假设并不准确。我猜这个函数应该更像分贝计算(振动的“强度”类似于声音的“响度”)。

Posting my simplified solution in case it is useful for anyone else who ends up here. This is close enough for the application I am using it for, but I would still like something better. If anyone posts an alternative answer, I'll test and accept it if it is better.

张贴我的简化解决方案,以防它对任何其他最终来到这里的人有用。这对于我正在使用它的应用程序来说已经足够接近了,但是我还是想要更好的东西。如果有人发布另一个答案,我会测试并接受它如果它更好。

public long[] genVibratorPattern( float intensity, long duration )
{
    float dutyCycle = Math.abs( ( intensity * 2.0f ) - 1.0f );
    long hWidth = (long) ( dutyCycle * ( duration - 1 ) ) + 1;
    long lWidth = dutyCycle == 1.0f ? 0 : 1;

    int pulseCount = (int) ( 2.0f * ( (float) duration / (float) ( hWidth + lWidth ) ) );
    long[] pattern = new long[ pulseCount ];

    for( int i = 0; i < pulseCount; i++ )
    {
        pattern[i] = intensity < 0.5f ? ( i % 2 == 0 ? hWidth : lWidth ) : ( i % 2 == 0 ? lWidth : hWidth );
    }

    return pattern;
}

#2


7  

Suppose the total duration is n, rather than 20. Your function does two things as intensity i changes:

假设总持续时间是n,而不是20。当强度i发生变化时,你的函数可以做两件事:

  • First, k(i), the number of cycles changes. It starts off with k(0) = 1, peaks at k(0.5) = n/2, then drops to k(1) = 1.
  • 首先,k(i)循环的数量会改变。它从k(0) = 1开始,峰值为k(0.5) = n/2,然后下降到k(1) = 1。
  • Second, the ratio r(i) of time on/time off in each pair changes. If we have a cycle [a, b], with a being the time on and b the time off, then r(i)*a = b. Going by your example, we have r(0) = 0, r(0.5) = 1, then an asymptote up to r(1) = infinity
  • 第二,每对中间隔时间的比率r(i)发生变化。如果我们有一个循环[a, b],其中a为开时,b为关时,那么r(i)*a = b。根据你的例子,我们有r(0) = 0, r(0.5) = 1,然后是渐近到r(1) =∞

There are a lot of functions that could match k(i) and r(i), but let's stick with simple ones:

有很多函数可以匹配k(i)和r(i),但我们还是用简单的函数:

k(i) = (int) (n/2 - (n-2)*|i - 0.5|)             r(i) = 1 / (1.000001 - i) - 1

where |x| denotes the absolute value of x. I've also substituted 1 for 1.000001 in r's denominator so that we won't have to deal with divide-by-zero errors.

|x|表示x的绝对值,我还用1代替了r分母上的1。000001这样我们就不用处理二分零误差了。

Now if the cycles need to sum to n, then the length of any one cycle [a, b] is n/k(i). Since we also have that r(i)*a = b, it follows that

如果周期需要和n,那么任意一个周期的长度[a, b]是n/k(i)因为我们还有r(i)*a = b,所以它是这样的

a = n/(k*(1+r))                      b = r*a

and to form the array for intensity i, we just have to repeat [a, b] k times. Here's an example of the output for n = 20:

要形成强度为i的数组,我们只需重复[a, b] k次。下面是n = 20的输出示例:

Intensity: 0.00, Timings: 20.0, 0.0
Intensity: 0.05, Timings: 9.5, 0.5, 9.5, 0.5
Intensity: 0.10, Timings: 6.0, 0.7, 6.0, 0.7, 6.0, 0.7
Intensity: 0.15, Timings: 4.3, 0.7, 4.3, 0.7, 4.3, 0.7, 4.3, 0.7
Intensity: 0.20, Timings: 3.2, 0.8, 3.2, 0.8, 3.2, 0.8, 3.2, 0.8, 3.2, 0.8
Intensity: 0.25, Timings: 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8
Intensity: 0.30, Timings: 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9
Intensity: 0.35, Timings: 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9
Intensity: 0.40, Timings: 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9
Intensity: 0.45, Timings: 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9
Intensity: 0.50, Timings: 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
Intensity: 0.55, Timings: 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1
Intensity: 0.60, Timings: 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3
Intensity: 0.65, Timings: 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6
Intensity: 0.70, Timings: 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0
Intensity: 0.75, Timings: 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5
Intensity: 0.80, Timings: 0.8, 3.2, 0.8, 3.2, 0.8, 3.2, 0.8, 3.2, 0.8, 3.2
Intensity: 0.85, Timings: 0.8, 4.2, 0.8, 4.2, 0.8, 4.2, 0.8, 4.2
Intensity: 0.90, Timings: 0.7, 6.0, 0.7, 6.0, 0.7, 6.0
Intensity: 0.95, Timings: 0.5, 9.5, 0.5, 9.5
Intensity: 1.00, Timings: 0.0, 20.0

And here's the shoddy code:

这是劣质代码:

    public void Test()
    {
        foreach (var intensity in Enumerable.Range(0, 20 + 1).Select(i => i/20f))
        {
            var cycle = new List<float> {a(intensity), b(intensity)};
            var timings = Enumerable.Repeat(cycle, k(intensity)).SelectMany(timing => timing).ToArray();

            SDebug.WriteLine(
                String.Format("Intensity: {0,2:N2}, Timings: ", intensity) + 
                String.Join(", ", timings.Select(timing => String.Format("{0,2:N1}", timing))));
        }
    }

    private static float r(float i)
    {
        return 1f/(1.000001f - i) - 1f;
    }

    private static int k(float i)
    {
        return Mathf.CeilToInt(10 - 18*Mathf.Abs(i - 0.5f));
    }

    private static float a(float i)
    {
        return 20/(k(i)*(1 + r(i)));
    }

    private static float b(float i)
    {
        return r(i)*a(i);
    }

The best thing to do from here is mess with the function r(i). If you can though, first relax the first and last timings to be [n, 1] and [1, n], which'll save you from having to bother with asymptotes.

从这里做的最好的事情就是处理函数r(i)。如果可以的话,首先把第一次和最后一次的时间放松为[n, 1]和[1,n],这样就不用担心渐近线了。

#3


2  

Three thoughts:

三个想法:

  1. This is a kind of PWM. As intensity rises, "off" gets smaller and "on" gets larger.

    这是一种PWM。当强度增加时,“off”变小,“on”变大。

  2. This seems like a form of dithering, like an Ordered Dither. But instead of an 2D, it's just 1D.

    这似乎是一种优柔寡断的表现,就像一种有序的抖动。但不是2D,而是1D。

  3. This also seems something like a Digital Differential Analyzer or Bresenham's line algorithm.

    这也像是一个数字微分分析仪或布里森汉姆的直线算法。

Some combination of these ideas should solve the problem.

这些想法的某种结合应该能解决问题。

#1


10  

After looking at the problem for a while, and not being not very mathematically talented, I came up with an overly simplified algorithm (compared to some of the PWM formulas I found after Dithermaster pointed me in that direction). A couple of assumptions I made were first that the short pulse width is always 1, and the long pulse width is an integer between 1 and the vibration duration. I also assumed the long pulse width is a linear function of the vibration strength. In particular, the latter assumption is not accurate. I'm guessing the function should be something more like a decibel calculation ("strength" of vibration is akin to "loudness" of a sound).

在看了这个问题一段时间后,我发现自己在数学上不是很有天赋,于是我想出了一个过于简化的算法(与我在迪瑟马斯特(Dithermaster)给我指明方向后找到的一些PWM公式相比)。我首先做了几个假设,短脉冲宽度总是1,长脉冲宽度是1和振动持续时间之间的整数。我还假设长脉冲宽度是振动强度的线性函数。特别是,后者的假设并不准确。我猜这个函数应该更像分贝计算(振动的“强度”类似于声音的“响度”)。

Posting my simplified solution in case it is useful for anyone else who ends up here. This is close enough for the application I am using it for, but I would still like something better. If anyone posts an alternative answer, I'll test and accept it if it is better.

张贴我的简化解决方案,以防它对任何其他最终来到这里的人有用。这对于我正在使用它的应用程序来说已经足够接近了,但是我还是想要更好的东西。如果有人发布另一个答案,我会测试并接受它如果它更好。

public long[] genVibratorPattern( float intensity, long duration )
{
    float dutyCycle = Math.abs( ( intensity * 2.0f ) - 1.0f );
    long hWidth = (long) ( dutyCycle * ( duration - 1 ) ) + 1;
    long lWidth = dutyCycle == 1.0f ? 0 : 1;

    int pulseCount = (int) ( 2.0f * ( (float) duration / (float) ( hWidth + lWidth ) ) );
    long[] pattern = new long[ pulseCount ];

    for( int i = 0; i < pulseCount; i++ )
    {
        pattern[i] = intensity < 0.5f ? ( i % 2 == 0 ? hWidth : lWidth ) : ( i % 2 == 0 ? lWidth : hWidth );
    }

    return pattern;
}

#2


7  

Suppose the total duration is n, rather than 20. Your function does two things as intensity i changes:

假设总持续时间是n,而不是20。当强度i发生变化时,你的函数可以做两件事:

  • First, k(i), the number of cycles changes. It starts off with k(0) = 1, peaks at k(0.5) = n/2, then drops to k(1) = 1.
  • 首先,k(i)循环的数量会改变。它从k(0) = 1开始,峰值为k(0.5) = n/2,然后下降到k(1) = 1。
  • Second, the ratio r(i) of time on/time off in each pair changes. If we have a cycle [a, b], with a being the time on and b the time off, then r(i)*a = b. Going by your example, we have r(0) = 0, r(0.5) = 1, then an asymptote up to r(1) = infinity
  • 第二,每对中间隔时间的比率r(i)发生变化。如果我们有一个循环[a, b],其中a为开时,b为关时,那么r(i)*a = b。根据你的例子,我们有r(0) = 0, r(0.5) = 1,然后是渐近到r(1) =∞

There are a lot of functions that could match k(i) and r(i), but let's stick with simple ones:

有很多函数可以匹配k(i)和r(i),但我们还是用简单的函数:

k(i) = (int) (n/2 - (n-2)*|i - 0.5|)             r(i) = 1 / (1.000001 - i) - 1

where |x| denotes the absolute value of x. I've also substituted 1 for 1.000001 in r's denominator so that we won't have to deal with divide-by-zero errors.

|x|表示x的绝对值,我还用1代替了r分母上的1。000001这样我们就不用处理二分零误差了。

Now if the cycles need to sum to n, then the length of any one cycle [a, b] is n/k(i). Since we also have that r(i)*a = b, it follows that

如果周期需要和n,那么任意一个周期的长度[a, b]是n/k(i)因为我们还有r(i)*a = b,所以它是这样的

a = n/(k*(1+r))                      b = r*a

and to form the array for intensity i, we just have to repeat [a, b] k times. Here's an example of the output for n = 20:

要形成强度为i的数组,我们只需重复[a, b] k次。下面是n = 20的输出示例:

Intensity: 0.00, Timings: 20.0, 0.0
Intensity: 0.05, Timings: 9.5, 0.5, 9.5, 0.5
Intensity: 0.10, Timings: 6.0, 0.7, 6.0, 0.7, 6.0, 0.7
Intensity: 0.15, Timings: 4.3, 0.7, 4.3, 0.7, 4.3, 0.7, 4.3, 0.7
Intensity: 0.20, Timings: 3.2, 0.8, 3.2, 0.8, 3.2, 0.8, 3.2, 0.8, 3.2, 0.8
Intensity: 0.25, Timings: 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8
Intensity: 0.30, Timings: 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9
Intensity: 0.35, Timings: 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9
Intensity: 0.40, Timings: 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9
Intensity: 0.45, Timings: 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9
Intensity: 0.50, Timings: 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
Intensity: 0.55, Timings: 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1, 0.9, 1.1
Intensity: 0.60, Timings: 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3, 0.9, 1.3
Intensity: 0.65, Timings: 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6, 0.9, 1.6
Intensity: 0.70, Timings: 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0, 0.9, 2.0
Intensity: 0.75, Timings: 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5, 0.8, 2.5
Intensity: 0.80, Timings: 0.8, 3.2, 0.8, 3.2, 0.8, 3.2, 0.8, 3.2, 0.8, 3.2
Intensity: 0.85, Timings: 0.8, 4.2, 0.8, 4.2, 0.8, 4.2, 0.8, 4.2
Intensity: 0.90, Timings: 0.7, 6.0, 0.7, 6.0, 0.7, 6.0
Intensity: 0.95, Timings: 0.5, 9.5, 0.5, 9.5
Intensity: 1.00, Timings: 0.0, 20.0

And here's the shoddy code:

这是劣质代码:

    public void Test()
    {
        foreach (var intensity in Enumerable.Range(0, 20 + 1).Select(i => i/20f))
        {
            var cycle = new List<float> {a(intensity), b(intensity)};
            var timings = Enumerable.Repeat(cycle, k(intensity)).SelectMany(timing => timing).ToArray();

            SDebug.WriteLine(
                String.Format("Intensity: {0,2:N2}, Timings: ", intensity) + 
                String.Join(", ", timings.Select(timing => String.Format("{0,2:N1}", timing))));
        }
    }

    private static float r(float i)
    {
        return 1f/(1.000001f - i) - 1f;
    }

    private static int k(float i)
    {
        return Mathf.CeilToInt(10 - 18*Mathf.Abs(i - 0.5f));
    }

    private static float a(float i)
    {
        return 20/(k(i)*(1 + r(i)));
    }

    private static float b(float i)
    {
        return r(i)*a(i);
    }

The best thing to do from here is mess with the function r(i). If you can though, first relax the first and last timings to be [n, 1] and [1, n], which'll save you from having to bother with asymptotes.

从这里做的最好的事情就是处理函数r(i)。如果可以的话,首先把第一次和最后一次的时间放松为[n, 1]和[1,n],这样就不用担心渐近线了。

#3


2  

Three thoughts:

三个想法:

  1. This is a kind of PWM. As intensity rises, "off" gets smaller and "on" gets larger.

    这是一种PWM。当强度增加时,“off”变小,“on”变大。

  2. This seems like a form of dithering, like an Ordered Dither. But instead of an 2D, it's just 1D.

    这似乎是一种优柔寡断的表现,就像一种有序的抖动。但不是2D,而是1D。

  3. This also seems something like a Digital Differential Analyzer or Bresenham's line algorithm.

    这也像是一个数字微分分析仪或布里森汉姆的直线算法。

Some combination of these ideas should solve the problem.

这些想法的某种结合应该能解决问题。