Doing audio processing (though it could just as well be image processing) I have a one-dimensional array of numbers. (They happen to be 16-bit signed integers representing audio samples, this question could apply to floats or integers of different sizes equally.)
In order to match audio with different frequencies (e.g. blend a 44.1kHz sample with a 22kHz sample), I need to either stretch or squash the array of values to meet a specific length.
Halving the array is simple: drop every other sample.
[231, 8143, 16341, 2000, -9352, ...] => [231, 16341, -9352, ...]
Doubling the array width is slightly less simple: double each entry in place (or optionally perform some interpolation between neighboring 'real' samples).
[231, 8143, 16341, 2000, -9352, ...] => [231, 4187, 8143, 12242, 16341, ...]
What I want is an efficient, simple algorithm that handles any scaling factor, and (ideally) optionally supports performing interpolation of one kind or another in the process.
My use case happens to be using Ruby arrays, but I'll happily take answers in most any language or pseudo-code.
This is something I threw together in a few minutes just as I was leaving work, then recreated after a glass of wine after dinner:
sample = [231, 8143, 16341, 2000, -9352]
new_sample = []
sample.zip([] * sample.size).each_cons(2) do |a,b|
a[1] = (a[0] + b[0]).to_f / 2 # <-- simple average could be replaced with something smarter
new_sample << a
end
new_sample.flatten!
new_sample[-1] = new_sample[-2]
new_sample # => [231, 4187.0, 8143, 12242.0, 16341, 9170.5, 2000, 2000]
I think it's a start but obviously not finished since the -9352 didn't propagate into the final array. I didn't bother converting floats to ints; I figure you know how to do that. :-)
I'd like to find a better way to iterate over each_cons. I'd rather use a map than each* but this works OK.
Here's what the loop iterates over:
asdf = sample.zip([] * sample.size).each_cons(2).to_a
asdf # => [[[231, nil], [8143, nil]], [[8143, nil], [16341, nil]], [[16341, nil], [2000, nil]], [[2000, nil], [-9352, nil]]]
each_cons is nice because it steps through the array returning slices of it, which seemed like a useful way to build up the averages.
[0,1,2,3].each_cons(2).to_a # => [[0, 1], [1, 2], [2, 3]]
EDIT:
I like this better:
sample = [231, 8143, 16341, 2000, -9352]
samples = sample.zip([] * sample.size).each_cons(2).to_a
new_sample = samples.map { |a,b|
a[1] = (a[0] + b[0]).to_f / 2
a
}.flatten
new_sample << sample[-1]
new_sample # => [231, 4187.0, 8143, 12242.0, 16341, 9170.5, 2000, -3676.0, -9352]