11

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).

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:

(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 :)

paulscode
  • 1,049
  • 1
  • 12
  • 29
  • Is each of those lines meant to be 1/20th of the intensity (eg, [9,1,9,1] = 0.05), or are the values related to the intensity of the pulses along the curve you've presented (eg, [9,1,9,1] = 0.25)? – Brian S Dec 27 '13 at 22:34
  • @BrianS The values represent number of milliseconds for an 'off' or 'on' state of the vibrator. First element in array is "off" state, second "on", and so-on. In my example, the total milliseconds I used was 20 (i.e. how long the total vibration should last). – paulscode Dec 27 '13 at 23:02
  • @BrianS Sorry, I misunderstood your question the first time. The patterns I listed are just random examples of output, not necessarily evenly spaced. Function should be linear, with the end points and mid point being the ones I labeled in the question. – paulscode Dec 28 '13 at 06:25

3 Answers3

11

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).

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;
}
paulscode
  • 1,049
  • 1
  • 12
  • 29
  • Forgot to mention, that parameter 'intensity' is a float value between 0.0 and 1.0, representing the desired strength of vibration. Parameter 'duration' is the number of milliseconds the vibration should last. – paulscode Dec 31 '13 at 15:08
7

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

  • 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.
  • 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

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

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.

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

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:

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.

Andy Jones
  • 4,723
  • 2
  • 19
  • 24
  • Thanks, I like this approach. It will take a little tweaking to make the duration variable rather than hard-coded to 20, and to then translate the result into a pattern array (consisting of longs instead of floats). I'll tinker with it a bit and report back. – paulscode Jan 03 '14 at 00:28
  • 1
    Adapted the equations to use `n` rather than `20`. If you want the durations to be integers though, any algorithm would likely make use of factorizations of `n` and the numbers close to it. Wouldn't be a simple thing. – Andy Jones Jan 03 '14 at 00:42
  • My thought is to write a function that takes an array of floats, plots integer-length line segments over that, and calculates the total time on and off for each segment. When the sums for on and off are greater than one, insert them into the integer array, and hold onto the remainder to include in the next calculation. Another approach would be to iterate the float array calculating a target time for each element, find the difference between that and the accumulated time, round the difference to an integer and add it to the accumulated time. I'll try both to see if either has better results. – paulscode Jan 03 '14 at 01:06
  • I finished plugging this into my code, and it works well. I particularly like the fact that I can tweak r() and k() to change the behavior (this flexibility will help me find a solution where the vibration strength transitions more evenly). If there are no better solutions posted before the deadline, you have earned the 100 reputation bounty. If a better solution comes along, I'll put in a second bounty to award to you as well for taking the time to provide an excellent solution :) Thanks again! – paulscode Jan 03 '14 at 14:15
  • Glad it worked out :) Don't worry about any second bounty if a better solution comes along though, rep's just a number. When you're finished tweaking though, would you mind posting the code? Would like to see how well it carries over to integers. – Andy Jones Jan 03 '14 at 15:39
  • 1
    Will do. Once I have the final solution(s) I'll update the OP. – paulscode Jan 03 '14 at 15:41
  • I too am interested in seasoned code - someone have this more refined? – RoundSparrow hilltx Dec 15 '14 at 10:05
3

Three thoughts:

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

  2. This seems like a form of dithering, like an Ordered Dither. But instead of an 2D, it's just 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.

Dithermaster
  • 6,223
  • 1
  • 12
  • 20
  • Thanks-- it is definitely a PWM strategy I am looking for, so being pointed to that subject should help me with writing an algorithm myself. The dithering/ rounding part I understand, so translating the numbers for the pattern array should be the easy part once I know how to calculate the target pulse widths. – paulscode Dec 28 '13 at 20:14