I wrote two template functions to take an integer type and output a float
in the given range. One function takes signed integers and the other takes unsigned integers.
The functions are used to normalize audio PCM data into a range that an encoder is expecting, such as [0.0f, 1.0f]. The source audio data can come in many forms (signed, unsigned, 1 byte, 2 bytes, etc) so I thought template functions would be ideal for this.
The caller has the responsibility of calling the function with an appropriately sized type containing the sample to normalize (e.g. int16_t
). The functions use a lookup-table to determine the max/min values the type can contain based on the size of the integer type.
util.cc
#include <stdint.h>
const uintmax_t MAX_VALUE_PER_BYTES_UNSIGNED[] = {
0,
uintmax_t(UINT8_MAX),
uintmax_t(UINT16_MAX),
uintmax_t(UINT16_MAX) + uintmax_t(UINT8_MAX),
uintmax_t(UINT32_MAX),
uintmax_t(UINT32_MAX) + uintmax_t(UINT8_MAX),
uintmax_t(UINT32_MAX) + uintmax_t(UINT16_MAX),
uintmax_t(UINT32_MAX) + uintmax_t(UINT16_MAX) + uintmax_t(UINT8_MAX),
uintmax_t(UINT64_MAX)
};
const intmax_t MAX_VALUE_PER_BYTES_SIGNED[] = {
0,
intmax_t(INT8_MAX),
intmax_t(INT16_MAX),
intmax_t(INT16_MAX) + intmax_t(INT8_MAX),
intmax_t(INT32_MAX),
intmax_t(INT32_MAX) + intmax_t(INT8_MAX),
intmax_t(INT32_MAX) + intmax_t(INT16_MAX),
intmax_t(INT32_MAX) + intmax_t(INT16_MAX) + intmax_t(INT8_MAX),
intmax_t(INT64_MAX)
};
const intmax_t MIN_VALUE_PER_BYTES_SIGNED[] = {
0,
intmax_t(INT8_MIN),
intmax_t(INT16_MIN),
intmax_t(INT16_MIN) + intmax_t(INT8_MIN),
intmax_t(INT32_MIN),
intmax_t(INT32_MIN) + intmax_t(INT8_MIN),
intmax_t(INT32_MIN) + intmax_t(INT16_MIN),
intmax_t(INT32_MIN) + intmax_t(INT16_MIN) + intmax_t(INT8_MIN),
intmax_t(INT64_MIN)
};
util.h
#include <stdint.h>
#include <limits>
#include <cmath>
extern const uintmax_t MAX_VALUE_PER_BYTES_UNSIGNED[];
extern const intmax_t MAX_VALUE_PER_BYTES_SIGNED[];
extern const intmax_t MIN_VALUE_PER_BYTES_SIGNED[];
template <typename T>
float normalizeToRangeFloatSigned(T num, float rangeMin, float rangeMax) {
float f = 0.0f;
if (!std::numeric_limits<T>::is_integer) {
return f;
}
const float rangeDiff = rangeMax - rangeMin;
if (!rangeDiff || rangeDiff < 0.0f) {
return f;
}
uintmax_t uintRangeMax = MAX_VALUE_PER_BYTES_UNSIGNED[sizeof(T)];
const intmax_t tRangeMin = MIN_VALUE_PER_BYTES_SIGNED[sizeof(T)];
const intmax_t tRangeMax = MAX_VALUE_PER_BYTES_SIGNED[sizeof(T)];
const intmax_t tRangeMinAbs = std::abs(tRangeMin);
float percent = 0.0f;
if (num < 0) {
if (num == tRangeMin) {
percent = 0.0f;
} else {
percent = (tRangeMinAbs - T{-1} * num) / float(uintRangeMax);
}
} else if (!num) {
percent = 0.5f;
} else {
if (num == tRangeMax) {
percent = 1.0f;
} else {
percent = (num + tRangeMinAbs) / float(uintRangeMax);
}
}
f = (percent) * rangeDiff + rangeMin;
return f;
}
template <typename T>
float normalizeToRangeFloatUnsigned(T num, float rangeMin, float rangeMax) {
float f = 0.0f;
if (!std::numeric_limits<T>::is_integer) {
return f;
}
const float rangeDiff = rangeMax - rangeMin;
if (!rangeDiff || rangeDiff < 0.0f) {
return f;
}
uintmax_t uintRangeMax = MAX_VALUE_PER_BYTES_UNSIGNED[sizeof(T)];
f = (num / float(uintRangeMax)) * rangeDiff + rangeMin;
return f;
}
I don't have much experience with templates so any feedback is welcome. Also, any improvements to the functions time-efficiency.