11

我正在尝试通过打开和关闭“微脉冲”以编程方式生成 Android 振动模式,以控制最终用户对振动的感觉有多强烈。这是我在一些类似主题中看到的解决方案,针对 API 未提供用于控制振动强度的接口的问题(据我了解,因为硬件功能如何)。

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

我想做的是,给定一个介于 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]

编写这样的算法有什么帮助(或建议更好的策略来实现相同的目标)?

编辑:我已经添加了 100 声望的赏金 :)

4

3 回答 3

11

在研究了一段时间后,在数学上不是很有天赋的情况下,我想出了一个过于简化的算法(与我在 Dithermaster 指向那个方向后发现的一些 PWM 公式相比)。我首先做了几个假设,短脉冲宽度始终为 1,而长脉冲宽度为 1 和振动持续时间之间的整数。我还假设长脉冲宽度是振动强度的线性函数。特别是,后一种假设是不准确的。我猜这个函数应该更像是一个分贝计算(振动的“强度”类似于声音的“响度”)。

发布我的简化解决方案,以防它对最终来到这里的其他人有用。这对于我使用它的应用程序来说已经足够接近了,但我仍然想要更好的东西。如果有人发布替代答案,我会测试并接受它是否更好。

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;
}
于 2013-12-29T02:37:41.363 回答
7

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

  • 首先,k(i),周期数发生变化。它从 开始k(0) = 1,在 达到峰值k(0.5) = n/2,然后下降到k(1) = 1
  • 其次,r(i)每对中的开/关时间比率发生变化。如果我们有一个循环[a, b]a即有时间,b有时间,那么r(i)*a = b。以您的示例为例,我们有r(0) = 0, r(0.5) = 1,然后是一个渐近线r(1) = infinity

有很多函数可以匹配k(i)r(i),但让我们坚持使用简单的函数:

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

其中|x|表示 的绝对值x。我还替换11.000001inr的分母,这样我们就不必处​​理被零除的错误。

现在如果循环需要总和为n,那么任何一个循环的长度[a, b]都是n/k(i)。既然我们也有它r(i)*a = b,那么它遵循

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

为了形成强度数组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

这是伪劣代码:

    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);
    }

从这里做的最好的事情就是弄乱函数r(i)。但是,如果可以,请先将第一个和最后一个时间放宽为[n, 1]and ,这样[1, n]您就不必为渐近线烦恼了。

于 2014-01-02T23:38:29.640 回答
3

三个想法:

  1. 这是一种PWM。随着强度的增加,“关”变小,“开”变大。

  2. 这似乎是一种抖动形式,例如Ordered Dither。但不是 2D,而是 1D。

  3. 这看起来也有点像数字差分分析仪或Bresenham 的线算法

这些想法的一些组合应该可以解决问题。

于 2013-12-28T16:53:54.683 回答