Mercurial > audlegacy
diff src/libSAD/dither.c @ 4232:704607c1f858
1st attempt to integrate dithering and RG engine
author | Eugene Zagidullin <e.asphyx@gmail.com> |
---|---|
date | Wed, 30 Jan 2008 01:22:37 +0300 |
parents | |
children | 74c6f3d3cf1d |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/libSAD/dither.c Wed Jan 30 01:22:37 2008 +0300 @@ -0,0 +1,492 @@ +/* Scale & Dither library (libSAD) + * High-precision bit depth converter with ReplayGain support + * + * (c)2007 by Eugene Zagidullin (e.asphyx@gmail.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "common.h" +#include "buffer.h" +#include "noisegen.h" + +#include <assert.h> +#include <math.h> + +/* + * Supported conversions: + * + * O U T P U T + * ,------------------+-----------------------------------------------. + * | |S8 U8 S16 U16 S24 U24 S32 U32 FLOAT FIXED-POINT| + * +------------------+-----------------------------------------------+ + * | S8 |X X X X X X X X - - | + * | U8 |X X X X X X X X - - | + * I | S16 |X X X X X X X X - - | + * N | U16 |X X X X X X X X - - | + * P | S24 |X X X X X X X X - - | + * U | U24 |X X X X X X X X - - | + * T | S32 |X X X X X X X X - - | + * | U32 |X X X X X X X X - - | + * | FLOAT |X X X X X X X X X - | + * | FIXED-POINT |X X X X X X X X X - | + * `------------------+-----------------------------------------------' + */ + +#define SCALE(x,s) (s != 1.0 ? x * s : x) +#define MAXINT(a) (1L << ((a)-1)) +#define CLIP(x,m) (x > m-1 ? m-1 : (x < -m ? -m : x)) + +/* private object */ +typedef struct { + SAD_sample_format input_sample_format; + SAD_sample_format output_sample_format; + int input_bits; + int input_fracbits; + int output_bits; + int output_fracbits; + int channels; + SAD_channels_order input_chorder; + SAD_channels_order output_chorder; + SAD_get_sample_proc get_sample; + SAD_put_sample_proc put_sample; + int dither; + int hardlimit; + float scale; + float rg_scale; +} SAD_state_priv; + +/* error code */ + +//static SAD_error SAD_last_error = SAD_ERROR_OK; + +static inline double compute_hardlimit (double sample, float scale) { + sample *= scale; + const double k = 0.5; /* -6dBFS */ + if (sample > k) { + return tanh((sample - k) / (1 - k)) * (1 - k) + k; + } + else if (sample < -k) { + return tanh((sample + k) / (1 - k)) * (1 - k) - k; + } + return sample; +} + +/* + * Dither fixed-point normalized or integer sample to n-bits integer + * samples < -1 and > 1 will be clipped + */ + +static inline sad_sint32 __dither_sample_fixed_to_int (sad_sint32 sample, int inbits, int fracbits, int outbits, float scale, int dither, + int hardlimit) +{ + int n_bits_to_loose, bitwidth, precision_loss; + sad_sint32 maxint = MAXINT(outbits); + + n_bits_to_loose = 0; + bitwidth = inbits; + precision_loss = FALSE; + +/*#ifdef DEEP_DEBUG + printf("f: __dither_sample_fixed_to_int\n"); +#endif*/ + + if (fracbits == 0) { + if (inbits<29) { + /* convert to 4.28 fixed-point */ + n_bits_to_loose = 29 - inbits; + sample <<= n_bits_to_loose; + bitwidth += n_bits_to_loose; + } + + n_bits_to_loose += inbits - outbits; + + if (inbits > outbits) { + precision_loss = TRUE; +#ifdef PRECISION_DEBUG + printf("Precision loss, reason: bitwidth loss %d --> %d\n", inbits, outbits); +#endif + } + } else { + n_bits_to_loose = fracbits + 1 - outbits; + bitwidth = fracbits; + precision_loss = TRUE; +#ifdef PRECISION_DEBUG + printf("Precision loss, reason: fixed-point input\n", inbits, outbits); +#endif + } + + assert(n_bits_to_loose >=0 ); + + if (hardlimit) { + sample = (sad_sint32)(compute_hardlimit((double)sample/(double)MAXINT(bitwidth), scale) * (double)MAXINT(bitwidth)); +#ifdef PRECISION_DEBUG + printf("Precision loss, reason: hard limiter\n", inbits, outbits); +#endif + precision_loss = TRUE; + } else { + sample = SCALE(sample, scale); + } + + if (scale != 1.0){ + precision_loss = TRUE; +#ifdef PRECISION_DEBUG + printf("Precision loss, reason: scale\n", inbits, outbits); +#endif + } + + if (precision_loss && (n_bits_to_loose >= 1)) sample += (1L << (n_bits_to_loose - 1)); + +#ifdef DITHER_DEBUG + sad_sint32 val_wo_dither = sample, >> n_bits_to_loose; + val_wo_dither = CLIP(val_wo_dither, maxint); +#endif + if (dither && precision_loss && (n_bits_to_loose >= 1)) { + sad_sint32 dither_num = triangular_dither_noise(n_bits_to_loose + 1); + sample += dither_num; + } + + sample >>= n_bits_to_loose; + + /* Clipping */ +#ifdef CLIPPING_DEBUG + sad_sint32 val_wo_clip = sample; +#endif + sample = CLIP(sample, maxint); +#ifdef CLIPPING_DEBUG + if (val_wo_clip != sample) { + printf("Clipping: %d --> %d\n", val_wo_clip, sample); + } +#endif +#ifdef DITHER_DEBUG + if (dither && precision_loss && (n_bits_to_loose >= 1)) printf("%d --> %d, noise: %d\n", val_wo_dither, sample, sample - val_wo_dither); +#endif + return sample; +} + +/* + * Dither floating-point normalized sample to n-bits integer + * samples < -1 and > 1 will be clipped + */ +static inline sad_sint32 __dither_sample_float_to_int (float sample, int nbits, float scale, int dither, int hardlimit) { + +#ifdef DEEP_DEBUG + printf("f: __dither_sample_float_to_int\n"); +#endif + + sad_sint32 maxint = MAXINT(nbits); + + if (hardlimit) { + sample = compute_hardlimit((double)sample, scale); + } else { + sample = SCALE(sample, scale); + } + + sample *= maxint; + /* we want to round precisely */ + sample = (sample < 0 ? sample - 0.5 : sample + 0.5); + +#ifdef DITHER_DEBUG + sad_sint32 val_wo_dither = (sad_sint32) sample; + val_wo_dither = CLIP(val_wo_dither, maxint); +#endif + if (dither) { + float dither_num = triangular_dither_noise_f(); + sample += dither_num; + } + + /* Round and clipping */ + sad_sint32 value = (sad_sint32) sample; +#ifdef CLIPPING_DEBUG + sad_sint32 val_wo_clip = value; +#endif + value = CLIP(value, maxint); +#ifdef CLIPPING_DEBUG + if (val_wo_clip != value) { + printf("Clipping: %d --> %d\n", val_wo_clip, value); + } +#endif + +#ifdef DITHER_DEBUG + printf("%d --> %d, noise: %d\n", val_wo_dither, value, value - val_wo_dither); +#endif + return value; +} + +static inline float __dither_sample_float_to_float (float sample, float scale, int hardlimit) { +#ifdef DEEP_DEBUG + printf("f: __dither_sample_float_to_float\n"); +#endif + if (hardlimit) { + sample = compute_hardlimit((double)sample, scale); + } else { + sample = SCALE(sample, scale); + } + return sample; +} + +static inline float __dither_sample_fixed_to_float (sad_sint32 sample, int inbits, int fracbits, float scale, int hardlimit) { + float fsample; + +#ifdef DEEP_DEBUG + printf("f: __dither_sample_fixed_to_float\n"); +#endif + if (fracbits == 0) { + fsample = (float)sample / (float)MAXINT(inbits); + } else { + fsample = (float)sample / (float)MAXINT(fracbits+1); + } + return __dither_sample_float_to_float (fsample, scale, hardlimit); +} + + + + + +SAD_dither_t* SAD_dither_init(SAD_buffer_format *inbuf_format, SAD_buffer_format *outbuf_format, int *error) { + SAD_state_priv *priv; + + DEBUG_MSG("f: SAD_dither_init\n",0); + + priv = calloc(sizeof(SAD_state_priv), 1); + + /* Check buffer formats and assign buffer ops */ + SAD_buffer_ops* inops = SAD_assign_buf_ops(inbuf_format); + + if (inbuf_format->sample_format != SAD_SAMPLE_FLOAT) { + if (inops != NULL) { + priv->get_sample = inops->get_sample; + } else { + free(priv); + *error = SAD_ERROR_INCORRECT_INPUT_SAMPLEFORMAT; + return NULL; + } + } + + SAD_buffer_ops* outops = SAD_assign_buf_ops(outbuf_format); + + if (outbuf_format->sample_format != SAD_SAMPLE_FLOAT) { + if (outops != NULL) { + priv->put_sample = outops->put_sample; + } else { + free(priv); + *error = SAD_ERROR_INCORRECT_OUTPUT_SAMPLEFORMAT; + return NULL; + } + } + + priv->input_fracbits = 0; + priv->output_fracbits = 0; + priv->input_sample_format = inbuf_format->sample_format; + priv->output_sample_format = outbuf_format->sample_format; + priv->input_chorder = inbuf_format->channels_order; + priv->output_chorder = outbuf_format->channels_order; + priv->channels = inbuf_format->channels; + priv->scale = 1.0; + priv->rg_scale = 1.0; + priv->dither = TRUE; + priv->hardlimit = FALSE; + + switch(outbuf_format->sample_format){ + case SAD_SAMPLE_S8: + case SAD_SAMPLE_U8: priv->output_bits = 8; break; + case SAD_SAMPLE_S16: + case SAD_SAMPLE_U16: priv->output_bits = 16; break; + case SAD_SAMPLE_S24: + case SAD_SAMPLE_U24: priv->output_bits = 24; break; + case SAD_SAMPLE_S32: + case SAD_SAMPLE_U32: priv->output_bits = 32; break; + case SAD_SAMPLE_FLOAT: break; + default: + free(priv); + *error = SAD_ERROR_INCORRECT_OUTPUT_SAMPLEFORMAT; + return NULL; + } + + switch(inbuf_format->sample_format){ + case SAD_SAMPLE_S8: + case SAD_SAMPLE_U8: priv->input_bits = 8; break; + case SAD_SAMPLE_S16: + case SAD_SAMPLE_U16: priv->input_bits = 16; break; + case SAD_SAMPLE_S24: + case SAD_SAMPLE_U24: priv->input_bits = 24; break; + case SAD_SAMPLE_S32: + case SAD_SAMPLE_U32: priv->input_bits = 32; break; + case SAD_SAMPLE_FIXED32: priv->input_fracbits = inbuf_format->fracbits; break; + case SAD_SAMPLE_FLOAT: break; + default: + free(priv); + *error = SAD_ERROR_INCORRECT_INPUT_SAMPLEFORMAT; + return NULL; + } + + *error = SAD_ERROR_OK; + return (SAD_dither_t*)priv; +} + +int SAD_dither_free(SAD_dither_t* state) { + DEBUG_MSG("f: SAD_dither_free\n",0); + free(state); + return SAD_ERROR_OK; +} + +/* + * Depend on format->channels_order inbuf and outbuf will be treated as + * smth* or smth** if channels_order = SAD_CHORDER_INTERLEAVED or SAD_CHORDER_SEPARATED + * accordingly + * + * frame is aggregate of format->channels samples + */ + +#define GET_FLOAT_SAMPLE(b,o,n,c,i) (o == SAD_CHORDER_INTERLEAVED ? (((float*)b)[i*n+c]) : (((float**)b)[c][i])) +#define PUT_FLOAT_SAMPLE(b,o,n,c,i,s) { \ + if (o == SAD_CHORDER_INTERLEAVED) { \ + ((float*)b)[i*n+c] = s; \ + } else { \ + ((float**)b)[c][i] = s; \ + } \ + } + +int SAD_dither_process_buffer (SAD_dither_t *state, void *inbuf, void *outbuf, int frames) +{ + SAD_state_priv *priv = (SAD_state_priv*) state; + int i, ch; + int channels = priv->channels; + int inbits = priv->input_bits; + int outbits = priv->output_bits; + int fracbits = priv->input_fracbits; + float scale = priv->scale * priv->rg_scale; + int dither = priv->dither; + int hardlimit = priv->hardlimit; + SAD_channels_order input_chorder = priv->input_chorder; + SAD_channels_order output_chorder = priv->output_chorder; + + SAD_get_sample_proc get_sample = priv->get_sample; + SAD_put_sample_proc put_sample = priv->put_sample; + +#ifdef DEEP_DEBUG + printf("f: SAD_process_buffer\n"); +#endif + + if (priv->input_sample_format == SAD_SAMPLE_FLOAT) { + if (priv->output_sample_format == SAD_SAMPLE_FLOAT) { + /* process buffer */ + for(i=0; i<frames; i++) { + for(ch=0; ch<channels; ch++) { + float sample = GET_FLOAT_SAMPLE(inbuf, input_chorder, channels, ch ,i); + sample = __dither_sample_float_to_float(sample, scale, hardlimit); + PUT_FLOAT_SAMPLE(outbuf, output_chorder, channels, ch ,i, sample); + } + } + } else { + if (put_sample == NULL) return SAD_ERROR_CORRUPTED_PRIVATE_DATA; + /* process buffer */ + for(i=0; i<frames; i++) { + for(ch=0; ch<channels; ch++) { + float sample = GET_FLOAT_SAMPLE(inbuf, input_chorder, channels, ch ,i); + sad_sint32 isample = __dither_sample_float_to_int(sample, outbits, scale, dither, hardlimit); + put_sample (outbuf, isample, channels, ch, i); + } + } + } + } else { + if (priv->output_sample_format == SAD_SAMPLE_FLOAT) { + if (get_sample == NULL) return SAD_ERROR_CORRUPTED_PRIVATE_DATA; + /* process buffer */ + for(i=0; i<frames; i++) { + for(ch=0; ch<channels; ch++) { + sad_sint32 sample = get_sample (inbuf, channels, ch, i); + float fsample = __dither_sample_fixed_to_float (sample, inbits, fracbits, scale, hardlimit); + PUT_FLOAT_SAMPLE(outbuf, output_chorder, channels, ch ,i, fsample); + } + } + } else { + if (put_sample == NULL || get_sample == NULL) return SAD_ERROR_CORRUPTED_PRIVATE_DATA; + /* process buffer */ + for(i=0; i<frames; i++) { + for(ch=0; ch<channels; ch++){ + sad_sint32 sample = get_sample (inbuf, channels, ch, i); + sad_sint32 isample = __dither_sample_fixed_to_int (sample, inbits, fracbits, outbits, scale, dither, hardlimit); + put_sample (outbuf, isample, channels, ch, i); + } + } + } + } + + return SAD_ERROR_OK; +} + +int SAD_dither_apply_replaygain (SAD_dither_t *state, SAD_replaygain_info *rg_info, SAD_replaygain_mode *mode) { + SAD_state_priv *priv = (SAD_state_priv*) state; + float scale = -1.0, peak = 0.0; + + DEBUG_MSG("f: SAD_dither_apply_replaygain\n",0); + + if(!rg_info->present) { + priv->rg_scale = 1.0; + priv->hardlimit = FALSE; + return SAD_ERROR_OK; + } + + switch(mode->mode) { + case SAD_RG_ALBUM: + scale = db2scale(rg_info->album_gain); + peak = rg_info->album_peak; + if (peak == 0.0) { + scale = db2scale(rg_info->track_gain); // fallback to per-track mode + peak = rg_info->track_peak; + DEBUG_MSG("f: SAD_dither_apply_replaygain: fallback to track mode\n",0); + } + break; + case SAD_RG_TRACK: + scale = db2scale(rg_info->track_gain); + peak = rg_info->track_peak; + break; + case SAD_RG_NONE: + scale = -1.0; + } + + if (scale != -1.0 && peak != 0.0) { + DEBUG_MSG("f: SAD_dither_apply_replaygain: applying\n",0); + scale *= mode->preamp; + // Clipping prevention + if(mode->clipping_prevention) { +#ifdef DEBUG + if(scale * peak > 1.0) DEBUG_MSG("f: SAD_dither_apply_replaygain: clipping prevented\n",0); +#endif + scale = scale * peak > 1.0 ? 1.0 / peak : scale; + } + scale = scale > 15.0 ? 15.0 : scale; // safety + priv->rg_scale = scale; + priv->hardlimit = mode->hard_limit; // apply settings + } else { + priv->rg_scale = 1.0; + priv->hardlimit = FALSE; + } + + return SAD_ERROR_OK; +} + +int SAD_dither_set_scale (SAD_dither_t *state, float scale) { + SAD_state_priv *priv = (SAD_state_priv*) state; + priv->scale = scale; + return SAD_ERROR_OK; +} + +int SAD_dither_set_dither (SAD_dither_t *state, int dither) { + SAD_state_priv *priv = (SAD_state_priv*) state; + priv->dither = dither; + return SAD_ERROR_OK; +}