2

I'm programming a sort of audio plugin, and I get an array of values that represent a step-based signal such as this:

enter image description here

that have these values:

[ 0.27, 0.43, 0.48, 0.51, 0.85, 0.15, 0.48, 0.01, 0.28, 0.84, 0.15, 0.22, 0.11, 0.86, 0.66, 0.92, 0.40, 0.71 ]

I'm looking for to transform those values into a bigger array of interpolated values that represent a smooth signal, such as a sine wave. Somethings like this (sorry for my Paint art):

enter image description here

What kind of math should I use here? Inside my development environment (Ruby based), I have a common number of math functions. But I don't know where to start.

sawa
  • 165,429
  • 45
  • 277
  • 381
markzzz
  • 47,390
  • 120
  • 299
  • 507
  • If your original data do not fit a sine wave (which is the case in general, and is the case with your example), then you cannot get a sine wave by interpolation. Note that interpolation just adds data. (To give you a more elementary example, if you have three points that are not on a single line, then you cannot make them be on a single line by adding more points.) – sawa Nov 22 '15 at 10:27
  • But I can smooth them! Making "custom" values between them and draw lines. – markzzz Nov 22 '15 at 11:07
  • @sawa Actually, a 'perfect' lowpass filter could reconstruct a sine wave from a square wave of the same frequency. Real-world ones can get arbitrarily close. – Nick Johnson Nov 22 '15 at 11:19
  • @NickJohnson That is not a sine wave, it is composition of multiple sine/cosine waves. – sawa Nov 22 '15 at 12:06
  • @sawa What 'that' are you talking about? A square wave consists of a sine wave at its fundamental, plus odd-order harmonics out to infinity. A 'perfect' lowpass filter would remove those fundamentals, leaving only the sine wave. – Nick Johnson Nov 22 '15 at 14:11
  • Would the close voters care to leave a comment as to why they're voting to close? This seems well defined and 100% on topic, to me. – Nick Johnson Nov 22 '15 at 17:05
  • @NickJohnson What OP has is not a square wave (the OP has discrete points). And there is no square wave (i.e., of a constant amplitude) that includes all those points. – sawa Nov 22 '15 at 18:20
  • @sawa The OP isn't asking for a single sine wave to fit his whole dataset. He wants to 'smooth it off', which is lowpass filtering. – Nick Johnson Nov 22 '15 at 18:46
  • @NickJohnson That depends on how you interpret "such as a sine wave", (which might be controversial, and I don't want to further argue). But what is clear is that the OP is not looking for a smooth function, they are looking for an "array of interpolated values that represent a smooth signal", (which does not make sense (i.e., is trivial) because if there is a smooth function that fits an interpolated array, that function would also fit the original array). – sawa Nov 22 '15 at 18:55
  • @sawa It's pretty clear what they want - they even drew a diagram of the desired output! – Nick Johnson Nov 22 '15 at 22:41
  • Yes please don't close this topic. It seems that @NickJohnson got it and can help me! – markzzz Nov 23 '15 at 10:13

1 Answers1

4

What you want here is a digital filter - specifically a lowpass filter.

There are two types of simple digital filter, Finite Impulse Response and Infinite Impulse Response.

A FIR filter works by summing, with some weighting, the previous n samples of the audio, and using that to generate the output sample. It's called "Finite Impulse Response", because a single impulse in the input can only affect a finite number of output samples.

An IIR filter, in contrast, uses its own previous output in addition to the current sample. It's called "Infinite Impulse Response" because of this feedback property; a single impulse can affect all future samples.

Of the two, the IIR filter is the simplest to implement, and in its most basic form looks like this:

state(N) = state(N - 1) * weighting + sample(N)
output(N) = state(N)

That is, for each input sample, reduce the previous state value by some amount and add the input, then use that as the output. As such, it's basically a moving average filter.

For example, if you set 'weighting' to 0.95, then each output sample is influenced 95% by previous samples and 5% by the current sample, and the output value will shift slowly in response to changing inputs. It will also be scaled up by 20X (1/(1-weighting)), so you should renormalize it accordingly.

Here's how the first few steps would work with your input data:

  1. Start by setting state = 20 * 0.27.
  2. Output state / 20 = 0.27
  3. Update state = state * 0.95 + 0.43 = 26.08
  4. Output state / 20 = 0.278.
  5. Update state = state * 0.95 + 0.48 = 5.76
  6. Output state / 20 = 0.288

And so forth. If you need more output data points than input data points, repeat your input samples n times before feeding into the filter, or interleave input samples with n zero samples. Both are valid, though they have different impacts on the filtered output.

There is a lot of theory behind digital filter design; in practice for a simple implementation you can probably use this first-order filter, and adjust the weighting value to suit.

Nick Johnson
  • 100,655
  • 16
  • 128
  • 198
  • Thanks for the reply! I got more or less the idea, but it is not really clear the steps I need to do for make this. For example, starting from state 0, I have 0.27. As for start: to know if I need to "incremenet" or not the value, I think I should know the value of the next step, right? Because if I have 0.43 is going "up", if it was 0.1 I need to go "down". In your example formula there is nothing about this. I guess N is the number of step (samples) I would use to describe the signal right? I.e. sample rate. Or can you give me an example of the first 2-3 steps with my data? Many thanks! – markzzz Nov 23 '15 at 10:12
  • @markazzz You don't need to know the next value - you just update the filter with the current value and output it. This does add some delay - phase shift - but it generally produces what you want. I'd suggest breaking open a spreadsheet and trying it out in there to see what it produces. Reading the Wikipedia pages on IIR filters and digital filtering will also help. – Nick Johnson Nov 23 '15 at 10:19
  • Yes. But what's the difference between "state" function and "sample" function? You wrote state(n - 1) and sample(n). Typo? – markzzz Nov 23 '15 at 10:33
  • Here for example, I don't get the point of this: https://docs.google.com/spreadsheets/d/1-_CXV1ywcxelIWv6m-L59gsVRMpJgkh2CGDwxzVEl4M/edit#gid=0 because after the first iteration I've a value that is greater than the second step value (0.43). Should I split value between 0.27 and 0.43? Why second value is greater? – markzzz Nov 23 '15 at 11:34
  • @markzzz 'state(N)' is the value of the state variable at time step N. 'sample(N)' is the Nth input sample. – Nick Johnson Nov 23 '15 at 13:41
  • I've updated my post with an illustration of how to execute the first few steps of the filter. – Nick Johnson Nov 23 '15 at 13:44
  • Ok, but this is what I don't get. At the 3° point, you already use 0.43 as value, which is my second step in the graph. If you see, every "step" is a value in the array I've suggested (18 steps in the graph, 18 values in the array). This means that between first and second step (0.27, 0.43) there must be lots of values that will define the smoothed curve. Where do I get those values? I mean, I would need to find (for example) 20 smoothed values between 0.27 and 0.43, so I can get the first smooth/sine-based curve between my 2 step-points. Maybe you misunderstand my original question? – markzzz Nov 23 '15 at 15:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/95949/discussion-between-markzzz-and-nick-johnson). – markzzz Nov 23 '15 at 15:46
  • @markzzz As I said in the answer, if you need more values, interpolate by either repeating values, or interspersing them with 0 values, before applying the filter. Play around and see what works best for you. – Nick Johnson Nov 23 '15 at 17:15
  • But that's the problem: if I repeat the values (i.e. between 0.27 and 0.43 I split 10 values, such as 0.27 0.27... 0.27 (10 times) how can I know if the ramp is going up or down? if the second value is 0.10 instead of 0.43, I need to go "down", not up. And those repeated values doesn't take care about the "next" value. That's the problem :( – markzzz Nov 25 '15 at 09:28
  • @markzzz You don't have to know - just apply the filter I described, and you'll get the required output, albeit slightly phase shifted. Give it a try! – Nick Johnson Nov 25 '15 at 10:34
  • Really man, I can't follow you :( Here I've made a testing project http://jsfiddle.net/z81sqzs6/ that makes no-sense values. I've split the first 2 step-based signal values into 10 repeated values (0.27 ten times, 0.43 again ten times) but the result it is not an "interpolated sine wave" that start from 0.27 and move around 0.43 in a smooth way. They seems just "progressive" values. Where am I wrong? Damn why I can't get your point... – markzzz Nov 25 '15 at 14:47
  • @markzzz Your weighting is very high - each new value only perturbs the state by 5% - so you're doing smoothing over a very longer period. Plot the new values against the raw data and you'll see this. Try playing with different weighting factors (and the corresponding 1/(1-n) divider). – Nick Johnson Nov 25 '15 at 15:06
  • It changes weight, but not the curve. I should got values around the red line I've drawn in the example. Instead I'm far away from that. Also, as I repeat, it doesn't take care about the ramp of the next point. 0.43 is considerate only when I'm in the "new" step zone, which should be near 0.43 if up, down if lower value. Really, maybe I can't express my dubts because is so easy see that the results I need is different from what you suggest :( – markzzz Nov 25 '15 at 15:25
  • @markzzz It does change the curve; consider the trivial example of 0% - your output signal would be the exact same as your input. And again, you will get some phase shift - because the filter doesn't look ahead. You can shift the signal a bit to compensate, if you want. – Nick Johnson Nov 25 '15 at 15:26
  • You don't get my point I guess. Consider this 3 step base point: 0.27, 0.43 and 0.15. I need a start around 0.27, an increment to 0.43, than a descending ramp going to 0.15. If I split those values into 10 repetead values, this is the result having weight 1: http://jsfiddle.net/z81sqzs6/1/ its all time increasing, I've not the falling ramp between 0.43 and 0.15. THIS IS the problem I don't get, do you see? Last values are around 0.60-070 :( – markzzz Nov 25 '15 at 15:41
  • That's not a proper filter. Your 'scale' always needs to be `1/(1-weight)`, and weight=1 would mean "ignore all incoming data and don't ever change anything". I really can't provide any more detailed support; please, please just read the Wikipedia pages on digital filtering and IIR filters. I assure you they're what you're looking for. – Nick Johnson Nov 25 '15 at 16:13
  • weight=1 was an example to see the data more specific man :) I think your example works, but I don't know how to fit the future data in order to make the oscillating slope. – markzzz Nov 25 '15 at 16:17