diff options
Diffstat (limited to 'lib/rbcodec/dsp/dsp.c')
-rw-r--r-- | lib/rbcodec/dsp/dsp.c | 1573 |
1 files changed, 1573 insertions, 0 deletions
diff --git a/lib/rbcodec/dsp/dsp.c b/lib/rbcodec/dsp/dsp.c new file mode 100644 index 0000000000..4da555747b --- /dev/null +++ b/lib/rbcodec/dsp/dsp.c @@ -0,0 +1,1573 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Miika Pekkarinen + * + * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "config.h" +#include "system.h" +#include <sound.h> +#include "dsp.h" +#include "dsp-util.h" +#include "eq.h" +#include "compressor.h" +#include "kernel.h" +#include "settings.h" +#include "replaygain.h" +#include "tdspeed.h" +#include "core_alloc.h" +#include "fixedpoint.h" +#include "fracmul.h" + +/* Define LOGF_ENABLE to enable logf output in this file */ +/*#define LOGF_ENABLE*/ +#include "logf.h" + +/* 16-bit samples are scaled based on these constants. The shift should be + * no more than 15. + */ +#define WORD_SHIFT 12 +#define WORD_FRACBITS 27 + +#define NATIVE_DEPTH 16 +#define SMALL_SAMPLE_BUF_COUNT 128 /* Per channel */ +#define DEFAULT_GAIN 0x01000000 + +/* enums to index conversion properly with stereo mode and other settings */ +enum +{ + SAMPLE_INPUT_LE_NATIVE_I_STEREO = STEREO_INTERLEAVED, + SAMPLE_INPUT_LE_NATIVE_NI_STEREO = STEREO_NONINTERLEAVED, + SAMPLE_INPUT_LE_NATIVE_MONO = STEREO_MONO, + SAMPLE_INPUT_GT_NATIVE_I_STEREO = STEREO_INTERLEAVED + STEREO_NUM_MODES, + SAMPLE_INPUT_GT_NATIVE_NI_STEREO = STEREO_NONINTERLEAVED + STEREO_NUM_MODES, + SAMPLE_INPUT_GT_NATIVE_MONO = STEREO_MONO + STEREO_NUM_MODES, + SAMPLE_INPUT_GT_NATIVE_1ST_INDEX = STEREO_NUM_MODES +}; + +enum +{ + SAMPLE_OUTPUT_MONO = 0, + SAMPLE_OUTPUT_STEREO, + SAMPLE_OUTPUT_DITHERED_MONO, + SAMPLE_OUTPUT_DITHERED_STEREO +}; + +/* No asm...yet */ +struct dither_data +{ + long error[3]; /* 00h */ + long random; /* 0ch */ + /* 10h */ +}; + +struct crossfeed_data +{ + int32_t gain; /* 00h - Direct path gain */ + int32_t coefs[3]; /* 04h - Coefficients for the shelving filter */ + int32_t history[4]; /* 10h - Format is x[n - 1], y[n - 1] for both channels */ + int32_t delay[13][2]; /* 20h */ + int32_t *index; /* 88h - Current pointer into the delay line */ + /* 8ch */ +}; + +/* Current setup is one lowshelf filters three peaking filters and one + * highshelf filter. Varying the number of shelving filters make no sense, + * but adding peaking filters is possible. + */ +struct eq_state +{ + char enabled[5]; /* 00h - Flags for active filters */ + struct eqfilter filters[5]; /* 08h - packing is 4? */ + /* 10ch */ +}; + +/* Include header with defines which functions are implemented in assembly + code for the target */ +#include <dsp_asm.h> + +/* Typedefs keep things much neater in this case */ +typedef void (*sample_input_fn_type)(int count, const char *src[], + int32_t *dst[]); +typedef int (*resample_fn_type)(int count, struct dsp_data *data, + const int32_t *src[], int32_t *dst[]); +typedef void (*sample_output_fn_type)(int count, struct dsp_data *data, + const int32_t *src[], int16_t *dst); + +/* Single-DSP channel processing in place */ +typedef void (*channels_process_fn_type)(int count, int32_t *buf[]); +/* DSP local channel processing in place */ +typedef void (*channels_process_dsp_fn_type)(int count, struct dsp_data *data, + int32_t *buf[]); + +/* + ***************************************************************************/ + +struct dsp_config +{ + struct dsp_data data; /* Config members for use in external routines */ + long codec_frequency; /* Sample rate of data coming from the codec */ + long frequency; /* Effective sample rate after pitch shift (if any) */ + int sample_depth; + int sample_bytes; + int stereo_mode; + int32_t tdspeed_percent; /* Speed% * PITCH_SPEED_PRECISION */ +#ifdef HAVE_PITCHSCREEN + bool tdspeed_active; /* Timestretch is in use */ +#endif +#ifdef HAVE_SW_TONE_CONTROLS + /* Filter struct for software bass/treble controls */ + struct eqfilter tone_filter; +#endif + /* Functions that change depending upon settings - NULL if stage is + disabled */ + sample_input_fn_type input_samples; + resample_fn_type resample; + sample_output_fn_type output_samples; + /* These will be NULL for the voice codec and is more economical that + way */ + channels_process_dsp_fn_type apply_gain; + channels_process_fn_type apply_crossfeed; + channels_process_fn_type eq_process; + channels_process_fn_type channels_process; + channels_process_dsp_fn_type compressor_process; +}; + +/* General DSP config */ +static struct dsp_config dsp_conf[2] IBSS_ATTR; /* 0=A, 1=V */ +/* Dithering */ +static struct dither_data dither_data[2] IBSS_ATTR; /* 0=left, 1=right */ +static long dither_mask IBSS_ATTR; +static long dither_bias IBSS_ATTR; +/* Crossfeed */ +struct crossfeed_data crossfeed_data IDATA_ATTR = /* A */ +{ + .index = (int32_t *)crossfeed_data.delay +}; + +/* Equalizer */ +static struct eq_state eq_data; /* A */ + +/* Software tone controls */ +#ifdef HAVE_SW_TONE_CONTROLS +static int prescale; /* A/V */ +static int bass; /* A/V */ +static int treble; /* A/V */ +#endif + +/* Settings applicable to audio codec only */ +#ifdef HAVE_PITCHSCREEN +static int32_t pitch_ratio = PITCH_SPEED_100; +static int big_sample_locks; +#endif +static int channels_mode; + long dsp_sw_gain; + long dsp_sw_cross; +static bool dither_enabled; +static long eq_precut; +static long track_gain; +static bool new_gain; +static long album_gain; +static long track_peak; +static long album_peak; +static long replaygain; +static bool crossfeed_enabled; + +#define AUDIO_DSP (dsp_conf[CODEC_IDX_AUDIO]) +#define VOICE_DSP (dsp_conf[CODEC_IDX_VOICE]) + +/* The internal format is 32-bit samples, non-interleaved, stereo. This + * format is similar to the raw output from several codecs, so the amount + * of copying needed is minimized for that case. + */ + +#define RESAMPLE_RATIO 4 /* Enough for 11,025 Hz -> 44,100 Hz */ +#define SMALL_RESAMPLE_BUF_COUNT (SMALL_SAMPLE_BUF_COUNT * RESAMPLE_RATIO) +#define BIG_SAMPLE_BUF_COUNT SMALL_RESAMPLE_BUF_COUNT +#define BIG_RESAMPLE_BUF_COUNT (BIG_SAMPLE_BUF_COUNT * RESAMPLE_RATIO) + +static int32_t small_sample_buf[2][SMALL_SAMPLE_BUF_COUNT] IBSS_ATTR; +static int32_t small_resample_buf[2][SMALL_RESAMPLE_BUF_COUNT] IBSS_ATTR; + +#ifdef HAVE_PITCHSCREEN +static int32_t (* big_sample_buf)[BIG_SAMPLE_BUF_COUNT] = NULL; +static int32_t (* big_resample_buf)[BIG_RESAMPLE_BUF_COUNT] = NULL; +#endif + +static int sample_buf_count = SMALL_SAMPLE_BUF_COUNT; +static int32_t *sample_buf[2] = { small_sample_buf[0], small_sample_buf[1] }; +static int resample_buf_count = SMALL_RESAMPLE_BUF_COUNT; +static int32_t *resample_buf[2] = { small_resample_buf[0], small_resample_buf[1] }; + +#ifdef HAVE_PITCHSCREEN +int32_t sound_get_pitch(void) +{ + return pitch_ratio; +} + +void sound_set_pitch(int32_t percent) +{ + pitch_ratio = percent; + dsp_configure(&AUDIO_DSP, DSP_SWITCH_FREQUENCY, + AUDIO_DSP.codec_frequency); +} + +static void tdspeed_set_pointers( bool time_stretch_active ) +{ + if( time_stretch_active ) + { + sample_buf_count = BIG_SAMPLE_BUF_COUNT; + resample_buf_count = BIG_RESAMPLE_BUF_COUNT; + sample_buf[0] = big_sample_buf[0]; + sample_buf[1] = big_sample_buf[1]; + resample_buf[0] = big_resample_buf[0]; + resample_buf[1] = big_resample_buf[1]; + } + else + { + sample_buf_count = SMALL_SAMPLE_BUF_COUNT; + resample_buf_count = SMALL_RESAMPLE_BUF_COUNT; + sample_buf[0] = small_sample_buf[0]; + sample_buf[1] = small_sample_buf[1]; + resample_buf[0] = small_resample_buf[0]; + resample_buf[1] = small_resample_buf[1]; + } +} + +static void tdspeed_setup(struct dsp_config *dspc) +{ + /* Assume timestretch will not be used */ + dspc->tdspeed_active = false; + + tdspeed_set_pointers( false ); + + if (!dsp_timestretch_available()) + return; /* Timestretch not enabled or buffer not allocated */ + + if (dspc->tdspeed_percent == 0) + dspc->tdspeed_percent = PITCH_SPEED_100; + + if (!tdspeed_config( + dspc->codec_frequency == 0 ? NATIVE_FREQUENCY : dspc->codec_frequency, + dspc->stereo_mode != STEREO_MONO, + dspc->tdspeed_percent)) + return; /* Timestretch not possible or needed with these parameters */ + + /* Timestretch is to be used */ + dspc->tdspeed_active = true; + + tdspeed_set_pointers( true ); +} + + +static int move_callback(int handle, void* current, void* new) +{ + (void)handle;(void)current; + + if ( big_sample_locks > 0 ) + return BUFLIB_CB_CANNOT_MOVE; + + big_sample_buf = new; + + /* no allocation without timestretch enabled */ + tdspeed_set_pointers( true ); + return BUFLIB_CB_OK; +} + +static void lock_sample_buf( bool lock ) +{ + if ( lock ) + big_sample_locks++; + else + big_sample_locks--; +} + +static struct buflib_callbacks ops = { + .move_callback = move_callback, + .shrink_callback = NULL, +}; + + +void dsp_timestretch_enable(bool enabled) +{ + /* Hook to set up timestretch buffer on first call to settings_apply() */ + static int handle = -1; + if (enabled) + { + if (big_sample_buf) + return; /* already allocated and enabled */ + + /* Set up timestretch buffers */ + big_sample_buf = &small_resample_buf[0]; + handle = core_alloc_ex("resample buf", + 2 * BIG_RESAMPLE_BUF_COUNT * sizeof(int32_t), + &ops); + big_sample_locks = 0; + enabled = handle >= 0; + + if (enabled) + { + /* success, now setup tdspeed */ + big_resample_buf = core_get_data(handle); + + tdspeed_init(); + tdspeed_setup(&AUDIO_DSP); + } + } + + if (!enabled) + { + dsp_set_timestretch(PITCH_SPEED_100); + tdspeed_finish(); + + if (handle >= 0) + core_free(handle); + + handle = -1; + big_sample_buf = NULL; + } +} + +void dsp_set_timestretch(int32_t percent) +{ + AUDIO_DSP.tdspeed_percent = percent; + tdspeed_setup(&AUDIO_DSP); +} + +int32_t dsp_get_timestretch() +{ + return AUDIO_DSP.tdspeed_percent; +} + +bool dsp_timestretch_available() +{ + return (global_settings.timestretch_enabled && big_sample_buf); +} +#endif /* HAVE_PITCHSCREEN */ + +/* Convert count samples to the internal format, if needed. Updates src + * to point past the samples "consumed" and dst is set to point to the + * samples to consume. Note that for mono, dst[0] equals dst[1], as there + * is no point in processing the same data twice. + */ + +/* convert count 16-bit mono to 32-bit mono */ +static void sample_input_lte_native_mono( + int count, const char *src[], int32_t *dst[]) +{ + const int16_t *s = (int16_t *) src[0]; + const int16_t * const send = s + count; + int32_t *d = dst[0] = dst[1] = sample_buf[0]; + int scale = WORD_SHIFT; + + while (s < send) + { + *d++ = *s++ << scale; + } + + src[0] = (char *)s; +} + +/* convert count 16-bit interleaved stereo to 32-bit noninterleaved */ +static void sample_input_lte_native_i_stereo( + int count, const char *src[], int32_t *dst[]) +{ + const int32_t *s = (int32_t *) src[0]; + const int32_t * const send = s + count; + int32_t *dl = dst[0] = sample_buf[0]; + int32_t *dr = dst[1] = sample_buf[1]; + int scale = WORD_SHIFT; + + while (s < send) + { + int32_t slr = *s++; +#ifdef ROCKBOX_LITTLE_ENDIAN + *dl++ = (slr >> 16) << scale; + *dr++ = (int32_t)(int16_t)slr << scale; +#else /* ROCKBOX_BIG_ENDIAN */ + *dl++ = (int32_t)(int16_t)slr << scale; + *dr++ = (slr >> 16) << scale; +#endif + } + + src[0] = (char *)s; +} + +/* convert count 16-bit noninterleaved stereo to 32-bit noninterleaved */ +static void sample_input_lte_native_ni_stereo( + int count, const char *src[], int32_t *dst[]) +{ + const int16_t *sl = (int16_t *) src[0]; + const int16_t *sr = (int16_t *) src[1]; + const int16_t * const slend = sl + count; + int32_t *dl = dst[0] = sample_buf[0]; + int32_t *dr = dst[1] = sample_buf[1]; + int scale = WORD_SHIFT; + + while (sl < slend) + { + *dl++ = *sl++ << scale; + *dr++ = *sr++ << scale; + } + + src[0] = (char *)sl; + src[1] = (char *)sr; +} + +/* convert count 32-bit mono to 32-bit mono */ +static void sample_input_gt_native_mono( + int count, const char *src[], int32_t *dst[]) +{ + dst[0] = dst[1] = (int32_t *)src[0]; + src[0] = (char *)(dst[0] + count); +} + +/* convert count 32-bit interleaved stereo to 32-bit noninterleaved stereo */ +static void sample_input_gt_native_i_stereo( + int count, const char *src[], int32_t *dst[]) +{ + const int32_t *s = (int32_t *)src[0]; + const int32_t * const send = s + 2*count; + int32_t *dl = dst[0] = sample_buf[0]; + int32_t *dr = dst[1] = sample_buf[1]; + + while (s < send) + { + *dl++ = *s++; + *dr++ = *s++; + } + + src[0] = (char *)send; +} + +/* convert 32 bit-noninterleaved stereo to 32-bit noninterleaved stereo */ +static void sample_input_gt_native_ni_stereo( + int count, const char *src[], int32_t *dst[]) +{ + dst[0] = (int32_t *)src[0]; + dst[1] = (int32_t *)src[1]; + src[0] = (char *)(dst[0] + count); + src[1] = (char *)(dst[1] + count); +} + +/** + * sample_input_new_format() + * + * set the to-native sample conversion function based on dsp sample parameters + * + * !DSPPARAMSYNC + * needs syncing with changes to the following dsp parameters: + * * dsp->stereo_mode (A/V) + * * dsp->sample_depth (A/V) + */ +static void sample_input_new_format(struct dsp_config *dsp) +{ + static const sample_input_fn_type sample_input_functions[] = + { + [SAMPLE_INPUT_LE_NATIVE_I_STEREO] = sample_input_lte_native_i_stereo, + [SAMPLE_INPUT_LE_NATIVE_NI_STEREO] = sample_input_lte_native_ni_stereo, + [SAMPLE_INPUT_LE_NATIVE_MONO] = sample_input_lte_native_mono, + [SAMPLE_INPUT_GT_NATIVE_I_STEREO] = sample_input_gt_native_i_stereo, + [SAMPLE_INPUT_GT_NATIVE_NI_STEREO] = sample_input_gt_native_ni_stereo, + [SAMPLE_INPUT_GT_NATIVE_MONO] = sample_input_gt_native_mono, + }; + + int convert = dsp->stereo_mode; + + if (dsp->sample_depth > NATIVE_DEPTH) + convert += SAMPLE_INPUT_GT_NATIVE_1ST_INDEX; + + dsp->input_samples = sample_input_functions[convert]; +} + + +#ifndef DSP_HAVE_ASM_SAMPLE_OUTPUT_MONO +/* write mono internal format to output format */ +static void sample_output_mono(int count, struct dsp_data *data, + const int32_t *src[], int16_t *dst) +{ + const int32_t *s0 = src[0]; + const int scale = data->output_scale; + const int dc_bias = 1 << (scale - 1); + + while (count-- > 0) + { + int32_t lr = clip_sample_16((*s0++ + dc_bias) >> scale); + *dst++ = lr; + *dst++ = lr; + } +} +#endif /* DSP_HAVE_ASM_SAMPLE_OUTPUT_MONO */ + +/* write stereo internal format to output format */ +#ifndef DSP_HAVE_ASM_SAMPLE_OUTPUT_STEREO +static void sample_output_stereo(int count, struct dsp_data *data, + const int32_t *src[], int16_t *dst) +{ + const int32_t *s0 = src[0]; + const int32_t *s1 = src[1]; + const int scale = data->output_scale; + const int dc_bias = 1 << (scale - 1); + + while (count-- > 0) + { + *dst++ = clip_sample_16((*s0++ + dc_bias) >> scale); + *dst++ = clip_sample_16((*s1++ + dc_bias) >> scale); + } +} +#endif /* DSP_HAVE_ASM_SAMPLE_OUTPUT_STEREO */ + +/** + * The "dither" code to convert the 24-bit samples produced by libmad was + * taken from the coolplayer project - coolplayer.sourceforge.net + * + * This function handles mono and stereo outputs. + */ +static void sample_output_dithered(int count, struct dsp_data *data, + const int32_t *src[], int16_t *dst) +{ + const int32_t mask = dither_mask; + const int32_t bias = dither_bias; + const int scale = data->output_scale; + const int32_t min = data->clip_min; + const int32_t max = data->clip_max; + const int32_t range = max - min; + int ch; + int16_t *d; + + for (ch = 0; ch < data->num_channels; ch++) + { + struct dither_data * const dither = &dither_data[ch]; + const int32_t *s = src[ch]; + int i; + + for (i = 0, d = &dst[ch]; i < count; i++, s++, d += 2) + { + int32_t output, sample; + int32_t random; + + /* Noise shape and bias (for correct rounding later) */ + sample = *s; + sample += dither->error[0] - dither->error[1] + dither->error[2]; + dither->error[2] = dither->error[1]; + dither->error[1] = dither->error[0]/2; + + output = sample + bias; + + /* Dither, highpass triangle PDF */ + random = dither->random*0x0019660dL + 0x3c6ef35fL; + output += (random & mask) - (dither->random & mask); + dither->random = random; + + /* Round sample to output range */ + output &= ~mask; + + /* Error feedback */ + dither->error[0] = sample - output; + + /* Clip */ + if ((uint32_t)(output - min) > (uint32_t)range) + { + int32_t c = min; + if (output > min) + c += range; + output = c; + } + + /* Quantize and store */ + *d = output >> scale; + } + } + + if (data->num_channels == 2) + return; + + /* Have to duplicate left samples into the right channel since + pcm buffer and hardware is interleaved stereo */ + d = &dst[0]; + + while (count-- > 0) + { + int16_t s = *d++; + *d++ = s; + } +} + +/** + * sample_output_new_format() + * + * set the from-native to ouput sample conversion routine + * + * !DSPPARAMSYNC + * needs syncing with changes to the following dsp parameters: + * * dsp->stereo_mode (A/V) + * * dither_enabled (A) + */ +static void sample_output_new_format(struct dsp_config *dsp) +{ + static const sample_output_fn_type sample_output_functions[] = + { + sample_output_mono, + sample_output_stereo, + sample_output_dithered, + sample_output_dithered + }; + + int out = dsp->data.num_channels - 1; + + if (dsp == &AUDIO_DSP && dither_enabled) + out += 2; + + dsp->output_samples = sample_output_functions[out]; +} + +/** + * Linear interpolation resampling that introduces a one sample delay because + * of our inability to look into the future at the end of a frame. + */ +#ifndef DSP_HAVE_ASM_RESAMPLING +static int dsp_downsample(int count, struct dsp_data *data, + const int32_t *src[], int32_t *dst[]) +{ + int ch = data->num_channels - 1; + uint32_t delta = data->resample_data.delta; + uint32_t phase, pos; + int32_t *d; + + /* Rolled channel loop actually showed slightly faster. */ + do + { + /* Just initialize things and not worry too much about the relatively + * uncommon case of not being able to spit out a sample for the frame. + */ + const int32_t *s = src[ch]; + int32_t last = data->resample_data.last_sample[ch]; + + data->resample_data.last_sample[ch] = s[count - 1]; + d = dst[ch]; + phase = data->resample_data.phase; + pos = phase >> 16; + + /* Do we need last sample of previous frame for interpolation? */ + if (pos > 0) + last = s[pos - 1]; + + while (pos < (uint32_t)count) + { + *d++ = last + FRACMUL((phase & 0xffff) << 15, s[pos] - last); + phase += delta; + pos = phase >> 16; + last = s[pos - 1]; + } + } + while (--ch >= 0); + + /* Wrap phase accumulator back to start of next frame. */ + data->resample_data.phase = phase - (count << 16); + return d - dst[0]; +} + +static int dsp_upsample(int count, struct dsp_data *data, + const int32_t *src[], int32_t *dst[]) +{ + int ch = data->num_channels - 1; + uint32_t delta = data->resample_data.delta; + uint32_t phase, pos; + int32_t *d; + + /* Rolled channel loop actually showed slightly faster. */ + do + { + /* Should always be able to output a sample for a ratio up to RESAMPLE_RATIO */ + const int32_t *s = src[ch]; + int32_t last = data->resample_data.last_sample[ch]; + + data->resample_data.last_sample[ch] = s[count - 1]; + d = dst[ch]; + phase = data->resample_data.phase; + pos = phase >> 16; + + while (pos == 0) + { + *d++ = last + FRACMUL((phase & 0xffff) << 15, s[0] - last); + phase += delta; + pos = phase >> 16; + } + + while (pos < (uint32_t)count) + { + last = s[pos - 1]; + *d++ = last + FRACMUL((phase & 0xffff) << 15, s[pos] - last); + phase += delta; + pos = phase >> 16; + } + } + while (--ch >= 0); + + /* Wrap phase accumulator back to start of next frame. */ + data->resample_data.phase = phase & 0xffff; + return d - dst[0]; +} +#endif /* DSP_HAVE_ASM_RESAMPLING */ + +static void resampler_new_delta(struct dsp_config *dsp) +{ + dsp->data.resample_data.delta = (unsigned long) + dsp->frequency * 65536LL / NATIVE_FREQUENCY; + + if (dsp->frequency == NATIVE_FREQUENCY) + { + /* NOTE: If fully glitch-free transistions from no resampling to + resampling are desired, last_sample history should be maintained + even when not resampling. */ + dsp->resample = NULL; + dsp->data.resample_data.phase = 0; + dsp->data.resample_data.last_sample[0] = 0; + dsp->data.resample_data.last_sample[1] = 0; + } + else if (dsp->frequency < NATIVE_FREQUENCY) + dsp->resample = dsp_upsample; + else + dsp->resample = dsp_downsample; +} + +/* Resample count stereo samples. Updates the src array, if resampling is + * done, to refer to the resampled data. Returns number of stereo samples + * for further processing. + */ +static inline int resample(struct dsp_config *dsp, int count, int32_t *src[]) +{ + int32_t *dst[2] = + { + resample_buf[0], + resample_buf[1] + }; + lock_sample_buf( true ); + count = dsp->resample(count, &dsp->data, (const int32_t **)src, dst); + + src[0] = dst[0]; + src[1] = dst[dsp->data.num_channels - 1]; + lock_sample_buf( false ); + return count; +} + +static void dither_init(struct dsp_config *dsp) +{ + memset(dither_data, 0, sizeof (dither_data)); + dither_bias = (1L << (dsp->data.frac_bits - NATIVE_DEPTH)); + dither_mask = (1L << (dsp->data.frac_bits + 1 - NATIVE_DEPTH)) - 1; +} + +void dsp_dither_enable(bool enable) +{ + struct dsp_config *dsp = &AUDIO_DSP; + dither_enabled = enable; + sample_output_new_format(dsp); +} + +/* Applies crossfeed to the stereo signal in src. + * Crossfeed is a process where listening over speakers is simulated. This + * is good for old hard panned stereo records, which might be quite fatiguing + * to listen to on headphones with no crossfeed. + */ +#ifndef DSP_HAVE_ASM_CROSSFEED +static void apply_crossfeed(int count, int32_t *buf[]) +{ + int32_t *hist_l = &crossfeed_data.history[0]; + int32_t *hist_r = &crossfeed_data.history[2]; + int32_t *delay = &crossfeed_data.delay[0][0]; + int32_t *coefs = &crossfeed_data.coefs[0]; + int32_t gain = crossfeed_data.gain; + int32_t *di = crossfeed_data.index; + + int32_t acc; + int32_t left, right; + int i; + + for (i = 0; i < count; i++) + { + left = buf[0][i]; + right = buf[1][i]; + + /* Filter delayed sample from left speaker */ + acc = FRACMUL(*di, coefs[0]); + acc += FRACMUL(hist_l[0], coefs[1]); + acc += FRACMUL(hist_l[1], coefs[2]); + /* Save filter history for left speaker */ + hist_l[1] = acc; + hist_l[0] = *di; + *di++ = left; + /* Filter delayed sample from right speaker */ + acc = FRACMUL(*di, coefs[0]); + acc += FRACMUL(hist_r[0], coefs[1]); + acc += FRACMUL(hist_r[1], coefs[2]); + /* Save filter history for right speaker */ + hist_r[1] = acc; + hist_r[0] = *di; + *di++ = right; + /* Now add the attenuated direct sound and write to outputs */ + buf[0][i] = FRACMUL(left, gain) + hist_r[1]; + buf[1][i] = FRACMUL(right, gain) + hist_l[1]; + + /* Wrap delay line index if bigger than delay line size */ + if (di >= delay + 13*2) + di = delay; + } + /* Write back local copies of data we've modified */ + crossfeed_data.index = di; +} +#endif /* DSP_HAVE_ASM_CROSSFEED */ + +/** + * dsp_set_crossfeed(bool enable) + * + * !DSPPARAMSYNC + * needs syncing with changes to the following dsp parameters: + * * dsp->stereo_mode (A) + */ +void dsp_set_crossfeed(bool enable) +{ + crossfeed_enabled = enable; + AUDIO_DSP.apply_crossfeed = (enable && AUDIO_DSP.data.num_channels > 1) + ? apply_crossfeed : NULL; +} + +void dsp_set_crossfeed_direct_gain(int gain) +{ + crossfeed_data.gain = get_replaygain_int(gain * 10) << 7; + /* If gain is negative, the calculation overflowed and we need to clamp */ + if (crossfeed_data.gain < 0) + crossfeed_data.gain = 0x7fffffff; +} + +/* Both gains should be below 0 dB */ +void dsp_set_crossfeed_cross_params(long lf_gain, long hf_gain, long cutoff) +{ + int32_t *c = crossfeed_data.coefs; + long scaler = get_replaygain_int(lf_gain * 10) << 7; + + cutoff = 0xffffffff/NATIVE_FREQUENCY*cutoff; + hf_gain -= lf_gain; + /* Divide cutoff by sqrt(10^(hf_gain/20)) to place cutoff at the -3 dB + * point instead of shelf midpoint. This is for compatibility with the old + * crossfeed shelf filter and should be removed if crossfeed settings are + * ever made incompatible for any other good reason. + */ + cutoff = fp_div(cutoff, get_replaygain_int(hf_gain*5), 24); + filter_shelf_coefs(cutoff, hf_gain, false, c); + /* Scale coefs by LF gain and shift them to s0.31 format. We have no gains + * over 1 and can do this safely + */ + c[0] = FRACMUL_SHL(c[0], scaler, 4); + c[1] = FRACMUL_SHL(c[1], scaler, 4); + c[2] <<= 4; +} + +/* Apply a constant gain to the samples (e.g., for ReplayGain). + * Note that this must be called before the resampler. + */ +#ifndef DSP_HAVE_ASM_APPLY_GAIN +static void dsp_apply_gain(int count, struct dsp_data *data, int32_t *buf[]) +{ + const int32_t gain = data->gain; + int ch; + + for (ch = 0; ch < data->num_channels; ch++) + { + int32_t *d = buf[ch]; + int i; + + for (i = 0; i < count; i++) + d[i] = FRACMUL_SHL(d[i], gain, 8); + } +} +#endif /* DSP_HAVE_ASM_APPLY_GAIN */ + +/* Combine all gains to a global gain. */ +static void set_gain(struct dsp_config *dsp) +{ + /* gains are in S7.24 format */ + dsp->data.gain = DEFAULT_GAIN; + + /* Replay gain not relevant to voice */ + if (dsp == &AUDIO_DSP && replaygain) + { + dsp->data.gain = replaygain; + } + + if (dsp->eq_process && eq_precut) + { + dsp->data.gain = fp_mul(dsp->data.gain, eq_precut, 24); + } + +#ifdef HAVE_SW_VOLUME_CONTROL + if (global_settings.volume < SW_VOLUME_MAX || + global_settings.volume > SW_VOLUME_MIN) + { + int vol_gain = get_replaygain_int(global_settings.volume * 100); + dsp->data.gain = (long) (((int64_t) dsp->data.gain * vol_gain) >> 24); + } +#endif + + if (dsp->data.gain == DEFAULT_GAIN) + { + dsp->data.gain = 0; + } + else + { + dsp->data.gain >>= 1; /* convert gain to S8.23 format */ + } + + dsp->apply_gain = dsp->data.gain != 0 ? dsp_apply_gain : NULL; +} + +/** + * Update the amount to cut the audio before applying the equalizer. + * + * @param precut to apply in decibels (multiplied by 10) + */ +void dsp_set_eq_precut(int precut) +{ + eq_precut = get_replaygain_int(precut * -10); + set_gain(&AUDIO_DSP); +} + +/** + * Synchronize the equalizer filter coefficients with the global settings. + * + * @param band the equalizer band to synchronize + */ +void dsp_set_eq_coefs(int band) +{ + /* Adjust setting pointer to the band we actually want to change */ + struct eq_band_setting *setting = &global_settings.eq_band_settings[band]; + + /* Convert user settings to format required by coef generator functions */ + unsigned long cutoff = 0xffffffff / NATIVE_FREQUENCY * setting->cutoff; + unsigned long q = setting->q; + int gain = setting->gain; + + if (q == 0) + q = 1; + + /* NOTE: The coef functions assume the EMAC unit is in fractional mode, + which it should be, since we're executed from the main thread. */ + + /* Assume a band is disabled if the gain is zero */ + if (gain == 0) + { + eq_data.enabled[band] = 0; + } + else + { + if (band == 0) + eq_ls_coefs(cutoff, q, gain, eq_data.filters[band].coefs); + else if (band == 4) + eq_hs_coefs(cutoff, q, gain, eq_data.filters[band].coefs); + else + eq_pk_coefs(cutoff, q, gain, eq_data.filters[band].coefs); + + eq_data.enabled[band] = 1; + } +} + +/* Apply EQ filters to those bands that have got it switched on. */ +static void eq_process(int count, int32_t *buf[]) +{ + static const int shifts[] = + { + EQ_SHELF_SHIFT, /* low shelf */ + EQ_PEAK_SHIFT, /* peaking */ + EQ_PEAK_SHIFT, /* peaking */ + EQ_PEAK_SHIFT, /* peaking */ + EQ_SHELF_SHIFT, /* high shelf */ + }; + unsigned int channels = AUDIO_DSP.data.num_channels; + int i; + + /* filter configuration currently is 1 low shelf filter, 3 band peaking + filters and 1 high shelf filter, in that order. we need to know this + so we can choose the correct shift factor. + */ + for (i = 0; i < 5; i++) + { + if (!eq_data.enabled[i]) + continue; + eq_filter(buf, &eq_data.filters[i], count, channels, shifts[i]); + } +} + +/** + * Use to enable the equalizer. + * + * @param enable true to enable the equalizer + */ +void dsp_set_eq(bool enable) +{ + AUDIO_DSP.eq_process = enable ? eq_process : NULL; + set_gain(&AUDIO_DSP); +} + +static void dsp_set_stereo_width(int value) +{ + long width, straight, cross; + + width = value * 0x7fffff / 100; + + if (value <= 100) + { + straight = (0x7fffff + width) / 2; + cross = straight - width; + } + else + { + /* straight = (1 + width) / (2 * width) */ + straight = ((int64_t)(0x7fffff + width) << 22) / width; + cross = straight - 0x7fffff; + } + + dsp_sw_gain = straight << 8; + dsp_sw_cross = cross << 8; +} + +/** + * Implements the different channel configurations and stereo width. + */ + +/* SOUND_CHAN_STEREO mode is a noop so has no function - just outline one for + * completeness. */ +#if 0 +static void channels_process_sound_chan_stereo(int count, int32_t *buf[]) +{ + /* The channels are each just themselves */ + (void)count; (void)buf; +} +#endif + +#ifndef DSP_HAVE_ASM_SOUND_CHAN_MONO +static void channels_process_sound_chan_mono(int count, int32_t *buf[]) +{ + int32_t *sl = buf[0], *sr = buf[1]; + + while (count-- > 0) + { + int32_t lr = *sl/2 + *sr/2; + *sl++ = lr; + *sr++ = lr; + } +} +#endif /* DSP_HAVE_ASM_SOUND_CHAN_MONO */ + +#ifndef DSP_HAVE_ASM_SOUND_CHAN_CUSTOM +static void channels_process_sound_chan_custom(int count, int32_t *buf[]) +{ + const int32_t gain = dsp_sw_gain; + const int32_t cross = dsp_sw_cross; + int32_t *sl = buf[0], *sr = buf[1]; + + while (count-- > 0) + { + int32_t l = *sl; + int32_t r = *sr; + *sl++ = FRACMUL(l, gain) + FRACMUL(r, cross); + *sr++ = FRACMUL(r, gain) + FRACMUL(l, cross); + } +} +#endif /* DSP_HAVE_ASM_SOUND_CHAN_CUSTOM */ + +static void channels_process_sound_chan_mono_left(int count, int32_t *buf[]) +{ + /* Just copy over the other channel */ + memcpy(buf[1], buf[0], count * sizeof (*buf)); +} + +static void channels_process_sound_chan_mono_right(int count, int32_t *buf[]) +{ + /* Just copy over the other channel */ + memcpy(buf[0], buf[1], count * sizeof (*buf)); +} + +#ifndef DSP_HAVE_ASM_SOUND_CHAN_KARAOKE +static void channels_process_sound_chan_karaoke(int count, int32_t *buf[]) +{ + int32_t *sl = buf[0], *sr = buf[1]; + + while (count-- > 0) + { + int32_t ch = *sl/2 - *sr/2; + *sl++ = ch; + *sr++ = -ch; + } +} +#endif /* DSP_HAVE_ASM_SOUND_CHAN_KARAOKE */ + +static void dsp_set_channel_config(int value) +{ + static const channels_process_fn_type channels_process_functions[] = + { + /* SOUND_CHAN_STEREO = All-purpose index for no channel processing */ + [SOUND_CHAN_STEREO] = NULL, + [SOUND_CHAN_MONO] = channels_process_sound_chan_mono, + [SOUND_CHAN_CUSTOM] = channels_process_sound_chan_custom, + [SOUND_CHAN_MONO_LEFT] = channels_process_sound_chan_mono_left, + [SOUND_CHAN_MONO_RIGHT] = channels_process_sound_chan_mono_right, + [SOUND_CHAN_KARAOKE] = channels_process_sound_chan_karaoke, + }; + + if ((unsigned)value >= ARRAYLEN(channels_process_functions) || + AUDIO_DSP.stereo_mode == STEREO_MONO) + { + value = SOUND_CHAN_STEREO; + } + + /* This doesn't apply to voice */ + channels_mode = value; + AUDIO_DSP.channels_process = channels_process_functions[value]; +} + +#if CONFIG_CODEC == SWCODEC + +#ifdef HAVE_SW_TONE_CONTROLS +static void set_tone_controls(void) +{ + filter_bishelf_coefs(0xffffffff/NATIVE_FREQUENCY*200, + 0xffffffff/NATIVE_FREQUENCY*3500, + bass, treble, -prescale, + AUDIO_DSP.tone_filter.coefs); + /* Sync the voice dsp coefficients */ + memcpy(&VOICE_DSP.tone_filter.coefs, AUDIO_DSP.tone_filter.coefs, + sizeof (VOICE_DSP.tone_filter.coefs)); +} +#endif + +/* Hook back from firmware/ part of audio, which can't/shouldn't call apps/ + * code directly. + */ +int dsp_callback(int msg, intptr_t param) +{ + switch (msg) + { +#ifdef HAVE_SW_TONE_CONTROLS + case DSP_CALLBACK_SET_PRESCALE: + prescale = param; + set_tone_controls(); + break; + /* prescaler is always set after calling any of these, so we wait with + * calculating coefs until the above case is hit. + */ + case DSP_CALLBACK_SET_BASS: + bass = param; + break; + case DSP_CALLBACK_SET_TREBLE: + treble = param; + break; +#ifdef HAVE_SW_VOLUME_CONTROL + case DSP_CALLBACK_SET_SW_VOLUME: + set_gain(&AUDIO_DSP); + break; +#endif +#endif + case DSP_CALLBACK_SET_CHANNEL_CONFIG: + dsp_set_channel_config(param); + break; + case DSP_CALLBACK_SET_STEREO_WIDTH: + dsp_set_stereo_width(param); + break; + default: + break; + } + return 0; +} +#endif + +/* Process and convert src audio to dst based on the DSP configuration, + * reading count number of audio samples. dst is assumed to be large + * enough; use dsp_output_count() to get the required number. src is an + * array of pointers; for mono and interleaved stereo, it contains one + * pointer to the start of the audio data and the other is ignored; for + * non-interleaved stereo, it contains two pointers, one for each audio + * channel. Returns number of bytes written to dst. + */ +int dsp_process(struct dsp_config *dsp, char *dst, const char *src[], int count) +{ + static int32_t *tmp[2]; /* tdspeed_doit() needs it static */ + static long last_yield; + long tick; + int written = 0; + +#if defined(CPU_COLDFIRE) + /* set emac unit for dsp processing, and save old macsr, we're running in + codec thread context at this point, so can't clobber it */ + unsigned long old_macsr = coldfire_get_macsr(); + coldfire_set_macsr(EMAC_FRACTIONAL | EMAC_SATURATE); +#endif + + if (new_gain) + dsp_set_replaygain(); /* Gain has changed */ + + /* Perform at least one yield before starting */ + last_yield = current_tick; + yield(); + + /* Testing function pointers for NULL is preferred since the pointer + will be preloaded to be used for the call if not. */ + while (count > 0) + { + int samples = MIN(sample_buf_count, count); + count -= samples; + + dsp->input_samples(samples, src, tmp); + +#ifdef HAVE_PITCHSCREEN + if (dsp->tdspeed_active) + samples = tdspeed_doit(tmp, samples); +#endif + + int chunk_offset = 0; + while (samples > 0) + { + int32_t *t2[2]; + t2[0] = tmp[0]+chunk_offset; + t2[1] = tmp[1]+chunk_offset; + + int chunk = MIN(sample_buf_count, samples); + chunk_offset += chunk; + samples -= chunk; + + if (dsp->apply_gain) + dsp->apply_gain(chunk, &dsp->data, t2); + + if (dsp->resample && (chunk = resample(dsp, chunk, t2)) <= 0) + break; /* I'm pretty sure we're downsampling here */ + + if (dsp->apply_crossfeed) + dsp->apply_crossfeed(chunk, t2); + + if (dsp->eq_process) + dsp->eq_process(chunk, t2); + +#ifdef HAVE_SW_TONE_CONTROLS + if ((bass | treble) != 0) + eq_filter(t2, &dsp->tone_filter, chunk, + dsp->data.num_channels, FILTER_BISHELF_SHIFT); +#endif + + if (dsp->channels_process) + dsp->channels_process(chunk, t2); + + if (dsp->compressor_process) + dsp->compressor_process(chunk, &dsp->data, t2); + + dsp->output_samples(chunk, &dsp->data, (const int32_t **)t2, (int16_t *)dst); + + written += chunk; + dst += chunk * sizeof (int16_t) * 2; + + /* yield at least once each tick */ + tick = current_tick; + if (TIME_AFTER(tick, last_yield)) + { + last_yield = tick; + yield(); + } + } + } + +#if defined(CPU_COLDFIRE) + /* set old macsr again */ + coldfire_set_macsr(old_macsr); +#endif + return written; +} + +/* Given count number of input samples, calculate the maximum number of + * samples of output data that would be generated (the calculation is not + * entirely exact and rounds upwards to be on the safe side; during + * resampling, the number of samples generated depends on the current state + * of the resampler). + */ +/* dsp_input_size MUST be called afterwards */ +int dsp_output_count(struct dsp_config *dsp, int count) +{ +#ifdef HAVE_PITCHSCREEN + if (dsp->tdspeed_active) + count = tdspeed_est_output_size(); +#endif + if (dsp->resample) + { + count = (int)(((unsigned long)count * NATIVE_FREQUENCY + + (dsp->frequency - 1)) / dsp->frequency); + } + + /* Now we have the resampled sample count which must not exceed + * resample_buf_count to avoid resample buffer overflow. One + * must call dsp_input_count() to get the correct input sample + * count. + */ + if (count > resample_buf_count) + count = resample_buf_count; + + return count; +} + +/* Given count output samples, calculate number of input samples + * that would be consumed in order to fill the output buffer. + */ +int dsp_input_count(struct dsp_config *dsp, int count) +{ + /* count is now the number of resampled input samples. Convert to + original input samples. */ + if (dsp->resample) + { + /* Use the real resampling delta = + * dsp->frequency * 65536 / NATIVE_FREQUENCY, and + * round towards zero to avoid buffer overflows. */ + count = (int)(((unsigned long)count * + dsp->data.resample_data.delta) >> 16); + } + +#ifdef HAVE_PITCHSCREEN + if (dsp->tdspeed_active) + count = tdspeed_est_input_size(count); +#endif + + return count; +} + +static void dsp_set_gain_var(long *var, long value) +{ + *var = value; + new_gain = true; +} + +static void dsp_update_functions(struct dsp_config *dsp) +{ + sample_input_new_format(dsp); + sample_output_new_format(dsp); + if (dsp == &AUDIO_DSP) + dsp_set_crossfeed(crossfeed_enabled); +} + +intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value) +{ + switch (setting) + { + case DSP_MYDSP: + switch (value) + { + case CODEC_IDX_AUDIO: + return (intptr_t)&AUDIO_DSP; + case CODEC_IDX_VOICE: + return (intptr_t)&VOICE_DSP; + default: + return (intptr_t)NULL; + } + + case DSP_SET_FREQUENCY: + memset(&dsp->data.resample_data, 0, sizeof (dsp->data.resample_data)); + /* Fall through!!! */ + case DSP_SWITCH_FREQUENCY: + dsp->codec_frequency = (value == 0) ? NATIVE_FREQUENCY : value; + /* Account for playback speed adjustment when setting dsp->frequency + if we're called from the main audio thread. Voice UI thread should + not need this feature. + */ +#ifdef HAVE_PITCHSCREEN + if (dsp == &AUDIO_DSP) + dsp->frequency = pitch_ratio * dsp->codec_frequency / PITCH_SPEED_100; + else +#endif + dsp->frequency = dsp->codec_frequency; + + resampler_new_delta(dsp); +#ifdef HAVE_PITCHSCREEN + tdspeed_setup(dsp); +#endif + break; + + case DSP_SET_SAMPLE_DEPTH: + dsp->sample_depth = value; + + if (dsp->sample_depth <= NATIVE_DEPTH) + { + dsp->data.frac_bits = WORD_FRACBITS; + dsp->sample_bytes = sizeof (int16_t); /* samples are 16 bits */ + dsp->data.clip_max = ((1 << WORD_FRACBITS) - 1); + dsp->data.clip_min = -((1 << WORD_FRACBITS)); + } + else + { + dsp->data.frac_bits = value; + dsp->sample_bytes = sizeof (int32_t); /* samples are 32 bits */ + dsp->data.clip_max = (1 << value) - 1; + dsp->data.clip_min = -(1 << value); + } + + dsp->data.output_scale = dsp->data.frac_bits + 1 - NATIVE_DEPTH; + sample_input_new_format(dsp); + dither_init(dsp); + break; + + case DSP_SET_STEREO_MODE: + dsp->stereo_mode = value; + dsp->data.num_channels = value == STEREO_MONO ? 1 : 2; + dsp_update_functions(dsp); +#ifdef HAVE_PITCHSCREEN + tdspeed_setup(dsp); +#endif + break; + + case DSP_RESET: + dsp->stereo_mode = STEREO_NONINTERLEAVED; + dsp->data.num_channels = 2; + dsp->sample_depth = NATIVE_DEPTH; + dsp->data.frac_bits = WORD_FRACBITS; + dsp->sample_bytes = sizeof (int16_t); + dsp->data.output_scale = dsp->data.frac_bits + 1 - NATIVE_DEPTH; + dsp->data.clip_max = ((1 << WORD_FRACBITS) - 1); + dsp->data.clip_min = -((1 << WORD_FRACBITS)); + dsp->codec_frequency = dsp->frequency = NATIVE_FREQUENCY; + + if (dsp == &AUDIO_DSP) + { + track_gain = 0; + album_gain = 0; + track_peak = 0; + album_peak = 0; + new_gain = true; + } + + dsp_update_functions(dsp); + resampler_new_delta(dsp); +#ifdef HAVE_PITCHSCREEN + tdspeed_setup(dsp); +#endif + if (dsp == &AUDIO_DSP) + compressor_reset(); + break; + + case DSP_FLUSH: + memset(&dsp->data.resample_data, 0, + sizeof (dsp->data.resample_data)); + resampler_new_delta(dsp); + dither_init(dsp); +#ifdef HAVE_PITCHSCREEN + tdspeed_setup(dsp); +#endif + if (dsp == &AUDIO_DSP) + compressor_reset(); + break; + + case DSP_SET_TRACK_GAIN: + if (dsp == &AUDIO_DSP) + dsp_set_gain_var(&track_gain, value); + break; + + case DSP_SET_ALBUM_GAIN: + if (dsp == &AUDIO_DSP) + dsp_set_gain_var(&album_gain, value); + break; + + case DSP_SET_TRACK_PEAK: + if (dsp == &AUDIO_DSP) + dsp_set_gain_var(&track_peak, value); + break; + + case DSP_SET_ALBUM_PEAK: + if (dsp == &AUDIO_DSP) + dsp_set_gain_var(&album_peak, value); + break; + + default: + return 0; + } + + return 1; +} + +int get_replaygain_mode(bool have_track_gain, bool have_album_gain) +{ + int type; + + bool track = ((global_settings.replaygain_type == REPLAYGAIN_TRACK) + || ((global_settings.replaygain_type == REPLAYGAIN_SHUFFLE) + && global_settings.playlist_shuffle)); + + type = (!track && have_album_gain) ? REPLAYGAIN_ALBUM + : have_track_gain ? REPLAYGAIN_TRACK : -1; + + return type; +} + +void dsp_set_replaygain(void) +{ + long gain = 0; + + new_gain = false; + + if ((global_settings.replaygain_type != REPLAYGAIN_OFF) || + global_settings.replaygain_noclip) + { + bool track_mode = get_replaygain_mode(track_gain != 0, + album_gain != 0) == REPLAYGAIN_TRACK; + long peak = (track_mode || !album_peak) ? track_peak : album_peak; + + if (global_settings.replaygain_type != REPLAYGAIN_OFF) + { + gain = (track_mode || !album_gain) ? track_gain : album_gain; + + if (global_settings.replaygain_preamp) + { + long preamp = get_replaygain_int( + global_settings.replaygain_preamp * 10); + + gain = (long) (((int64_t) gain * preamp) >> 24); + } + } + + if (gain == 0) + { + /* So that noclip can work even with no gain information. */ + gain = DEFAULT_GAIN; + } + + if (global_settings.replaygain_noclip && (peak != 0) + && ((((int64_t) gain * peak) >> 24) >= DEFAULT_GAIN)) + { + gain = (((int64_t) DEFAULT_GAIN << 24) / peak); + } + + if (gain == DEFAULT_GAIN) + { + /* Nothing to do, disable processing. */ + gain = 0; + } + } + + /* Store in S7.24 format to simplify calculations. */ + replaygain = gain; + set_gain(&AUDIO_DSP); +} + +/** SET COMPRESSOR + * Called by the menu system to configure the compressor process */ +void dsp_set_compressor(void) +{ + /* enable/disable the compressor */ + AUDIO_DSP.compressor_process = compressor_update() ? + compressor_process : NULL; +} |