0c74d2ce8e
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12361 a1c6a512-1295-4272-9138-f99709370657
1015 lines
28 KiB
C
1015 lines
28 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2005 Miika Pekkarinen
|
|
*
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include "config.h"
|
|
#include <stdbool.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <sound.h>
|
|
#include "dsp.h"
|
|
#include "eq.h"
|
|
#include "kernel.h"
|
|
#include "playback.h"
|
|
#include "system.h"
|
|
#include "settings.h"
|
|
#include "replaygain.h"
|
|
#include "misc.h"
|
|
#include "debug.h"
|
|
|
|
#ifndef SIMULATOR
|
|
#include <dsp_asm.h>
|
|
#endif
|
|
|
|
/* 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 SAMPLE_BUF_COUNT 256
|
|
#define RESAMPLE_BUF_COUNT (256 * 4) /* Enough for 11,025 Hz -> 44,100 Hz*/
|
|
#define DEFAULT_GAIN 0x01000000
|
|
|
|
struct dsp_config
|
|
{
|
|
long codec_frequency; /* Sample rate of data coming from the codec */
|
|
long frequency; /* Effective sample rate after pitch shift (if any) */
|
|
long clip_min;
|
|
long clip_max;
|
|
long track_gain;
|
|
long album_gain;
|
|
long track_peak;
|
|
long album_peak;
|
|
long replaygain;
|
|
int sample_depth;
|
|
int sample_bytes;
|
|
int stereo_mode;
|
|
int frac_bits;
|
|
bool dither_enabled;
|
|
long dither_bias;
|
|
long dither_mask;
|
|
bool new_gain;
|
|
bool crossfeed_enabled;
|
|
bool eq_enabled;
|
|
long eq_precut;
|
|
long gain; /* Note that this is in S8.23 format. */
|
|
};
|
|
|
|
struct resample_data
|
|
{
|
|
long phase, delta;
|
|
int32_t last_sample[2];
|
|
};
|
|
|
|
struct dither_data
|
|
{
|
|
long error[3];
|
|
long random;
|
|
};
|
|
|
|
struct crossfeed_data
|
|
{
|
|
int32_t gain; /* Direct path gain */
|
|
int32_t coefs[3]; /* Coefficients for the shelving filter */
|
|
int32_t history[4]; /* Format is x[n - 1], y[n - 1] for both channels */
|
|
int32_t delay[13][2];
|
|
int index; /* Current index into the delay line */
|
|
};
|
|
|
|
/* 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]; /* Flags for active filters */
|
|
struct eqfilter filters[5];
|
|
};
|
|
|
|
static struct dsp_config dsp_conf[2] IBSS_ATTR;
|
|
static struct dither_data dither_data[2] IBSS_ATTR;
|
|
static struct resample_data resample_data[2] IBSS_ATTR;
|
|
struct crossfeed_data crossfeed_data IBSS_ATTR;
|
|
static struct eq_state eq_data;
|
|
|
|
static int pitch_ratio = 1000;
|
|
static int channels_mode = 0;
|
|
static int32_t sw_gain, sw_cross;
|
|
|
|
extern int current_codec;
|
|
static struct dsp_config *dsp;
|
|
|
|
/* 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.
|
|
*/
|
|
|
|
static int32_t sample_buf[SAMPLE_BUF_COUNT] IBSS_ATTR;
|
|
static int32_t resample_buf[RESAMPLE_BUF_COUNT] IBSS_ATTR;
|
|
|
|
int sound_get_pitch(void)
|
|
{
|
|
return pitch_ratio;
|
|
}
|
|
|
|
void sound_set_pitch(int permille)
|
|
{
|
|
pitch_ratio = permille;
|
|
|
|
dsp_configure(DSP_SWITCH_FREQUENCY, dsp->codec_frequency);
|
|
}
|
|
|
|
/* Convert at most count samples to the internal format, if needed. Returns
|
|
* number of samples ready for further processing. 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.
|
|
*/
|
|
static int convert_to_internal(const char* src[], int count, int32_t* dst[])
|
|
{
|
|
count = MIN(SAMPLE_BUF_COUNT / 2, count);
|
|
|
|
if ((dsp->sample_depth <= NATIVE_DEPTH)
|
|
|| (dsp->stereo_mode == STEREO_INTERLEAVED))
|
|
{
|
|
dst[0] = &sample_buf[0];
|
|
dst[1] = (dsp->stereo_mode == STEREO_MONO)
|
|
? dst[0] : &sample_buf[SAMPLE_BUF_COUNT / 2];
|
|
}
|
|
else
|
|
{
|
|
dst[0] = (int32_t*) src[0];
|
|
dst[1] = (int32_t*) ((dsp->stereo_mode == STEREO_MONO) ? src[0] : src[1]);
|
|
}
|
|
|
|
if (dsp->sample_depth <= NATIVE_DEPTH)
|
|
{
|
|
short* s0 = (short*) src[0];
|
|
int32_t* d0 = dst[0];
|
|
int32_t* d1 = dst[1];
|
|
int scale = WORD_SHIFT;
|
|
int i;
|
|
|
|
if (dsp->stereo_mode == STEREO_INTERLEAVED)
|
|
{
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
*d0++ = *s0++ << scale;
|
|
*d1++ = *s0++ << scale;
|
|
}
|
|
}
|
|
else if (dsp->stereo_mode == STEREO_NONINTERLEAVED)
|
|
{
|
|
short* s1 = (short*) src[1];
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
*d0++ = *s0++ << scale;
|
|
*d1++ = *s1++ << scale;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
*d0++ = *s0++ << scale;
|
|
}
|
|
}
|
|
}
|
|
else if (dsp->stereo_mode == STEREO_INTERLEAVED)
|
|
{
|
|
int32_t* s0 = (int32_t*) src[0];
|
|
int32_t* d0 = dst[0];
|
|
int32_t* d1 = dst[1];
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
*d0++ = *s0++;
|
|
*d1++ = *s0++;
|
|
}
|
|
}
|
|
|
|
if (dsp->stereo_mode == STEREO_NONINTERLEAVED)
|
|
{
|
|
src[0] += count * dsp->sample_bytes;
|
|
src[1] += count * dsp->sample_bytes;
|
|
}
|
|
else if (dsp->stereo_mode == STEREO_INTERLEAVED)
|
|
{
|
|
src[0] += count * dsp->sample_bytes * 2;
|
|
}
|
|
else
|
|
{
|
|
src[0] += count * dsp->sample_bytes;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static void resampler_set_delta(int frequency)
|
|
{
|
|
resample_data[current_codec].delta = (unsigned long)
|
|
frequency * 65536LL / NATIVE_FREQUENCY;
|
|
}
|
|
|
|
/* Linear interpolation resampling that introduces a one sample delay because
|
|
* of our inability to look into the future at the end of a frame.
|
|
*/
|
|
|
|
/* TODO: we really should have a separate set of resample functions for both
|
|
mono and stereo to avoid all this internal branching and looping. */
|
|
static int downsample(int32_t **dst, int32_t **src, int count,
|
|
struct resample_data *r)
|
|
{
|
|
long phase = r->phase;
|
|
long delta = r->delta;
|
|
int32_t last_sample;
|
|
int32_t *d[2] = { dst[0], dst[1] };
|
|
int pos = phase >> 16;
|
|
int i = 1, j;
|
|
int num_channels = dsp->stereo_mode == STEREO_MONO ? 1 : 2;
|
|
|
|
for (j = 0; j < num_channels; j++) {
|
|
last_sample = r->last_sample[j];
|
|
/* Do we need last sample of previous frame for interpolation? */
|
|
if (pos > 0)
|
|
last_sample = src[j][pos - 1];
|
|
|
|
/* Be sure starting position isn't passed the available data */
|
|
if (pos < count)
|
|
*d[j]++ = last_sample + FRACMUL((phase & 0xffff) << 15,
|
|
src[j][pos] - last_sample);
|
|
else
|
|
{
|
|
/* No samples can be output here since were already passed the
|
|
end. Keep phase, save the last sample and return nothing. */
|
|
i = 0;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
phase += delta;
|
|
|
|
while ((pos = phase >> 16) < count)
|
|
{
|
|
for (j = 0; j < num_channels; j++)
|
|
*d[j]++ = src[j][pos - 1] + FRACMUL((phase & 0xffff) << 15,
|
|
src[j][pos] - src[j][pos - 1]);
|
|
phase += delta;
|
|
i++;
|
|
}
|
|
|
|
/* Wrap phase accumulator back to start of next frame. */
|
|
done:
|
|
r->phase = phase - (count << 16);
|
|
r->last_sample[0] = src[0][count - 1];
|
|
r->last_sample[1] = src[1][count - 1];
|
|
return i;
|
|
}
|
|
|
|
static long upsample(int32_t **dst, int32_t **src, int count, struct resample_data *r)
|
|
{
|
|
long phase = r->phase;
|
|
long delta = r->delta;
|
|
int32_t *d[2] = { dst[0], dst[1] };
|
|
int i = 0, j;
|
|
int pos;
|
|
int num_channels = dsp->stereo_mode == STEREO_MONO ? 1 : 2;
|
|
|
|
while ((phase >> 16) == 0)
|
|
{
|
|
for (j = 0; j < num_channels; j++)
|
|
*d[j]++ = r->last_sample[j] + FRACMUL((phase & 0xffff) << 15,
|
|
src[j][0] - r->last_sample[j]);
|
|
phase += delta;
|
|
i++;
|
|
}
|
|
|
|
while ((pos = phase >> 16) < count)
|
|
{
|
|
for (j = 0; j < num_channels; j++)
|
|
*d[j]++ = src[j][pos - 1] + FRACMUL((phase & 0xffff) << 15,
|
|
src[j][pos] - src[j][pos - 1]);
|
|
phase += delta;
|
|
i++;
|
|
}
|
|
|
|
/* Wrap phase accumulator back to start of next frame. */
|
|
r->phase = phase - (count << 16);
|
|
r->last_sample[0] = src[0][count - 1];
|
|
r->last_sample[1] = src[1][count - 1];
|
|
return i;
|
|
}
|
|
|
|
/* 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(int32_t* src[], int count)
|
|
{
|
|
long new_count;
|
|
|
|
if (dsp->frequency != NATIVE_FREQUENCY)
|
|
{
|
|
int32_t* dst[2] = {&resample_buf[0], &resample_buf[RESAMPLE_BUF_COUNT / 2]};
|
|
|
|
if (dsp->frequency < NATIVE_FREQUENCY)
|
|
{
|
|
new_count = upsample(dst, src, count,
|
|
&resample_data[current_codec]);
|
|
}
|
|
else
|
|
{
|
|
new_count = downsample(dst, src, count,
|
|
&resample_data[current_codec]);
|
|
}
|
|
|
|
src[0] = dst[0];
|
|
if (dsp->stereo_mode != STEREO_MONO)
|
|
src[1] = dst[1];
|
|
else
|
|
src[1] = dst[0];
|
|
}
|
|
else
|
|
{
|
|
new_count = count;
|
|
}
|
|
|
|
return new_count;
|
|
}
|
|
|
|
static inline long clip_sample(int32_t sample, int32_t min, int32_t max)
|
|
{
|
|
if (sample > max)
|
|
{
|
|
sample = max;
|
|
}
|
|
else if (sample < min)
|
|
{
|
|
sample = min;
|
|
}
|
|
|
|
return sample;
|
|
}
|
|
|
|
/* The "dither" code to convert the 24-bit samples produced by libmad was
|
|
* taken from the coolplayer project - coolplayer.sourceforge.net
|
|
*/
|
|
|
|
void dsp_dither_enable(bool enable)
|
|
{
|
|
dsp->dither_enabled = enable;
|
|
}
|
|
|
|
static void dither_init(void)
|
|
{
|
|
memset(&dither_data[0], 0, sizeof(struct dither_data));
|
|
memset(&dither_data[1], 0, sizeof(struct dither_data));
|
|
dsp->dither_bias = (1L << (dsp->frac_bits - NATIVE_DEPTH));
|
|
dsp->dither_mask = (1L << (dsp->frac_bits + 1 - NATIVE_DEPTH)) - 1;
|
|
}
|
|
|
|
static void dither_samples(int32_t* src, int num, struct dither_data* dither)
|
|
{
|
|
int32_t output, sample;
|
|
int32_t random;
|
|
int32_t min, max;
|
|
long mask = dsp->dither_mask;
|
|
long bias = dsp->dither_bias;
|
|
int i;
|
|
|
|
for (i = 0; i < num; ++i) {
|
|
/* Noise shape and bias */
|
|
sample = src[i];
|
|
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 */
|
|
random = dither->random*0x0019660dL + 0x3c6ef35fL;
|
|
output += (random & mask) - (dither->random & mask);
|
|
dither->random = random;
|
|
|
|
/* Clip and quantize */
|
|
min = dsp->clip_min;
|
|
max = dsp->clip_max;
|
|
if (output > max) {
|
|
output = max;
|
|
if (sample > max)
|
|
sample = max;
|
|
} else if (output < min) {
|
|
output = min;
|
|
if (sample < min)
|
|
sample = min;
|
|
}
|
|
output &= ~mask;
|
|
|
|
/* Error feedback */
|
|
dither->error[0] = sample - output;
|
|
src[i] = output;
|
|
}
|
|
}
|
|
|
|
void dsp_set_crossfeed(bool enable)
|
|
{
|
|
dsp->crossfeed_enabled = enable;
|
|
}
|
|
|
|
void dsp_set_crossfeed_direct_gain(int gain)
|
|
{
|
|
/* Work around bug in get_replaygain_int which returns 0 for 0 dB */
|
|
if (gain == 0)
|
|
crossfeed_data.gain = 0x7fffffff;
|
|
else
|
|
crossfeed_data.gain = get_replaygain_int(gain * -10) << 7;
|
|
}
|
|
|
|
void dsp_set_crossfeed_cross_params(long lf_gain, long hf_gain, long cutoff)
|
|
{
|
|
long g1 = get_replaygain_int(lf_gain * -10) << 3;
|
|
long g2 = get_replaygain_int(hf_gain * -10) << 3;
|
|
|
|
filter_bishelf_coefs(0xffffffff/NATIVE_FREQUENCY*cutoff, g1, g2,
|
|
crossfeed_data.coefs);
|
|
}
|
|
|
|
/* 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(int32_t* src[], int count)
|
|
{
|
|
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;
|
|
int di = crossfeed_data.index;
|
|
|
|
int32_t acc;
|
|
int32_t left, right;
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
left = src[0][i];
|
|
right = src[1][i];
|
|
|
|
/* Filter delayed sample from left speaker */
|
|
ACC_INIT(acc, delay[di*2], coefs[0]);
|
|
ACC(acc, hist_l[0], coefs[1]);
|
|
ACC(acc, hist_l[1], coefs[2]);
|
|
/* Save filter history for left speaker */
|
|
hist_l[1] = GET_ACC(acc);
|
|
hist_l[0] = delay[di*2];
|
|
/* Filter delayed sample from right speaker */
|
|
ACC_INIT(acc, delay[di*2 + 1], coefs[0]);
|
|
ACC(acc, hist_r[0], coefs[1]);
|
|
ACC(acc, hist_r[1], coefs[2]);
|
|
/* Save filter history for right speaker */
|
|
hist_r[1] = GET_ACC(acc);
|
|
hist_r[0] = delay[di*2 + 1];
|
|
delay[di*2] = left;
|
|
delay[di*2 + 1] = right;
|
|
/* Now add the attenuated direct sound and write to outputs */
|
|
src[0][i] = FRACMUL(left, gain) + hist_r[1];
|
|
src[1][i] = FRACMUL(right, gain) + hist_l[1];
|
|
|
|
/* Wrap delay line index if bigger than delay line size */
|
|
if (++di > 12)
|
|
di = 0;
|
|
}
|
|
/* Write back local copies of data we've modified */
|
|
crossfeed_data.index = di;
|
|
}
|
|
#endif
|
|
|
|
/* Combine all gains to a global gain. */
|
|
static void set_gain(void)
|
|
{
|
|
dsp->gain = DEFAULT_GAIN;
|
|
|
|
if (dsp->replaygain)
|
|
{
|
|
dsp->gain = dsp->replaygain;
|
|
}
|
|
|
|
if (dsp->eq_enabled && dsp->eq_precut)
|
|
{
|
|
dsp->gain = (long) (((int64_t) dsp->gain * dsp->eq_precut) >> 24);
|
|
}
|
|
|
|
if (dsp->gain == DEFAULT_GAIN)
|
|
{
|
|
dsp->gain = 0;
|
|
}
|
|
else
|
|
{
|
|
dsp->gain >>= 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Use to enable the equalizer.
|
|
*
|
|
* @param enable true to enable the equalizer
|
|
*/
|
|
void dsp_set_eq(bool enable)
|
|
{
|
|
dsp->eq_enabled = enable;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
dsp->eq_precut = get_replaygain_int(precut * -10);
|
|
set_gain();
|
|
}
|
|
|
|
/**
|
|
* Synchronize the equalizer filter coefficients with the global settings.
|
|
*
|
|
* @param band the equalizer band to synchronize
|
|
*/
|
|
void dsp_set_eq_coefs(int band)
|
|
{
|
|
const int *setting;
|
|
long gain;
|
|
unsigned long cutoff, q;
|
|
|
|
/* Adjust setting pointer to the band we actually want to change */
|
|
setting = &global_settings.eq_band0_cutoff + (band * 3);
|
|
|
|
/* Convert user settings to format required by coef generator functions */
|
|
cutoff = 0xffffffff / NATIVE_FREQUENCY * (*setting++);
|
|
q = *setting++;
|
|
gain = *setting++;
|
|
|
|
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(int32_t **x, unsigned num)
|
|
{
|
|
int i;
|
|
unsigned int channels = dsp->stereo_mode != STEREO_MONO ? 2 : 1;
|
|
unsigned shift;
|
|
|
|
/* 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]) {
|
|
if (i == 0 || i == 4) /* shelving filters */
|
|
shift = EQ_SHELF_SHIFT;
|
|
else
|
|
shift = EQ_PEAK_SHIFT;
|
|
eq_filter(x, &eq_data.filters[i], num, channels, shift);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Apply a constant gain to the samples (e.g., for ReplayGain). May update
|
|
* the src array if gain was applied.
|
|
* Note that this must be called before the resampler.
|
|
*/
|
|
static void apply_gain(int32_t* _src[], int _count)
|
|
{
|
|
if (dsp->gain)
|
|
{
|
|
int32_t** src = _src;
|
|
int count = _count;
|
|
int32_t* s0 = src[0];
|
|
int32_t* s1 = src[1];
|
|
long gain = dsp->gain;
|
|
int32_t s;
|
|
int i;
|
|
int32_t *d;
|
|
|
|
if (s0 != s1)
|
|
{
|
|
d = &sample_buf[SAMPLE_BUF_COUNT / 2];
|
|
src[1] = d;
|
|
s = *s1++;
|
|
|
|
for (i = 0; i < count; i++)
|
|
FRACMUL_8_LOOP(s, gain, s1, d);
|
|
}
|
|
else
|
|
{
|
|
src[1] = &sample_buf[0];
|
|
}
|
|
|
|
d = &sample_buf[0];
|
|
src[0] = d;
|
|
s = *s0++;
|
|
|
|
for (i = 0; i < count; i++)
|
|
FRACMUL_8_LOOP(s, gain, s0, d);
|
|
}
|
|
}
|
|
|
|
void channels_set(int value)
|
|
{
|
|
channels_mode = value;
|
|
}
|
|
|
|
void stereo_width_set(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;
|
|
}
|
|
sw_gain = straight << 8;
|
|
sw_cross = cross << 8;
|
|
}
|
|
|
|
/* Implements the different channel configurations and stereo width.
|
|
* We might want to combine this with the write_samples stage for efficiency,
|
|
* but for now we'll just let it stay as a stage of its own.
|
|
*/
|
|
static void channels_process(int32_t **src, int num)
|
|
{
|
|
int i;
|
|
int32_t *sl = src[0], *sr = src[1];
|
|
|
|
if (channels_mode == SOUND_CHAN_STEREO)
|
|
return;
|
|
switch (channels_mode) {
|
|
case SOUND_CHAN_MONO:
|
|
for (i = 0; i < num; i++)
|
|
sl[i] = sr[i] = sl[i]/2 + sr[i]/2;
|
|
break;
|
|
case SOUND_CHAN_CUSTOM:
|
|
for (i = 0; i < num; i++) {
|
|
int32_t left_sample = sl[i];
|
|
|
|
sl[i] = FRACMUL(sl[i], sw_gain) + FRACMUL(sr[i], sw_cross);
|
|
sr[i] = FRACMUL(sr[i], sw_gain) + FRACMUL(left_sample, sw_cross);
|
|
}
|
|
break;
|
|
case SOUND_CHAN_MONO_LEFT:
|
|
for (i = 0; i < num; i++)
|
|
sr[i] = sl[i];
|
|
break;
|
|
case SOUND_CHAN_MONO_RIGHT:
|
|
for (i = 0; i < num; i++)
|
|
sl[i] = sr[i];
|
|
break;
|
|
case SOUND_CHAN_KARAOKE:
|
|
for (i = 0; i < num; i++) {
|
|
int32_t left_sample = sl[i]/2;
|
|
|
|
sl[i] = left_sample - sr[i]/2;
|
|
sr[i] = sr[i]/2 - left_sample;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void write_samples(short* dst, int32_t* src[], int count)
|
|
{
|
|
int32_t* s0 = src[0];
|
|
int32_t* s1 = src[1];
|
|
int scale = dsp->frac_bits + 1 - NATIVE_DEPTH;
|
|
|
|
if (dsp->dither_enabled)
|
|
{
|
|
dither_samples(src[0], count, &dither_data[0]);
|
|
dither_samples(src[1], count, &dither_data[1]);
|
|
|
|
while (count-- > 0)
|
|
{
|
|
*dst++ = (short) (*s0++ >> scale);
|
|
*dst++ = (short) (*s1++ >> scale);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
long min = dsp->clip_min;
|
|
long max = dsp->clip_max;
|
|
|
|
while (count-- > 0)
|
|
{
|
|
*dst++ = (short) (clip_sample(*s0++, min, max) >> scale);
|
|
*dst++ = (short) (clip_sample(*s1++, min, max) >> scale);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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(char *dst, const char *src[], int count)
|
|
{
|
|
int32_t* tmp[2];
|
|
int written = 0;
|
|
int samples;
|
|
|
|
#if defined(CPU_COLDFIRE) && !defined(SIMULATOR)
|
|
/* 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
|
|
|
|
dsp = &dsp_conf[current_codec];
|
|
|
|
dsp_set_replaygain(false);
|
|
|
|
while (count > 0)
|
|
{
|
|
samples = convert_to_internal(src, count, tmp);
|
|
count -= samples;
|
|
apply_gain(tmp, samples);
|
|
samples = resample(tmp, samples);
|
|
if (samples <= 0)
|
|
break; /* I'm pretty sure we're downsampling here */
|
|
if (dsp->crossfeed_enabled && dsp->stereo_mode != STEREO_MONO)
|
|
apply_crossfeed(tmp, samples);
|
|
if (dsp->eq_enabled)
|
|
eq_process(tmp, samples);
|
|
if (dsp->stereo_mode != STEREO_MONO)
|
|
channels_process(tmp, samples);
|
|
write_samples((short*) dst, tmp, samples);
|
|
written += samples;
|
|
dst += samples * sizeof(short) * 2;
|
|
yield();
|
|
}
|
|
|
|
#if defined(CPU_COLDFIRE) && !defined(SIMULATOR)
|
|
/* 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(int count)
|
|
{
|
|
dsp = &dsp_conf[current_codec];
|
|
|
|
if (dsp->frequency != NATIVE_FREQUENCY)
|
|
{
|
|
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/2 to avoid resample buffer overflow. One
|
|
* must call dsp_input_count() to get the correct input sample
|
|
* count.
|
|
*/
|
|
if (count > RESAMPLE_BUF_COUNT/2)
|
|
count = RESAMPLE_BUF_COUNT/2;
|
|
|
|
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(int count)
|
|
{
|
|
dsp = &dsp_conf[current_codec];
|
|
|
|
/* count is now the number of resampled input samples. Convert to
|
|
original input samples. */
|
|
if (dsp->frequency != NATIVE_FREQUENCY)
|
|
{
|
|
/* Use the real resampling delta =
|
|
* dsp->frequency * 65536 / NATIVE_FREQUENCY, and
|
|
* round towards zero to avoid buffer overflows. */
|
|
count = (int)(((unsigned long)count *
|
|
resample_data[current_codec].delta) >> 16);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
int dsp_stereo_mode(void)
|
|
{
|
|
dsp = &dsp_conf[current_codec];
|
|
|
|
return dsp->stereo_mode;
|
|
}
|
|
|
|
bool dsp_configure(int setting, intptr_t value)
|
|
{
|
|
dsp = &dsp_conf[current_codec];
|
|
|
|
switch (setting)
|
|
{
|
|
case DSP_SET_FREQUENCY:
|
|
memset(&resample_data[current_codec], 0,
|
|
sizeof(struct 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.
|
|
*/
|
|
if (current_codec == CODEC_IDX_AUDIO)
|
|
dsp->frequency = pitch_ratio * dsp->codec_frequency / 1000;
|
|
else
|
|
dsp->frequency = dsp->codec_frequency;
|
|
resampler_set_delta(dsp->frequency);
|
|
break;
|
|
|
|
case DSP_SET_CLIP_MIN:
|
|
dsp->clip_min = value;
|
|
break;
|
|
|
|
case DSP_SET_CLIP_MAX:
|
|
dsp->clip_max = value;
|
|
break;
|
|
|
|
case DSP_SET_SAMPLE_DEPTH:
|
|
dsp->sample_depth = value;
|
|
|
|
if (dsp->sample_depth <= NATIVE_DEPTH)
|
|
{
|
|
dsp->frac_bits = WORD_FRACBITS;
|
|
dsp->sample_bytes = sizeof(short);
|
|
dsp->clip_max = ((1 << WORD_FRACBITS) - 1);
|
|
dsp->clip_min = -((1 << WORD_FRACBITS));
|
|
}
|
|
else
|
|
{
|
|
dsp->frac_bits = value;
|
|
dsp->sample_bytes = 4; /* samples are 32 bits */
|
|
dsp->clip_max = (1 << value) - 1;
|
|
dsp->clip_min = -(1 << value);
|
|
}
|
|
|
|
dither_init();
|
|
break;
|
|
|
|
case DSP_SET_STEREO_MODE:
|
|
dsp->stereo_mode = (long) value;
|
|
break;
|
|
|
|
case DSP_RESET:
|
|
dsp->stereo_mode = STEREO_NONINTERLEAVED;
|
|
dsp->clip_max = ((1 << WORD_FRACBITS) - 1);
|
|
dsp->clip_min = -((1 << WORD_FRACBITS));
|
|
dsp->track_gain = 0;
|
|
dsp->album_gain = 0;
|
|
dsp->track_peak = 0;
|
|
dsp->album_peak = 0;
|
|
dsp->codec_frequency = dsp->frequency = NATIVE_FREQUENCY;
|
|
dsp->sample_depth = NATIVE_DEPTH;
|
|
dsp->frac_bits = WORD_FRACBITS;
|
|
dsp->new_gain = true;
|
|
break;
|
|
|
|
case DSP_FLUSH:
|
|
memset(&resample_data[current_codec], 0,
|
|
sizeof (struct resample_data));
|
|
resampler_set_delta(dsp->frequency);
|
|
dither_init();
|
|
break;
|
|
|
|
case DSP_SET_TRACK_GAIN:
|
|
dsp->track_gain = (long) value;
|
|
dsp->new_gain = true;
|
|
break;
|
|
|
|
case DSP_SET_ALBUM_GAIN:
|
|
dsp->album_gain = (long) value;
|
|
dsp->new_gain = true;
|
|
break;
|
|
|
|
case DSP_SET_TRACK_PEAK:
|
|
dsp->track_peak = (long) value;
|
|
dsp->new_gain = true;
|
|
break;
|
|
|
|
case DSP_SET_ALBUM_PEAK:
|
|
dsp->album_peak = (long) value;
|
|
dsp->new_gain = true;
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void dsp_set_replaygain(bool always)
|
|
{
|
|
dsp = &dsp_conf[current_codec];
|
|
|
|
if (always || dsp->new_gain)
|
|
{
|
|
long gain = 0;
|
|
|
|
dsp->new_gain = false;
|
|
|
|
if (global_settings.replaygain || global_settings.replaygain_noclip)
|
|
{
|
|
bool track_mode = get_replaygain_mode(dsp->track_gain != 0,
|
|
dsp->album_gain != 0) == REPLAYGAIN_TRACK;
|
|
long peak = (track_mode || !dsp->album_peak)
|
|
? dsp->track_peak : dsp->album_peak;
|
|
|
|
if (global_settings.replaygain)
|
|
{
|
|
gain = (track_mode || !dsp->album_gain)
|
|
? dsp->track_gain : dsp->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 S8.23 format to simplify calculations. */
|
|
dsp->replaygain = gain;
|
|
set_gain();
|
|
}
|
|
}
|