I have an array of 16-bit PCM samples (for the purpose of this question, let's say that they are signed). I have to apply a value, ranging from 0 to 65535, as the gain (volume control) for the samples, with 65535 being maximum volume (no change), and 0 being no volume (silence).
The easiest solution would be to convert the gain value to a float 0.0 - 1.0, and while iterating on the array, convert the iterated sample to a float as well, then to multiply the two values, and convert the result back to a 16-bit integer, then write that integer back to the array. I feel like that would be very inefficient - we would be doing multiple short
-> float
-> short
conversions, floating point math is slower than integer math, and all of this would be magnified as (usually) 44100 PCM samples are equal to 1 second.
Is there a way to do this without floating-point math? I'm fine with inaccuracies, if any come up.
You can avoid the conversions to and from float by using fixed-point arithmetic. To keep exactly the original samples at maximum volume, it is a bit easier if the volume is a "Q15" value in the range 0 to 32768. To do a fixed-point multiplication:
In C, something like this
// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0
#include <stdint.h>
void adjust_volume(int16_t* samples, int num_samples, uint16_t volume) {
for (int i = 0; i < num_samples; ++i) {
samples[i] = (int16_t)(
(((int32_t)volume) * samples[i]) / (INT32_C(1) << 15));
}
}