-
Notifications
You must be signed in to change notification settings - Fork 33
Description
There are several common ways to store samples:
- Unsigned 8-bit int
- Signed 16-bit int
- Signed 24 bit int
- Signed 32 bit int
- 32 bit float
- 64 bit float
So the natural way to handle audio is templates based on storage type. In fact, in my own library I have 2 fundamental concepts:
template <typename T>
concept bool StorageType()
{
return requires()
{
typename T::ValueType;
{T::ValueSize} -> std::size_t;
{T::BitDepth} -> std::size_t;
{T::DefaultValue} -> typename T::ValueType;
{T::MinimumValue()} -> typename T::ValueType;
{T::MaximumValue()} -> typename T::ValueType;
{T::ShouldClamp()} -> bool;
};
}
template <typename T>
concept bool CalculationType()
{
return General::Arithmetic<T>() && requires(double a)
{
T(a);
};
}
The idea is that integer processing requires saturation arithmetic, int24 have special limits, floats are usually in [-1, 1] and do no need saturation arithmetic.
I'd say the design of StorageType can be made easier by introducing a dedicated type for saturation arithmetic - consider std::saturated<std::int16_t> - but that would hurt compatibility with C API that return std::intN_t because that would require conversion.
The idea that DSP code converts storage type to calculation type, performs arithmetic and converts back. Calculation type must be constructible from double so I can use constants in generic code.