| /*************************************************************************** |
| * __________ __ ___. |
| * 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; |
| } |