rockbox/apps/dsp.c
Magnus Holmgren 059a6c8671 Make simulator version of FRACMUL correct. Also corrected related comments.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7261 a1c6a512-1295-4272-9138-f99709370657
2005-07-30 13:47:16 +00:00

658 lines
17 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 <inttypes.h>
#include <string.h>
#include "dsp.h"
#include "kernel.h"
#include "playback.h"
#include "system.h"
#include "settings.h"
#include "debug.h"
/* The "dither" code to convert the 24-bit samples produced by libmad was
* taken from the coolplayer project - coolplayer.sourceforge.net
*/
/* 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_SIZE 256
#define RESAMPLE_BUF_SIZE (256 * 4) /* Enough for 11,025 Hz -> 44,100 Hz*/
#define DEFAULT_REPLAYGAIN 0x01000000
#if defined(CPU_COLDFIRE) && !defined(SIMULATOR)
#define INIT() asm volatile ("move.l #0xb0, %macsr") /* frac, round, clip */
/* Multiply two S.31 fractional integers and return the sign bit and the
* 31 most significant bits of the result.
*/
#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; \
})
/* Multiply one S.31-bit and one S7.24 fractional integer and return the
* sign bit and the 31 most significant bits of the result.
*/
#define FRACMUL_8(x, y) \
({ \
long t; \
long u; \
asm volatile ("mac.l %[a], %[b], %%acc0\n\t" \
"move.l %%accext01, %[u]\n\t" \
"movclr.l %%acc0, %[t]\n\t" \
: [t] "=r" (t), [u] "=r" (u) : [a] "r" (x), [b] "r" (y)); \
(t << 8) | (u & 0xff); \
})
#else
#define INIT()
#define FRACMUL(x, y) (long) (((((long long) (x)) * ((long long) (y))) >> 31))
#define FRACMUL_8(x, y) (long) (((((long long) (x)) * ((long long) (y))) >> 24))
#endif
struct dsp_config
{
long frequency;
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;
bool new_gain;
};
struct resample_data
{
long last_sample;
long phase;
long delta;
};
struct dither_data
{
long error[3];
long random;
};
static struct dsp_config dsp IDATA_ATTR;
static struct dither_data dither_data[2] IDATA_ATTR;
static struct resample_data resample_data[2] IDATA_ATTR;
/* 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 long sample_buf[SAMPLE_BUF_SIZE] IDATA_ATTR;
static long resample_buf[RESAMPLE_BUF_SIZE] IDATA_ATTR;
/* 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(char* src[], int count, long* dst[])
{
count = MIN(SAMPLE_BUF_SIZE / 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_SIZE / 2];
}
else
{
dst[0] = (long*) src[0];
dst[1] = (long*) ((dsp.stereo_mode == STEREO_MONO) ? src[0] : src[1]);
}
if (dsp.sample_depth <= NATIVE_DEPTH)
{
short* s0 = (short*) src[0];
long* d0 = dst[0];
long* 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)
{
long* s0 = (long*) src[0];
long* d0 = dst[0];
long* 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;
}
/* Linear resampling that introduces a one sample delay, because of our
* inability to look into the future at the end of a frame.
*/
static long downsample(long *dst, long *src, int count,
struct resample_data *r)
{
long phase = r->phase;
long delta = r->delta;
long last_sample = r->last_sample;
int pos = phase >> 16;
int i = 1;
/* Do we need last sample of previous frame for interpolation? */
if (pos > 0)
{
last_sample = src[pos - 1];
}
*dst++ = last_sample + FRACMUL((phase & 0xffff) << 15,
src[pos] - last_sample);
phase += delta;
while ((pos = phase >> 16) < count)
{
*dst++ = src[pos - 1] + FRACMUL((phase & 0xffff) << 15,
src[pos] - src[pos - 1]);
phase += delta;
i++;
}
/* Wrap phase accumulator back to start of next frame. */
r->phase = phase - (count << 16);
r->delta = delta;
r->last_sample = src[count - 1];
return i;
}
static long upsample(long *dst, long *src, int count, struct resample_data *r)
{
long phase = r->phase;
long delta = r->delta;
long last_sample = r->last_sample;
int i = 0;
int pos;
while ((pos = phase >> 16) == 0)
{
*dst++ = last_sample + FRACMUL((phase & 0xffff) << 15,
src[pos] - last_sample);
phase += delta;
i++;
}
while ((pos = phase >> 16) < count)
{
*dst++ = src[pos - 1] + FRACMUL((phase & 0xffff) << 15,
src[pos] - src[pos - 1]);
phase += delta;
i++;
}
/* Wrap phase accumulator back to start of next frame. */
r->phase = phase - (count << 16);
r->delta = delta;
r->last_sample = src[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(long* src[], int count)
{
long new_count;
if (dsp.frequency != NATIVE_FREQUENCY)
{
long* d0 = &resample_buf[0];
/* Only process the second channel if needed. */
long* d1 = (src[0] == src[1]) ? d0
: &resample_buf[RESAMPLE_BUF_SIZE / 2];
if (dsp.frequency < NATIVE_FREQUENCY)
{
new_count = upsample(d0, src[0], count, &resample_data[0]);
if (d0 != d1)
{
upsample(d1, src[1], count, &resample_data[1]);
}
}
else
{
new_count = downsample(d0, src[0], count, &resample_data[0]);
if (d0 != d1)
{
downsample(d1, src[1], count, &resample_data[1]);
}
}
src[0] = d0;
src[1] = d1;
}
else
{
new_count = count;
}
return new_count;
}
static inline long clip_sample(long sample)
{
if (sample > dsp.clip_max)
{
sample = dsp.clip_max;
}
else if (sample < dsp.clip_min)
{
sample = dsp.clip_min;
}
return sample;
}
/* The "dither" code to convert the 24-bit samples produced by libmad was
* taken from the coolplayer project - coolplayer.sourceforge.net
*/
static long dither_sample(long sample, long bias, long mask,
struct dither_data* dither)
{
long output;
long random;
/* Noise shape and bias */
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;
sample += (random & mask) - (dither->random & mask);
dither->random = random;
/* Clip and quantize */
sample = clip_sample(sample);
output = clip_sample(output) & ~mask;
/* Error feedback */
dither->error[0] = sample - output;
return output;
}
/* 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(long* src[], int count)
{
if (dsp.replaygain)
{
long* s0 = src[0];
long* s1 = src[1];
long* d0 = &sample_buf[0];
long* d1 = (s0 == s1) ? d0 : &sample_buf[SAMPLE_BUF_SIZE / 2];
long gain = dsp.replaygain;
long i;
src[0] = d0;
src[1] = d1;
for (i = 0; i < count; i++)
{
*d0++ = FRACMUL_8(*s0++, gain);
}
if (src [0] != src [1])
{
for (i = 0; i < count; i++)
{
*d1++ = FRACMUL_8(*s1++, gain);
}
}
}
}
static void write_samples(short* dst, long* src[], int count)
{
long* s0 = src[0];
long* s1 = src[1];
int scale = dsp.frac_bits + 1 - NATIVE_DEPTH;
if (dsp.dither_enabled)
{
long bias = (1L << (dsp.frac_bits - NATIVE_DEPTH));
long mask = (1L << scale) - 1;
while (count-- > 0)
{
*dst++ = (short) (dither_sample(*s0++, bias, mask, &dither_data[0])
>> scale);
*dst++ = (short) (dither_sample(*s1++, bias, mask, &dither_data[1])
>> scale);
}
}
else
{
while (count-- > 0)
{
*dst++ = (short) (clip_sample(*s0++) >> scale);
*dst++ = (short) (clip_sample(*s1++) >> scale);
}
}
}
/* Process and convert src audio to dst based on the DSP configuration,
* reading size bytes of audio data. dst is assumed to be large enough; use
* dst_get_dest_size() to get the required size. src is an array of
* pointers; for mono and interleaved stereo, it contains one pointer to the
* start of the audio data; for non-interleaved stereo, it contains two
* pointers, one for each audio channel. Returns number of bytes written to
* dest.
*/
long dsp_process(char* dst, char* src[], long size)
{
long* tmp[2];
long written = 0;
long factor = (dsp.stereo_mode != STEREO_MONO) ? 2 : 1;
int samples;
size /= dsp.sample_bytes * factor;
INIT();
dsp_set_replaygain(false);
while (size > 0)
{
samples = convert_to_internal(src, size, tmp);
size -= samples;
apply_gain(tmp, samples);
samples = resample(tmp, samples);
write_samples((short*) dst, tmp, samples);
written += samples;
dst += samples * sizeof(short) * 2;
yield();
}
return written * sizeof(short) * 2;
}
/* Given size bytes of input data, calculate the maximum number of bytes 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).
*/
long dsp_output_size(long size)
{
if (dsp.stereo_mode == STEREO_MONO)
{
size *= 2;
}
if (dsp.sample_depth > NATIVE_DEPTH)
{
size /= 2;
}
if (dsp.frequency != NATIVE_FREQUENCY)
{
size = (long) ((((unsigned long) size * NATIVE_FREQUENCY)
+ (dsp.frequency - 1)) / dsp.frequency);
}
return (size + 3) & ~3;
}
/* Given size bytes of output buffer, calculate number of bytes of input
* data that would be consumed in order to fill the output buffer.
*/
long dsp_input_size(long size)
{
if (dsp.stereo_mode == STEREO_MONO)
{
size /= 2;
}
if (dsp.sample_depth > NATIVE_DEPTH)
{
size *= 2;
}
if (dsp.frequency != NATIVE_FREQUENCY)
{
size = (long) ((((unsigned long) size * dsp.frequency)
+ (NATIVE_FREQUENCY - 1)) / NATIVE_FREQUENCY);
}
return size;
}
int dsp_stereo_mode(void)
{
return dsp.stereo_mode;
}
bool dsp_configure(int setting, void *value)
{
switch (setting)
{
case DSP_SET_FREQUENCY:
dsp.frequency = ((int) value == 0) ? NATIVE_FREQUENCY : (int) value;
memset(resample_data, 0, sizeof(resample_data));
resample_data[0].delta = resample_data[1].delta =
(unsigned long) dsp.frequency * 65536 / NATIVE_FREQUENCY;
break;
case DSP_SET_CLIP_MIN:
dsp.clip_min = (long) value;
break;
case DSP_SET_CLIP_MAX:
dsp.clip_max = (long) value;
break;
case DSP_SET_SAMPLE_DEPTH:
dsp.sample_depth = (long) 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 = (long) value;
dsp.sample_bytes = sizeof(long);
}
break;
case DSP_SET_STEREO_MODE:
dsp.stereo_mode = (long) value;
break;
case DSP_RESET:
dsp.dither_enabled = false;
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.frequency = NATIVE_FREQUENCY;
dsp.sample_depth = NATIVE_DEPTH;
dsp.frac_bits = WORD_FRACBITS;
dsp.new_gain = true;
break;
case DSP_DITHER:
memset(dither_data, 0, sizeof(dither_data));
dsp.dither_enabled = (bool) value;
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)
{
if (always || dsp.new_gain)
{
long gain = 0;
dsp.new_gain = false;
if (global_settings.replaygain || global_settings.replaygain_noclip)
{
long peak;
if (global_settings.replaygain)
{
gain = (global_settings.replaygain_track || !dsp.album_gain)
? dsp.track_gain : dsp.album_gain;
}
peak = (global_settings.replaygain_track || !dsp.album_peak)
? dsp.track_peak : dsp.album_peak;
if (gain == 0)
{
/* So that noclip can work even with no gain information. */
gain = DEFAULT_REPLAYGAIN;
}
if (global_settings.replaygain_noclip && (peak != 0)
&& ((((int64_t) gain * peak) >> 24) >= DEFAULT_REPLAYGAIN))
{
gain = (((int64_t) DEFAULT_REPLAYGAIN << 24) / peak);
}
if (gain == DEFAULT_REPLAYGAIN)
{
/* Nothing to do, disable processing. */
gain = 0;
}
}
dsp.replaygain = gain;
}
}