mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-22 00:06:59 +02:00
Add initial support for dithering.
This commit is contained in:
@@ -677,8 +677,8 @@ typedef enum
|
|||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
mal_dither_mode_none = 0,
|
mal_dither_mode_none = 0,
|
||||||
//mal_dither_mode_rectangle,
|
mal_dither_mode_rectangle,
|
||||||
//mal_dither_mode_triangle
|
mal_dither_mode_triangle
|
||||||
} mal_dither_mode;
|
} mal_dither_mode;
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
@@ -888,6 +888,7 @@ typedef struct
|
|||||||
mal_uint32 sampleRateOut;
|
mal_uint32 sampleRateOut;
|
||||||
mal_channel channelMapOut[MAL_MAX_CHANNELS];
|
mal_channel channelMapOut[MAL_MAX_CHANNELS];
|
||||||
mal_channel_mix_mode channelMixMode;
|
mal_channel_mix_mode channelMixMode;
|
||||||
|
mal_dither_mode ditherMode;
|
||||||
mal_src_algorithm srcAlgorithm;
|
mal_src_algorithm srcAlgorithm;
|
||||||
mal_bool32 allowDynamicSampleRate;
|
mal_bool32 allowDynamicSampleRate;
|
||||||
mal_dsp_read_proc onRead;
|
mal_dsp_read_proc onRead;
|
||||||
@@ -1924,6 +1925,39 @@ mal_bool32 mal_channel_map_contains_channel_position(mal_uint32 channels, const
|
|||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Format Conversion
|
// Format Conversion
|
||||||
|
// =================
|
||||||
|
// The format converter serves two purposes:
|
||||||
|
// 1) Conversion between data formats (u8 to f32, etc.)
|
||||||
|
// 2) Interleaving and deinterleaving
|
||||||
|
//
|
||||||
|
// When initializing a converter, you specify the input and output formats (u8, s16, etc.) and read callbacks. There are two read callbacks - one for
|
||||||
|
// interleaved input data (onRead) and another for deinterleaved input data (onReadDeinterleaved). You implement whichever is most convenient for you. You
|
||||||
|
// can implement both, but it's not recommended as it just introduces unnecessary complexity.
|
||||||
|
//
|
||||||
|
// To read data as interleaved samples, use mal_format_converter_read(). Otherwise use mal_format_converter_read_deinterleaved().
|
||||||
|
//
|
||||||
|
// Dithering
|
||||||
|
// ---------
|
||||||
|
// The format converter also supports dithering. Dithering can be set using ditherMode variable in the config, like so.
|
||||||
|
//
|
||||||
|
// pConfig->ditherMode = mal_dither_mode_rectangle;
|
||||||
|
//
|
||||||
|
// The different dithering modes include the following, in order of efficiency:
|
||||||
|
// - None: mal_dither_mode_none
|
||||||
|
// - Rectangle: mal_dither_mode_rectangle
|
||||||
|
// - Triangle: mal_dither_mode_triangle
|
||||||
|
//
|
||||||
|
// Note that even if the dither mode is set to something other than mal_dither_mode_none, it will be ignored for conversions where dithering is not needed.
|
||||||
|
// Dithering is available for the following conversions:
|
||||||
|
// - s16 -> u8
|
||||||
|
// - s24 -> u8
|
||||||
|
// - s32 -> u8
|
||||||
|
// - f32 -> u8
|
||||||
|
// - s24 -> s16
|
||||||
|
// - s32 -> s16
|
||||||
|
// - f32 -> s16
|
||||||
|
//
|
||||||
|
// Note that it is not an error to pass something other than mal_dither_mode_none for conversions where dither is not used. It will just be ignored.
|
||||||
//
|
//
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@@ -3063,6 +3097,90 @@ static MAL_INLINE float mal_mix_f32(float x, float y, float a)
|
|||||||
return x*(1-a) + y*a;
|
return x*(1-a) + y*a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MAL_INLINE float mal_scale_to_range_f32(float x, float lo, float hi)
|
||||||
|
{
|
||||||
|
return lo + x*(hi-lo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Random Number Generation
|
||||||
|
//
|
||||||
|
// mini_al uses the LCG random number generation algorithm. This is good enough for audio.
|
||||||
|
//
|
||||||
|
// Note that mini_al's LCG implementation uses global state which is _not_ thread-local. When this is called across
|
||||||
|
// multiple threads, results will be unpredictable. However, it won't crash and results will still be random enough
|
||||||
|
// for mini_al's purposes.
|
||||||
|
#define MAL_LCG_M 4294967296
|
||||||
|
#define MAL_LCG_A 1103515245
|
||||||
|
#define MAL_LCG_C 12345
|
||||||
|
static mal_int32 g_malLCG;
|
||||||
|
|
||||||
|
void mal_seed(mal_int32 seed)
|
||||||
|
{
|
||||||
|
g_malLCG = seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_int32 mal_rand_s32()
|
||||||
|
{
|
||||||
|
mal_int32 lcg = g_malLCG;
|
||||||
|
mal_int32 r = (MAL_LCG_A * lcg + MAL_LCG_C) % MAL_LCG_M;
|
||||||
|
g_malLCG = r;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
double mal_rand_f64()
|
||||||
|
{
|
||||||
|
return (mal_rand_s32() + 0x80000000) / (double)0x7FFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
float mal_rand_f32()
|
||||||
|
{
|
||||||
|
return (float)mal_rand_f64();
|
||||||
|
}
|
||||||
|
|
||||||
|
static MAL_INLINE float mal_rand_range_f32(float lo, float hi)
|
||||||
|
{
|
||||||
|
return mal_scale_to_range_f32(mal_rand_f32(), lo, hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
static MAL_INLINE mal_int32 mal_rand_range_s32(mal_int32 lo, mal_int32 hi)
|
||||||
|
{
|
||||||
|
double x = mal_rand_f64();
|
||||||
|
return lo + (mal_int32)(x*(hi-lo));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static MAL_INLINE float mal_dither_f32(mal_dither_mode ditherMode, float ditherMin, float ditherMax)
|
||||||
|
{
|
||||||
|
if (ditherMode == mal_dither_mode_rectangle) {
|
||||||
|
float a = mal_rand_range_f32(ditherMin, ditherMax);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (ditherMode == mal_dither_mode_triangle) {
|
||||||
|
float a = mal_rand_range_f32(ditherMin, 0);
|
||||||
|
float b = mal_rand_range_f32(0, ditherMax);
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static MAL_INLINE mal_int32 mal_dither_s32(mal_dither_mode ditherMode, mal_int32 ditherMin, mal_int32 ditherMax)
|
||||||
|
{
|
||||||
|
if (ditherMode == mal_dither_mode_rectangle) {
|
||||||
|
mal_int32 a = mal_rand_range_s32(ditherMin, ditherMax);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (ditherMode == mal_dither_mode_triangle) {
|
||||||
|
mal_int32 a = mal_rand_range_s32(ditherMin, 0);
|
||||||
|
mal_int32 b = mal_rand_range_s32(0, ditherMax);
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Splits a buffer into parts of equal length and of the given alignment. The returned size of the split buffers will be a
|
// Splits a buffer into parts of equal length and of the given alignment. The returned size of the split buffers will be a
|
||||||
// multiple of the alignment. The alignment must be a power of 2.
|
// multiple of the alignment. The alignment must be a power of 2.
|
||||||
@@ -16977,17 +17095,34 @@ void mal_pcm_deinterleave_u8(void** dst, const void* src, mal_uint64 frameCount,
|
|||||||
// s16
|
// s16
|
||||||
void mal_pcm_s16_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_s16_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
|
||||||
|
|
||||||
mal_uint8* dst_u8 = (mal_uint8*)dst;
|
mal_uint8* dst_u8 = (mal_uint8*)dst;
|
||||||
const mal_int16* src_s16 = (const mal_int16*)src;
|
const mal_int16* src_s16 = (const mal_int16*)src;
|
||||||
|
|
||||||
mal_uint64 i;
|
if (ditherMode == mal_dither_mode_none) {
|
||||||
for (i = 0; i < count; i += 1) {
|
mal_uint64 i;
|
||||||
mal_int16 x = src_s16[i];
|
for (i = 0; i < count; i += 1) {
|
||||||
x = x >> 8;
|
mal_int16 x = src_s16[i];
|
||||||
x = x + 128;
|
x = x >> 8;
|
||||||
dst_u8[i] = (mal_uint8)x;
|
x = x + 128;
|
||||||
|
dst_u8[i] = (mal_uint8)x;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mal_uint64 i;
|
||||||
|
for (i = 0; i < count; i += 1) {
|
||||||
|
mal_int16 x = src_s16[i];
|
||||||
|
|
||||||
|
// Dither. Don't overflow.
|
||||||
|
mal_int32 dither = mal_dither_s32(ditherMode, -0x80, 0x7F);
|
||||||
|
if ((x + dither) <= 0x7FFF) {
|
||||||
|
x = (mal_int16)(x + dither);
|
||||||
|
} else {
|
||||||
|
x = 0x7FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = x >> 8;
|
||||||
|
x = x + 128;
|
||||||
|
dst_u8[i] = (mal_uint8)x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17216,15 +17351,32 @@ void mal_pcm_deinterleave_s16(void** dst, const void* src, mal_uint64 frameCount
|
|||||||
// s24
|
// s24
|
||||||
void mal_pcm_s24_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_s24_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
|
||||||
|
|
||||||
mal_uint8* dst_u8 = (mal_uint8*)dst;
|
mal_uint8* dst_u8 = (mal_uint8*)dst;
|
||||||
const mal_uint8* src_s24 = (const mal_uint8*)src;
|
const mal_uint8* src_s24 = (const mal_uint8*)src;
|
||||||
|
|
||||||
mal_uint64 i;
|
if (ditherMode == mal_dither_mode_none) {
|
||||||
for (i = 0; i < count; i += 1) {
|
mal_uint64 i;
|
||||||
mal_int8 x = (mal_int8)src_s24[i*3 + 2] + 128;
|
for (i = 0; i < count; i += 1) {
|
||||||
dst_u8[i] = (mal_uint8)x;
|
mal_int8 x = (mal_int8)src_s24[i*3 + 2] + 128;
|
||||||
|
dst_u8[i] = (mal_uint8)x;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mal_uint64 i;
|
||||||
|
for (i = 0; i < count; i += 1) {
|
||||||
|
mal_int32 x = (mal_int32)(((mal_uint32)(src_s24[i*3+0]) << 8) | ((mal_uint32)(src_s24[i*3+1]) << 16) | ((mal_uint32)(src_s24[i*3+2])) << 24);
|
||||||
|
|
||||||
|
// Dither. Don't overflow.
|
||||||
|
mal_int32 dither = mal_dither_s32(ditherMode, -0x800000, 0x7FFFFF);
|
||||||
|
if ((mal_int64)x + dither <= 0x7FFFFFFF) {
|
||||||
|
x = x + dither;
|
||||||
|
} else {
|
||||||
|
x = 0x7FFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = x >> 24;
|
||||||
|
x = x + 128;
|
||||||
|
dst_u8[i] = (mal_uint8)x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17256,16 +17408,32 @@ void mal_pcm_s24_to_u8(void* dst, const void* src, mal_uint64 count, mal_dither_
|
|||||||
|
|
||||||
void mal_pcm_s24_to_s16__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_s24_to_s16__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
|
||||||
|
|
||||||
mal_int16* dst_s16 = (mal_int16*)dst;
|
mal_int16* dst_s16 = (mal_int16*)dst;
|
||||||
const mal_uint8* src_s24 = (const mal_uint8*)src;
|
const mal_uint8* src_s24 = (const mal_uint8*)src;
|
||||||
|
|
||||||
mal_uint64 i;
|
if (ditherMode == mal_dither_mode_none) {
|
||||||
for (i = 0; i < count; i += 1) {
|
mal_uint64 i;
|
||||||
mal_uint16 dst_lo = ((mal_uint16)src_s24[i*3 + 1]);
|
for (i = 0; i < count; i += 1) {
|
||||||
mal_uint16 dst_hi = ((mal_uint16)src_s24[i*3 + 2]) << 8;
|
mal_uint16 dst_lo = ((mal_uint16)src_s24[i*3 + 1]);
|
||||||
dst_s16[i] = (mal_int16)dst_lo | dst_hi;
|
mal_uint16 dst_hi = ((mal_uint16)src_s24[i*3 + 2]) << 8;
|
||||||
|
dst_s16[i] = (mal_int16)dst_lo | dst_hi;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mal_uint64 i;
|
||||||
|
for (i = 0; i < count; i += 1) {
|
||||||
|
mal_int32 x = (mal_int32)(((mal_uint32)(src_s24[i*3+0]) << 8) | ((mal_uint32)(src_s24[i*3+1]) << 16) | ((mal_uint32)(src_s24[i*3+2])) << 24);
|
||||||
|
|
||||||
|
// Dither. Don't overflow.
|
||||||
|
mal_int32 dither = mal_dither_s32(ditherMode, -0x8000, 0x7FFF);
|
||||||
|
if ((mal_int64)x + dither <= 0x7FFFFFFF) {
|
||||||
|
x = x + dither;
|
||||||
|
} else {
|
||||||
|
x = 0x7FFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = x >> 16;
|
||||||
|
dst_s16[i] = (mal_int16)x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17459,17 +17627,34 @@ void mal_pcm_deinterleave_s24(void** dst, const void* src, mal_uint64 frameCount
|
|||||||
// s32
|
// s32
|
||||||
void mal_pcm_s32_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_s32_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
|
||||||
|
|
||||||
mal_uint8* dst_u8 = (mal_uint8*)dst;
|
mal_uint8* dst_u8 = (mal_uint8*)dst;
|
||||||
const mal_int32* src_s32 = (const mal_int32*)src;
|
const mal_int32* src_s32 = (const mal_int32*)src;
|
||||||
|
|
||||||
mal_uint64 i;
|
if (ditherMode == mal_dither_mode_none) {
|
||||||
for (i = 0; i < count; i += 1) {
|
mal_uint64 i;
|
||||||
mal_int32 x = src_s32[i];
|
for (i = 0; i < count; i += 1) {
|
||||||
x = x >> 24;
|
mal_int32 x = src_s32[i];
|
||||||
x = x + 128;
|
x = x >> 24;
|
||||||
dst_u8[i] = (mal_uint8)x;
|
x = x + 128;
|
||||||
|
dst_u8[i] = (mal_uint8)x;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mal_uint64 i;
|
||||||
|
for (i = 0; i < count; i += 1) {
|
||||||
|
mal_int32 x = src_s32[i];
|
||||||
|
|
||||||
|
// Dither. Don't overflow.
|
||||||
|
mal_int32 dither = mal_dither_s32(ditherMode, -0x800000, 0x7FFFFF);
|
||||||
|
if ((mal_int64)x + dither <= 0x7FFFFFFF) {
|
||||||
|
x = x + dither;
|
||||||
|
} else {
|
||||||
|
x = 0x7FFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = x >> 24;
|
||||||
|
x = x + 128;
|
||||||
|
dst_u8[i] = (mal_uint8)x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17501,16 +17686,32 @@ void mal_pcm_s32_to_u8(void* dst, const void* src, mal_uint64 count, mal_dither_
|
|||||||
|
|
||||||
void mal_pcm_s32_to_s16__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_s32_to_s16__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
|
||||||
|
|
||||||
mal_int16* dst_s16 = (mal_int16*)dst;
|
mal_int16* dst_s16 = (mal_int16*)dst;
|
||||||
const mal_int32* src_s32 = (const mal_int32*)src;
|
const mal_int32* src_s32 = (const mal_int32*)src;
|
||||||
|
|
||||||
mal_uint64 i;
|
if (ditherMode == mal_dither_mode_none) {
|
||||||
for (i = 0; i < count; i += 1) {
|
mal_uint64 i;
|
||||||
mal_int32 x = src_s32[i];
|
for (i = 0; i < count; i += 1) {
|
||||||
x = x >> 16;
|
mal_int32 x = src_s32[i];
|
||||||
dst_s16[i] = (mal_int16)x;
|
x = x >> 16;
|
||||||
|
dst_s16[i] = (mal_int16)x;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mal_uint64 i;
|
||||||
|
for (i = 0; i < count; i += 1) {
|
||||||
|
mal_int32 x = src_s32[i];
|
||||||
|
|
||||||
|
// Dither. Don't overflow.
|
||||||
|
mal_int32 dither = mal_dither_s32(ditherMode, -0x8000, 0x7FFF);
|
||||||
|
if ((mal_int64)x + dither <= 0x7FFFFFFF) {
|
||||||
|
x = x + dither;
|
||||||
|
} else {
|
||||||
|
x = 0x7FFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = x >> 16;
|
||||||
|
dst_s16[i] = (mal_int16)x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17542,7 +17743,7 @@ void mal_pcm_s32_to_s16(void* dst, const void* src, mal_uint64 count, mal_dither
|
|||||||
|
|
||||||
void mal_pcm_s32_to_s24__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_s32_to_s24__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
(void)ditherMode; // No dithering for s32 -> s24.
|
||||||
|
|
||||||
mal_uint8* dst_s24 = (mal_uint8*)dst;
|
mal_uint8* dst_s24 = (mal_uint8*)dst;
|
||||||
const mal_int32* src_s32 = (const mal_int32*)src;
|
const mal_int32* src_s32 = (const mal_int32*)src;
|
||||||
@@ -17592,7 +17793,7 @@ void mal_pcm_s32_to_s32(void* dst, const void* src, mal_uint64 count, mal_dither
|
|||||||
|
|
||||||
void mal_pcm_s32_to_f32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_s32_to_f32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
(void)ditherMode; // No dithering for s32 -> f32.
|
||||||
|
|
||||||
float* dst_f32 = (float*)dst;
|
float* dst_f32 = (float*)dst;
|
||||||
const mal_int32* src_s32 = (const mal_int32*)src;
|
const mal_int32* src_s32 = (const mal_int32*)src;
|
||||||
@@ -17700,14 +17901,20 @@ void mal_pcm_deinterleave_s32(void** dst, const void* src, mal_uint64 frameCount
|
|||||||
// f32
|
// f32
|
||||||
void mal_pcm_f32_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_f32_to_u8__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
|
||||||
|
|
||||||
mal_uint8* dst_u8 = (mal_uint8*)dst;
|
mal_uint8* dst_u8 = (mal_uint8*)dst;
|
||||||
const float* src_f32 = (const float*)src;
|
const float* src_f32 = (const float*)src;
|
||||||
|
|
||||||
|
float ditherMin = 0;
|
||||||
|
float ditherMax = 0;
|
||||||
|
if (ditherMode != mal_dither_mode_none) {
|
||||||
|
ditherMin = 1.0f / -128;
|
||||||
|
ditherMax = 1.0f / 127;
|
||||||
|
}
|
||||||
|
|
||||||
mal_uint64 i;
|
mal_uint64 i;
|
||||||
for (i = 0; i < count; i += 1) {
|
for (i = 0; i < count; i += 1) {
|
||||||
float x = src_f32[i];
|
float x = src_f32[i];
|
||||||
|
x = x + mal_dither_f32(ditherMode, ditherMin, ditherMax);
|
||||||
x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); // clip
|
x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); // clip
|
||||||
x = x + 1; // -1..1 to 0..2
|
x = x + 1; // -1..1 to 0..2
|
||||||
x = x * 127.5f; // 0..2 to 0..255
|
x = x * 127.5f; // 0..2 to 0..255
|
||||||
@@ -17744,14 +17951,20 @@ void mal_pcm_f32_to_u8(void* dst, const void* src, mal_uint64 count, mal_dither_
|
|||||||
|
|
||||||
void mal_pcm_f32_to_s16__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_f32_to_s16__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
|
||||||
|
|
||||||
mal_int16* dst_s16 = (mal_int16*)dst;
|
mal_int16* dst_s16 = (mal_int16*)dst;
|
||||||
const float* src_f32 = (const float*)src;
|
const float* src_f32 = (const float*)src;
|
||||||
|
|
||||||
|
float ditherMin = 0;
|
||||||
|
float ditherMax = 0;
|
||||||
|
if (ditherMode != mal_dither_mode_none) {
|
||||||
|
ditherMin = 1.0f / -32768;
|
||||||
|
ditherMax = 1.0f / 32767;
|
||||||
|
}
|
||||||
|
|
||||||
mal_uint64 i;
|
mal_uint64 i;
|
||||||
for (i = 0; i < count; i += 1) {
|
for (i = 0; i < count; i += 1) {
|
||||||
float x = src_f32[i];
|
float x = src_f32[i];
|
||||||
|
x = x + mal_dither_f32(ditherMode, ditherMin, ditherMax);
|
||||||
x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); // clip
|
x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); // clip
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
@@ -17796,7 +18009,7 @@ void mal_pcm_f32_to_s16(void* dst, const void* src, mal_uint64 count, mal_dither
|
|||||||
|
|
||||||
void mal_pcm_f32_to_s24__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_f32_to_s24__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
(void)ditherMode; // No dithering for f32 -> s24.
|
||||||
|
|
||||||
mal_uint8* dst_s24 = (mal_uint8*)dst;
|
mal_uint8* dst_s24 = (mal_uint8*)dst;
|
||||||
const float* src_f32 = (const float*)src;
|
const float* src_f32 = (const float*)src;
|
||||||
@@ -17851,7 +18064,7 @@ void mal_pcm_f32_to_s24(void* dst, const void* src, mal_uint64 count, mal_dither
|
|||||||
|
|
||||||
void mal_pcm_f32_to_s32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
void mal_pcm_f32_to_s32__reference(void* dst, const void* src, mal_uint64 count, mal_dither_mode ditherMode)
|
||||||
{
|
{
|
||||||
(void)ditherMode;
|
(void)ditherMode; // No dithering for f32 -> s32.
|
||||||
|
|
||||||
mal_int32* dst_s32 = (mal_int32*)dst;
|
mal_int32* dst_s32 = (mal_int32*)dst;
|
||||||
const float* src_f32 = (const float*)src;
|
const float* src_f32 = (const float*)src;
|
||||||
@@ -19586,6 +19799,7 @@ mal_result mal_dsp_init(const mal_dsp_config* pConfig, mal_dsp* pDSP)
|
|||||||
preFormatConverterConfig.formatIn = pConfig->formatIn;
|
preFormatConverterConfig.formatIn = pConfig->formatIn;
|
||||||
preFormatConverterConfig.formatOut = mal_format_f32;
|
preFormatConverterConfig.formatOut = mal_format_f32;
|
||||||
preFormatConverterConfig.channels = pConfig->channelsIn;
|
preFormatConverterConfig.channels = pConfig->channelsIn;
|
||||||
|
preFormatConverterConfig.ditherMode = pConfig->ditherMode;
|
||||||
preFormatConverterConfig.streamFormatIn = mal_stream_format_pcm;
|
preFormatConverterConfig.streamFormatIn = mal_stream_format_pcm;
|
||||||
preFormatConverterConfig.streamFormatOut = mal_stream_format_pcm;
|
preFormatConverterConfig.streamFormatOut = mal_stream_format_pcm;
|
||||||
preFormatConverterConfig.onRead = mal_dsp__pre_format_converter_on_read;
|
preFormatConverterConfig.onRead = mal_dsp__pre_format_converter_on_read;
|
||||||
@@ -19604,6 +19818,7 @@ mal_result mal_dsp_init(const mal_dsp_config* pConfig, mal_dsp* pDSP)
|
|||||||
postFormatConverterConfig.formatIn = pConfig->formatIn;
|
postFormatConverterConfig.formatIn = pConfig->formatIn;
|
||||||
postFormatConverterConfig.formatOut = pConfig->formatOut;
|
postFormatConverterConfig.formatOut = pConfig->formatOut;
|
||||||
postFormatConverterConfig.channels = pConfig->channelsOut;
|
postFormatConverterConfig.channels = pConfig->channelsOut;
|
||||||
|
postFormatConverterConfig.ditherMode = pConfig->ditherMode;
|
||||||
postFormatConverterConfig.streamFormatIn = mal_stream_format_pcm;
|
postFormatConverterConfig.streamFormatIn = mal_stream_format_pcm;
|
||||||
postFormatConverterConfig.streamFormatOut = mal_stream_format_pcm;
|
postFormatConverterConfig.streamFormatOut = mal_stream_format_pcm;
|
||||||
postFormatConverterConfig.pUserData = pDSP;
|
postFormatConverterConfig.pUserData = pDSP;
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
#define MAL_USE_REFERENCE_CONVERSION_APIS
|
||||||
|
#define MAL_IMPLEMENTATION
|
||||||
|
#include "../mini_al.h"
|
||||||
|
|
||||||
|
// Two converters are needed here. One for converting f32 samples from the sine wave generator to the input format,
|
||||||
|
// and another for converting the input format to the output format for device output.
|
||||||
|
mal_sine_wave sineWave;
|
||||||
|
mal_format_converter converterIn;
|
||||||
|
mal_format_converter converterOut;
|
||||||
|
|
||||||
|
mal_uint32 on_convert_samples_in(mal_format_converter* pConverter, mal_uint32 frameCount, void* pFrames, void* pUserData)
|
||||||
|
{
|
||||||
|
(void)pUserData;
|
||||||
|
mal_assert(pConverter->config.formatIn == mal_format_f32);
|
||||||
|
|
||||||
|
mal_sine_wave* pSineWave = (mal_sine_wave*)pConverter->config.pUserData;
|
||||||
|
mal_assert(pSineWave);
|
||||||
|
|
||||||
|
return (mal_uint32)mal_sine_wave_read(pSineWave, frameCount, pFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_uint32 on_convert_samples_out(mal_format_converter* pConverter, mal_uint32 frameCount, void* pFrames, void* pUserData)
|
||||||
|
{
|
||||||
|
(void)pUserData;
|
||||||
|
|
||||||
|
mal_format_converter* pConverterIn = (mal_format_converter*)pConverter->config.pUserData;
|
||||||
|
mal_assert(pConverterIn != NULL);
|
||||||
|
|
||||||
|
return (mal_uint32)mal_format_converter_read(pConverterIn, frameCount, pFrames, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_uint32 on_send_to_device__original(mal_device* pDevice, mal_uint32 frameCount, void* pFrames)
|
||||||
|
{
|
||||||
|
(void)pDevice;
|
||||||
|
mal_assert(pDevice->format == mal_format_f32);
|
||||||
|
mal_assert(pDevice->channels == 1);
|
||||||
|
|
||||||
|
return (mal_uint32)mal_sine_wave_read(&sineWave, frameCount, pFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_uint32 on_send_to_device__dithered(mal_device* pDevice, mal_uint32 frameCount, void* pFrames)
|
||||||
|
{
|
||||||
|
mal_assert(pDevice->channels == 1);
|
||||||
|
|
||||||
|
mal_format_converter* pConverter = (mal_format_converter*)pDevice->pUserData;
|
||||||
|
mal_assert(pConverter != NULL);
|
||||||
|
mal_assert(pDevice->format == pConverter->config.formatOut);
|
||||||
|
|
||||||
|
return (mal_uint32)mal_format_converter_read(pConverter, frameCount, pFrames, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int do_dithering_test()
|
||||||
|
{
|
||||||
|
mal_sine_wave_init(0.5, 400, 48000, &sineWave);
|
||||||
|
|
||||||
|
|
||||||
|
mal_device_config config = mal_device_config_init_playback(mal_format_f32, 1, 0, on_send_to_device__original);
|
||||||
|
mal_device device;
|
||||||
|
mal_result result;
|
||||||
|
|
||||||
|
// We first play the sound the way it's meant to be played.
|
||||||
|
result = mal_device_init(NULL, mal_device_type_playback, NULL, &config, NULL, &device);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = mal_device_start(&device);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Press Enter to play enable dithering.\n");
|
||||||
|
getchar();
|
||||||
|
mal_device_uninit(&device);
|
||||||
|
|
||||||
|
|
||||||
|
// Now we play the sound after it's run through a dithered format converter.
|
||||||
|
mal_sine_wave_init(0.5, 400, 48000, &sineWave);
|
||||||
|
|
||||||
|
mal_format srcFormat = mal_format_s24;
|
||||||
|
mal_format dstFormat = mal_format_s16;
|
||||||
|
mal_dither_mode ditherMode = mal_dither_mode_triangle;
|
||||||
|
|
||||||
|
mal_format_converter_config converterInConfig;
|
||||||
|
mal_zero_object(&converterInConfig);
|
||||||
|
converterInConfig.formatIn = mal_format_f32; // <-- From the sine wave generator.
|
||||||
|
converterInConfig.formatOut = srcFormat;
|
||||||
|
converterInConfig.channels = config.channels;
|
||||||
|
converterInConfig.ditherMode = mal_dither_mode_none;
|
||||||
|
converterInConfig.onRead = on_convert_samples_in;
|
||||||
|
converterInConfig.pUserData = &sineWave;
|
||||||
|
result = mal_format_converter_init(&converterInConfig, &converterIn);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_format_converter_config converterOutConfig;
|
||||||
|
mal_zero_object(&converterInConfig);
|
||||||
|
converterOutConfig.formatIn = srcFormat;
|
||||||
|
converterOutConfig.formatOut = dstFormat;
|
||||||
|
converterOutConfig.channels = config.channels;
|
||||||
|
converterOutConfig.ditherMode = ditherMode;
|
||||||
|
converterOutConfig.onRead = on_convert_samples_out;
|
||||||
|
converterOutConfig.pUserData = &converterIn;
|
||||||
|
result = mal_format_converter_init(&converterOutConfig, &converterOut);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
config = mal_device_config_init_playback(converterOutConfig.formatOut, 1, 0, on_send_to_device__dithered);
|
||||||
|
|
||||||
|
result = mal_device_init(NULL, mal_device_type_playback, NULL, &config, &converterOut, &device);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = mal_device_start(&device);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Press Enter to stop.\n");
|
||||||
|
getchar();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
(void)argc;
|
||||||
|
(void)argv;
|
||||||
|
|
||||||
|
|
||||||
|
do_dithering_test();
|
||||||
|
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -260,6 +260,7 @@
|
|||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClCompile Include="mal_dithering.c" />
|
||||||
<ClCompile Include="mal_profiling.c">
|
<ClCompile Include="mal_profiling.c">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
@@ -269,12 +270,12 @@
|
|||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="mal_test_0.c">
|
<ClCompile Include="mal_test_0.c">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="mal_test_0.cpp">
|
<ClCompile Include="mal_test_0.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
<ClCompile Include="mal_profiling.c">
|
<ClCompile Include="mal_profiling.c">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="mal_dithering.c">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\mini_al.h">
|
<ClInclude Include="..\mini_al.h">
|
||||||
|
|||||||
Reference in New Issue
Block a user