ed6a271dfc
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7172 a1c6a512-1295-4272-9138-f99709370657
407 lines
10 KiB
C
407 lines
10 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 <string.h>
|
|
#include "kernel.h"
|
|
#include "logf.h"
|
|
|
|
#include "dsp.h"
|
|
#include "playback.h"
|
|
#include "system.h"
|
|
|
|
/* The "dither" code to convert the 24-bit samples produced by libmad was
|
|
taken from the coolplayer project - coolplayer.sourceforge.net */
|
|
struct s_dither {
|
|
int error[3];
|
|
int random;
|
|
};
|
|
|
|
static struct s_dither dither[2];
|
|
struct dsp_configuration dsp_config;
|
|
static int channel;
|
|
static int fracbits;
|
|
|
|
#define SAMPLE_DEPTH 16
|
|
|
|
/*
|
|
* NAME: prng()
|
|
* DESCRIPTION: 32-bit pseudo-random number generator
|
|
*/
|
|
static __inline
|
|
unsigned long prng(unsigned long state)
|
|
{
|
|
return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
|
|
}
|
|
|
|
inline long dsp_noiseshape(long sample)
|
|
{
|
|
sample += dither[channel].error[0] - dither[channel].error[1]
|
|
+ dither[channel].error[2];
|
|
dither[channel].error[2] = dither[channel].error[1];
|
|
dither[channel].error[1] = dither[channel].error[0]/2;
|
|
|
|
return sample;
|
|
}
|
|
|
|
inline long dsp_bias(long sample)
|
|
{
|
|
sample = sample + (1L << (fracbits - SAMPLE_DEPTH));
|
|
|
|
return sample;
|
|
}
|
|
|
|
inline long dsp_dither(long *mask)
|
|
{
|
|
long random, output;
|
|
|
|
random = prng(dither[channel].random);
|
|
output = (random & *mask) - (dither[channel].random & *mask);
|
|
dither[channel].random = random;
|
|
|
|
return output;
|
|
}
|
|
|
|
inline void dsp_clip(long *sample, long *output)
|
|
{
|
|
if (*output > dsp_config.clip_max) {
|
|
*output = dsp_config.clip_max;
|
|
|
|
if (*sample > dsp_config.clip_max)
|
|
*sample = dsp_config.clip_max;
|
|
} else if (*output < dsp_config.clip_min) {
|
|
*output = dsp_config.clip_min;
|
|
|
|
if (*sample < dsp_config.clip_min)
|
|
*sample = dsp_config.clip_min;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* NAME: dither()
|
|
* DESCRIPTION: dither and scale sample
|
|
*/
|
|
inline int scale_dither_clip(long sample)
|
|
{
|
|
unsigned int scalebits;
|
|
long output, mask;
|
|
|
|
/* noise shape */
|
|
sample = dsp_noiseshape(sample);
|
|
|
|
/* bias */
|
|
output = dsp_bias(sample);
|
|
|
|
scalebits = fracbits + 1 - SAMPLE_DEPTH;
|
|
mask = (1L << scalebits) - 1;
|
|
|
|
/* dither */
|
|
output += dsp_dither(&mask);
|
|
|
|
/* clip */
|
|
dsp_clip(&sample, &output);
|
|
|
|
/* quantize */
|
|
output &= ~mask;
|
|
|
|
/* error feedback */
|
|
dither->error[0] = sample - output;
|
|
|
|
/* scale */
|
|
return output >> scalebits;
|
|
}
|
|
|
|
inline int scale_clip(long sample)
|
|
{
|
|
unsigned int scalebits;
|
|
long output, mask;
|
|
|
|
output = sample;
|
|
scalebits = fracbits + 1 - SAMPLE_DEPTH;
|
|
mask = (1L << scalebits) - 1;
|
|
|
|
dsp_clip(&sample, &output);
|
|
output &= ~mask;
|
|
|
|
return output >> scalebits;
|
|
}
|
|
|
|
void dsp_scale_dither_clip(short *dest, long *src, int samplecount)
|
|
{
|
|
dest += channel;
|
|
while (samplecount-- > 0) {
|
|
*dest = scale_dither_clip(*src);
|
|
src++;
|
|
dest += 2;
|
|
}
|
|
}
|
|
|
|
void dsp_scale_clip(short *dest, long *src, int samplecount)
|
|
{
|
|
dest += channel;
|
|
while (samplecount-- > 0) {
|
|
*dest = scale_clip(*src);
|
|
src++;
|
|
dest += 2;
|
|
}
|
|
}
|
|
|
|
struct resampler {
|
|
long last_sample, phase, delta;
|
|
};
|
|
|
|
static struct resampler resample[2];
|
|
|
|
#if CONFIG_CPU==MCF5249 && !defined(SIMULATOR)
|
|
|
|
#define INIT() asm volatile ("move.l #0xb0, %macsr") /* frac, round, clip */
|
|
#define FRACMUL(x, y) \
|
|
({ \
|
|
long t; \
|
|
asm volatile ("mac.l %[a], %[b], %%acc0\n\t" \
|
|
"movclr.l %%acc0, %[t]\n\t" \
|
|
: [t] "=r" (t) : [a] "r" (x), [b] "r" (y)); \
|
|
t; \
|
|
})
|
|
|
|
#else
|
|
|
|
#define INIT()
|
|
#define FRACMUL(x, y) (long)(((long long)(x)*(long long)(y)) << 1)
|
|
#endif
|
|
|
|
/* linear resampling, introduces one sample delay, because of our inability to
|
|
look into the future at the end of a frame */
|
|
long downsample(long *out, long *in, int num, struct resampler *s)
|
|
{
|
|
long i = 1, pos;
|
|
long last = s->last_sample;
|
|
|
|
INIT();
|
|
pos = s->phase >> 16;
|
|
/* check if we need last sample of previous frame for interpolation */
|
|
if (pos > 0)
|
|
last = in[pos - 1];
|
|
out[0] = last + FRACMUL((s->phase & 0xffff) << 15, in[pos] - last);
|
|
s->phase += s->delta;
|
|
while ((pos = s->phase >> 16) < num) {
|
|
out[i++] = in[pos - 1] + FRACMUL((s->phase & 0xffff) << 15, in[pos] - in[pos - 1]);
|
|
s->phase += s->delta;
|
|
}
|
|
/* wrap phase accumulator back to start of next frame */
|
|
s->phase -= num << 16;
|
|
s->last_sample = in[num - 1];
|
|
return i;
|
|
}
|
|
|
|
long upsample(long *out, long *in, int num, struct resampler *s)
|
|
{
|
|
long i = 0, pos;
|
|
|
|
INIT();
|
|
while ((pos = s->phase >> 16) == 0) {
|
|
out[i++] = s->last_sample + FRACMUL((s->phase & 0xffff) << 15, in[pos] - s->last_sample);
|
|
s->phase += s->delta;
|
|
}
|
|
while ((pos = s->phase >> 16) < num) {
|
|
out[i++] = in[pos - 1] + FRACMUL((s->phase & 0xffff) << 15, in[pos] - in[pos - 1]);
|
|
s->phase += s->delta;
|
|
}
|
|
/* wrap phase accumulator back to start of next frame */
|
|
s->phase -= num << 16;
|
|
s->last_sample = in[num - 1];
|
|
return i;
|
|
}
|
|
|
|
#define MAX_CHUNK_SIZE 1024
|
|
static char samplebuf[MAX_CHUNK_SIZE];
|
|
/* enough to cope with 11khz upsampling */
|
|
long resampled[MAX_CHUNK_SIZE * 4];
|
|
|
|
int process(short *dest, long *src, int samplecount)
|
|
{
|
|
long *p;
|
|
int length = samplecount;
|
|
|
|
p = resampled;
|
|
|
|
/* Resample as necessary */
|
|
if (dsp_config.frequency > NATIVE_FREQUENCY)
|
|
length = downsample(resampled, src, samplecount, &resample[channel]);
|
|
else if (dsp_config.frequency < NATIVE_FREQUENCY)
|
|
length = upsample(resampled, src, samplecount, &resample[channel]);
|
|
else
|
|
p = src;
|
|
|
|
/* Scale & dither */
|
|
if (dsp_config.dither_enabled) {
|
|
dsp_scale_dither_clip(dest, p, length);
|
|
} else {
|
|
dsp_scale_clip(dest, p, length);
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
void convert_stereo_mode(long *dest, long *src, int samplecount)
|
|
{
|
|
int i;
|
|
|
|
samplecount /= 2;
|
|
|
|
for (i = 0; i < samplecount; i++) {
|
|
dest[i] = src[i*2 + 0];
|
|
dest[i+samplecount] = src[i*2 + 1];
|
|
}
|
|
}
|
|
|
|
void scale_up(long *dest, short *src, int samplecount)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < samplecount; i++)
|
|
dest[i] = (long)(src[i] << 8);
|
|
}
|
|
|
|
void scale_up_convert_stereo_mode(long *dest, short *src, int samplecount)
|
|
{
|
|
int i;
|
|
|
|
samplecount /= 2;
|
|
|
|
for (i = 0; i < samplecount; i++) {
|
|
dest[i] = (long)(src[i*2+0] << SAMPLE_DEPTH);
|
|
dest[i+samplecount] = (long)(src[i*2+1] << SAMPLE_DEPTH);
|
|
}
|
|
}
|
|
|
|
int dsp_process(char *dest, char *src, int samplecount)
|
|
{
|
|
int copy_n, rc;
|
|
char *p;
|
|
int processed_bytes = 0;
|
|
|
|
fracbits = dsp_config.sample_depth;
|
|
|
|
while (samplecount > 0) {
|
|
yield();
|
|
copy_n = MIN(MAX_CHUNK_SIZE / 4, samplecount);
|
|
|
|
p = src;
|
|
/* Scale up to 32-bit samples. */
|
|
if (dsp_config.sample_depth <= SAMPLE_DEPTH) {
|
|
if (dsp_config.stereo_mode == STEREO_INTERLEAVED) {
|
|
scale_up_convert_stereo_mode((long *)samplebuf,
|
|
(short *)p, copy_n);
|
|
} else {
|
|
scale_up((long *)samplebuf, (short *)p, copy_n);
|
|
}
|
|
p = samplebuf;
|
|
fracbits = 31;
|
|
}
|
|
|
|
/* Convert to non-interleaved stereo. */
|
|
else if (dsp_config.stereo_mode == STEREO_INTERLEAVED) {
|
|
convert_stereo_mode((long *)samplebuf, (long *)p, copy_n / 2);
|
|
p = samplebuf;
|
|
}
|
|
|
|
/* Apply DSP functions. */
|
|
if (dsp_config.stereo_mode == STEREO_INTERLEAVED) {
|
|
channel = 0;
|
|
rc = process((short *)dest, (long *)p, copy_n / 2) * 4;
|
|
p += copy_n * 2;
|
|
channel = 1;
|
|
process((short *)dest, (long *)p, copy_n / 2);
|
|
dest += rc;
|
|
} else if (dsp_config.stereo_mode == STEREO_MONO) {
|
|
channel = 0;
|
|
rc = process((short *)dest, (long *)p, copy_n) * 4;
|
|
channel = 1;
|
|
process((short *)dest, (long *)p, copy_n);
|
|
dest += rc;
|
|
} else {
|
|
rc = process((short *)dest, (long *)p, copy_n) * 2;
|
|
dest += rc * 2;
|
|
}
|
|
|
|
samplecount -= copy_n;
|
|
if (dsp_config.sample_depth <= SAMPLE_DEPTH)
|
|
src += copy_n * 2;
|
|
else
|
|
src += copy_n * 4;
|
|
|
|
processed_bytes += rc;
|
|
}
|
|
|
|
/* Set stereo channel */
|
|
channel = channel ? 0 : 1;
|
|
|
|
return processed_bytes;
|
|
}
|
|
|
|
bool dsp_configure(int setting, void *value)
|
|
{
|
|
switch (setting) {
|
|
case DSP_SET_FREQUENCY:
|
|
if ((int)value == 0) {
|
|
dsp_config.frequency = NATIVE_FREQUENCY;
|
|
break ;
|
|
}
|
|
memset(resample, 0, sizeof(resample));
|
|
dsp_config.frequency = (int)value;
|
|
resample[0].delta = resample[1].delta =
|
|
(unsigned long)value*65536/NATIVE_FREQUENCY;
|
|
break ;
|
|
|
|
case DSP_SET_CLIP_MIN:
|
|
dsp_config.clip_min = (long)value;
|
|
break ;
|
|
|
|
case DSP_SET_CLIP_MAX:
|
|
dsp_config.clip_max = (long)value;
|
|
break ;
|
|
|
|
case DSP_SET_SAMPLE_DEPTH:
|
|
dsp_config.sample_depth = (long)value;
|
|
break ;
|
|
|
|
case DSP_SET_STEREO_MODE:
|
|
dsp_config.stereo_mode = (long)value;
|
|
channel = 0;
|
|
break ;
|
|
|
|
case DSP_RESET:
|
|
dsp_config.dither_enabled = false;
|
|
dsp_config.clip_max = 0x7fffffff;
|
|
dsp_config.clip_min = 0x80000000;
|
|
dsp_config.frequency = NATIVE_FREQUENCY;
|
|
channel = 0;
|
|
break ;
|
|
|
|
case DSP_DITHER:
|
|
dsp_config.dither_enabled = (bool)value;
|
|
break ;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|