FS#8894 - Add time stretching feature to all SWCODEC targets - the current algorithm is best for spoken word.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@21258 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Steve Bavin 2009-06-12 07:20:50 +00:00
parent 9e3255fdb0
commit fb2380790e
40 changed files with 1027 additions and 261 deletions

View File

@ -126,6 +126,7 @@ pcmbuf.c
playback.c
codecs.c
dsp.c
tdspeed.c
#ifdef HAVE_RECORDING
enc_config.c
#ifndef SIMULATOR

View File

@ -211,6 +211,8 @@ enum {
ACTION_PS_TOGGLE_MODE,
ACTION_PS_RESET,
ACTION_PS_EXIT, /* _STD_* isnt going to work here */
ACTION_PS_SLOWER,
ACTION_PS_FASTER,
/* yesno screen */
ACTION_YESNO_ACCEPT,

View File

@ -32,22 +32,19 @@
#include "replaygain.h"
#include "misc.h"
#include "debug.h"
#include "tdspeed.h"
#include "buffer.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 WORD_SHIFT 12
#define WORD_FRACBITS 27
#define NATIVE_DEPTH 16
/* If the buffer sizes change, check the assembly code! */
#define SAMPLE_BUF_COUNT 256
#define RESAMPLE_BUF_COUNT (256 * 4) /* Enough for 11,025 Hz -> 44,100 Hz*/
#define DEFAULT_GAIN 0x01000000
#define SAMPLE_BUF_LEFT_CHANNEL 0
#define SAMPLE_BUF_RIGHT_CHANNEL (SAMPLE_BUF_COUNT/2)
#define RESAMPLE_BUF_LEFT_CHANNEL 0
#define RESAMPLE_BUF_RIGHT_CHANNEL (RESAMPLE_BUF_COUNT/2)
#define NATIVE_DEPTH 16
/* If the small buffer size changes, check the assembly code! */
#define SMALL_SAMPLE_BUF_COUNT 256
#define DEFAULT_GAIN 0x01000000
/* enums to index conversion properly with stereo mode and other settings */
enum
@ -101,7 +98,7 @@ struct dsp_data
struct resample_data resample_data; /* 08h */
int32_t clip_min; /* 18h */
int32_t clip_max; /* 1ch */
int32_t gain; /* 20h - Note that this is in S8.23 format. */
int32_t gain; /* 20h - Note that this is in S8.23 format. */
/* 24h */
};
@ -140,11 +137,12 @@ struct eq_state
/* Typedefs keep things much neater in this case */
typedef void (*sample_input_fn_type)(int count, const char *src[],
int32_t *dst[]);
int32_t *dst[]);
typedef int (*resample_fn_type)(int count, struct dsp_data *data,
int32_t *src[], int32_t *dst[]);
const int32_t *src[], int32_t *dst[]);
typedef void (*sample_output_fn_type)(int count, struct dsp_data *data,
int32_t *src[], int16_t *dst);
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 */
@ -163,6 +161,9 @@ struct dsp_config
int sample_depth;
int sample_bytes;
int stereo_mode;
bool tdspeed_enabled; /* User has enabled timestretch */
int tdspeed_percent; /* Speed % */
bool tdspeed_active; /* Timestretch is in use */
int frac_bits;
#ifdef HAVE_SW_TONE_CONTROLS
/* Filter struct for software bass/treble controls */
@ -218,16 +219,31 @@ 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])
#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.
*/
int32_t sample_buf[SAMPLE_BUF_COUNT] IBSS_ATTR;
static int32_t resample_buf[RESAMPLE_BUF_COUNT] IBSS_ATTR;
#define RESAMPLE_RATIO 4 /* Enough for 11,025 Hz -> 44,100 Hz */
static int32_t small_sample_buf[SMALL_SAMPLE_BUF_COUNT] IBSS_ATTR;
static int32_t small_resample_buf[SMALL_SAMPLE_BUF_COUNT * RESAMPLE_RATIO] IBSS_ATTR;
static int32_t *big_sample_buf = NULL;
static int32_t *big_resample_buf = NULL;
static int big_sample_buf_count = -1; /* -1=unknown, 0=not available */
static int sample_buf_count;
static int32_t *sample_buf;
static int32_t *resample_buf;
#define SAMPLE_BUF_LEFT_CHANNEL 0
#define SAMPLE_BUF_RIGHT_CHANNEL (sample_buf_count/2)
#define RESAMPLE_BUF_LEFT_CHANNEL 0
#define RESAMPLE_BUF_RIGHT_CHANNEL (sample_buf_count/2 * RESAMPLE_RATIO)
#if 0
/* Clip sample to arbitrary limits where range > 0 and min + range = max */
@ -260,8 +276,66 @@ int sound_get_pitch(void)
void sound_set_pitch(int permille)
{
pitch_ratio = permille;
dsp_configure(&audio_dsp, DSP_SWITCH_FREQUENCY,
audio_dsp.codec_frequency);
dsp_configure(&AUDIO_DSP, DSP_SWITCH_FREQUENCY,
AUDIO_DSP.codec_frequency);
}
void tdspeed_setup(struct dsp_config *dspc)
{
if (dspc == &AUDIO_DSP)
{
dspc->tdspeed_active = false;
if (!dspc->tdspeed_enabled)
return;
if (dspc->tdspeed_percent == 0)
dspc->tdspeed_percent = 100;
if (!tdspeed_init(
dspc->codec_frequency == 0 ? NATIVE_FREQUENCY : dspc->codec_frequency,
dspc->stereo_mode != STEREO_MONO,
dspc->tdspeed_percent))
return;
if (dspc->tdspeed_percent == 100 || big_sample_buf_count <= 0)
return;
dspc->tdspeed_active = true;
}
}
void dsp_timestretch_enable(bool enable)
{
if (enable)
{
/* Set up timestretch buffers on first enable */
if (big_sample_buf_count < 0)
{
big_sample_buf_count = SMALL_SAMPLE_BUF_COUNT * RESAMPLE_RATIO;
big_sample_buf = small_resample_buf;
big_resample_buf = (int32_t *) buffer_alloc(big_sample_buf_count * RESAMPLE_RATIO * sizeof(int32_t));
}
}
else
{
/* If not enabled at startup, buffers will never be available */
if (big_sample_buf_count < 0)
big_sample_buf_count = 0;
}
AUDIO_DSP.tdspeed_enabled = enable;
tdspeed_setup(&AUDIO_DSP);
}
void dsp_set_timestretch(int percent)
{
AUDIO_DSP.tdspeed_percent = percent;
tdspeed_setup(&AUDIO_DSP);
}
int dsp_get_timestretch()
{
return AUDIO_DSP.tdspeed_percent;
}
bool dsp_timestretch_enabled()
{
return (AUDIO_DSP.tdspeed_enabled && big_sample_buf_count > 0);
}
/* Convert count samples to the internal format, if needed. Updates src
@ -403,10 +477,11 @@ static void sample_input_new_format(struct dsp_config *dsp)
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,
int32_t *src[], int16_t *dst)
const int32_t *src[], int16_t *dst)
{
const int32_t *s0 = src[0];
const int scale = data->output_scale;
@ -425,7 +500,7 @@ static void sample_output_mono(int count, struct dsp_data *data,
/* 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,
int32_t *src[], int16_t *dst)
const int32_t *src[], int16_t *dst)
{
const int32_t *s0 = src[0];
const int32_t *s1 = src[1];
@ -448,7 +523,7 @@ static void sample_output_stereo(int count, struct dsp_data *data,
* This function handles mono and stereo outputs.
*/
static void sample_output_dithered(int count, struct dsp_data *data,
int32_t *src[], int16_t *dst)
const int32_t *src[], int16_t *dst)
{
const int32_t mask = dither_mask;
const int32_t bias = dither_bias;
@ -462,7 +537,7 @@ static void sample_output_dithered(int count, struct dsp_data *data,
for (ch = 0; ch < data->num_channels; ch++)
{
struct dither_data * const dither = &dither_data[ch];
int32_t *s = src[ch];
const int32_t *s = src[ch];
int i;
for (i = 0, d = &dst[ch]; i < count; i++, s++, d += 2)
@ -540,7 +615,7 @@ static void sample_output_new_format(struct dsp_config *dsp)
int out = dsp->data.num_channels - 1;
if (dsp == &audio_dsp && dither_enabled)
if (dsp == &AUDIO_DSP && dither_enabled)
out += 2;
dsp->output_samples = sample_output_functions[out];
@ -552,7 +627,7 @@ static void sample_output_new_format(struct dsp_config *dsp)
*/
#ifndef DSP_HAVE_ASM_RESAMPLING
static int dsp_downsample(int count, struct dsp_data *data,
int32_t *src[], int32_t *dst[])
const int32_t *src[], int32_t *dst[])
{
int ch = data->num_channels - 1;
uint32_t delta = data->resample_data.delta;
@ -565,9 +640,9 @@ static int dsp_downsample(int count, struct dsp_data *data,
/* 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.
*/
int32_t *s = src[ch];
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;
@ -593,7 +668,7 @@ static int dsp_downsample(int count, struct dsp_data *data,
}
static int dsp_upsample(int count, struct dsp_data *data,
int32_t *src[], int32_t *dst[])
const int32_t *src[], int32_t *dst[])
{
int ch = data->num_channels - 1;
uint32_t delta = data->resample_data.delta;
@ -603,11 +678,10 @@ static int dsp_upsample(int count, struct dsp_data *data,
/* Rolled channel loop actually showed slightly faster. */
do
{
/* Should always be able to output a sample for a ratio up to
RESAMPLE_BUF_COUNT / SAMPLE_BUF_COUNT. */
int32_t *s = src[ch];
/* 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;
@ -638,7 +712,7 @@ static int dsp_upsample(int count, struct dsp_data *data,
static void resampler_new_delta(struct dsp_config *dsp)
{
dsp->data.resample_data.delta = (unsigned long)
dsp->data.resample_data.delta = (unsigned long)
dsp->frequency * 65536LL / NATIVE_FREQUENCY;
if (dsp->frequency == NATIVE_FREQUENCY)
@ -669,7 +743,7 @@ static inline int resample(struct dsp_config *dsp, int count, int32_t *src[])
&resample_buf[RESAMPLE_BUF_RIGHT_CHANNEL],
};
count = dsp->resample(count, &dsp->data, src, dst);
count = dsp->resample(count, &dsp->data, (const int32_t **)src, dst);
src[0] = dst[0];
src[1] = dst[dsp->data.num_channels - 1];
@ -686,7 +760,7 @@ static void dither_init(struct dsp_config *dsp)
void dsp_dither_enable(bool enable)
{
struct dsp_config *dsp = &audio_dsp;
struct dsp_config *dsp = &AUDIO_DSP;
dither_enabled = enable;
sample_output_new_format(dsp);
}
@ -705,7 +779,7 @@ static void apply_crossfeed(int count, int32_t *buf[])
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;
@ -734,7 +808,7 @@ static void apply_crossfeed(int count, int32_t *buf[])
/* 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;
@ -754,7 +828,7 @@ static void apply_crossfeed(int count, int32_t *buf[])
void dsp_set_crossfeed(bool enable)
{
crossfeed_enabled = enable;
audio_dsp.apply_crossfeed = (enable && audio_dsp.data.num_channels > 1)
AUDIO_DSP.apply_crossfeed = (enable && AUDIO_DSP.data.num_channels > 1)
? apply_crossfeed : NULL;
}
@ -815,17 +889,17 @@ static void set_gain(struct dsp_config *dsp)
dsp->data.gain = DEFAULT_GAIN;
/* Replay gain not relevant to voice */
if (dsp == &audio_dsp && replaygain)
if (dsp == &AUDIO_DSP && replaygain)
{
dsp->data.gain = replaygain;
}
if (dsp->eq_process && eq_precut)
{
dsp->data.gain =
(long) (((int64_t) dsp->data.gain * eq_precut) >> 24);
}
if (dsp->data.gain == DEFAULT_GAIN)
{
dsp->data.gain = 0;
@ -846,7 +920,7 @@ static void set_gain(struct dsp_config *dsp)
void dsp_set_eq_precut(int precut)
{
eq_precut = get_replaygain_int(precut * -10);
set_gain(&audio_dsp);
set_gain(&AUDIO_DSP);
}
/**
@ -867,10 +941,10 @@ void dsp_set_eq_coefs(int band)
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. */
@ -903,7 +977,7 @@ static void eq_process(int count, int32_t *buf[])
EQ_PEAK_SHIFT, /* peaking */
EQ_SHELF_SHIFT, /* high shelf */
};
unsigned int channels = audio_dsp.data.num_channels;
unsigned int channels = AUDIO_DSP.data.num_channels;
int i;
/* filter configuration currently is 1 low shelf filter, 3 band peaking
@ -925,14 +999,14 @@ static void eq_process(int count, int32_t *buf[])
*/
void dsp_set_eq(bool enable)
{
audio_dsp.eq_process = enable ? eq_process : NULL;
set_gain(&audio_dsp);
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)
@ -1039,14 +1113,14 @@ static void dsp_set_channel_config(int value)
};
if ((unsigned)value >= ARRAYLEN(channels_process_functions) ||
audio_dsp.stereo_mode == STEREO_MONO)
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];
AUDIO_DSP.channels_process = channels_process_functions[value];
}
#if CONFIG_CODEC == SWCODEC
@ -1057,10 +1131,10 @@ 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);
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));
memcpy(&VOICE_DSP.tone_filter.coefs, AUDIO_DSP.tone_filter.coefs,
sizeof (VOICE_DSP.tone_filter.coefs));
}
#endif
@ -1069,7 +1143,8 @@ static void set_tone_controls(void)
*/
int dsp_callback(int msg, intptr_t param)
{
switch (msg) {
switch (msg)
{
#ifdef HAVE_SW_TONE_CONTROLS
case DSP_CALLBACK_SET_PRESCALE:
prescale = param;
@ -1112,7 +1187,6 @@ int dsp_process(struct dsp_config *dsp, char *dst, const char *src[], int count)
static long last_yield;
long tick;
int written = 0;
int samples;
#if defined(CPU_COLDFIRE)
/* set emac unit for dsp processing, and save old macsr, we're running in
@ -1132,43 +1206,58 @@ int dsp_process(struct dsp_config *dsp, char *dst, const char *src[], int count)
will be preloaded to be used for the call if not. */
while (count > 0)
{
samples = MIN(SAMPLE_BUF_COUNT/2, count);
int samples = MIN(sample_buf_count/2, count);
count -= samples;
dsp->input_samples(samples, src, tmp);
if (dsp->apply_gain)
dsp->apply_gain(samples, &dsp->data, tmp);
if (dsp->tdspeed_active)
samples = tdspeed_doit(tmp, samples);
if (dsp->resample && (samples = resample(dsp, samples, tmp)) <= 0)
break; /* I'm pretty sure we're downsampling here */
int chunk_offset = 0;
while (samples > 0)
{
int32_t *t2[2];
t2[0] = tmp[0]+chunk_offset;
t2[1] = tmp[1]+chunk_offset;
if (dsp->apply_crossfeed)
dsp->apply_crossfeed(samples, tmp);
int chunk = MIN(sample_buf_count/2, samples);
chunk_offset += chunk;
samples -= chunk;
if (dsp->eq_process)
dsp->eq_process(samples, tmp);
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(tmp, &dsp->tone_filter, samples,
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(samples, tmp);
if (dsp->channels_process)
dsp->channels_process(chunk, t2);
dsp->output_samples(samples, &dsp->data, tmp, (int16_t *)dst);
dsp->output_samples(chunk, &dsp->data, (const int32_t **)t2, (int16_t *)dst);
written += samples;
dst += samples * sizeof (int16_t) * 2;
/* yield at least once each tick */
tick = current_tick;
if (TIME_AFTER(tick, last_yield))
{
last_yield = tick;
yield();
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();
}
}
}
@ -1188,6 +1277,20 @@ int dsp_process(struct dsp_config *dsp, char *dst, const char *src[], int count)
/* dsp_input_size MUST be called afterwards */
int dsp_output_count(struct dsp_config *dsp, int count)
{
if(!dsp->tdspeed_active)
{
sample_buf = small_sample_buf;
resample_buf = small_resample_buf;
sample_buf_count = SMALL_SAMPLE_BUF_COUNT;
}
else
{
sample_buf = big_sample_buf;
sample_buf_count = big_sample_buf_count;
resample_buf = big_resample_buf;
}
if(dsp->tdspeed_active)
count = tdspeed_est_output_size();
if (dsp->resample)
{
count = (int)(((unsigned long)count * NATIVE_FREQUENCY
@ -1195,12 +1298,12 @@ int dsp_output_count(struct dsp_config *dsp, int count)
}
/* Now we have the resampled sample count which must not exceed
* RESAMPLE_BUF_COUNT/2 to avoid resample buffer overflow. One
* RESAMPLE_BUF_RIGHT_CHANNEL 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;
if (count > RESAMPLE_BUF_RIGHT_CHANNEL)
count = RESAMPLE_BUF_RIGHT_CHANNEL;
return count;
}
@ -1221,6 +1324,9 @@ int dsp_input_count(struct dsp_config *dsp, int count)
dsp->data.resample_data.delta) >> 16);
}
if(dsp->tdspeed_active)
count = tdspeed_est_input_size(count);
return count;
}
@ -1234,7 +1340,7 @@ static void dsp_update_functions(struct dsp_config *dsp)
{
sample_input_new_format(dsp);
sample_output_new_format(dsp);
if (dsp == &audio_dsp)
if (dsp == &AUDIO_DSP)
dsp_set_crossfeed(crossfeed_enabled);
}
@ -1246,9 +1352,9 @@ intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value)
switch (value)
{
case CODEC_IDX_AUDIO:
return (intptr_t)&audio_dsp;
return (intptr_t)&AUDIO_DSP;
case CODEC_IDX_VOICE:
return (intptr_t)&voice_dsp;
return (intptr_t)&VOICE_DSP;
default:
return (intptr_t)NULL;
}
@ -1262,12 +1368,13 @@ intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value)
if we're called from the main audio thread. Voice UI thread should
not need this feature.
*/
if (dsp == &audio_dsp)
if (dsp == &AUDIO_DSP)
dsp->frequency = pitch_ratio * dsp->codec_frequency / 1000;
else
dsp->frequency = dsp->codec_frequency;
resampler_new_delta(dsp);
tdspeed_setup(dsp);
break;
case DSP_SET_SAMPLE_DEPTH:
@ -1290,13 +1397,14 @@ intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value)
dsp->data.output_scale = dsp->frac_bits + 1 - NATIVE_DEPTH;
sample_input_new_format(dsp);
dither_init(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);
tdspeed_setup(dsp);
break;
case DSP_RESET:
@ -1310,7 +1418,7 @@ intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value)
dsp->data.clip_min = -((1 << WORD_FRACBITS));
dsp->codec_frequency = dsp->frequency = NATIVE_FREQUENCY;
if (dsp == &audio_dsp)
if (dsp == &AUDIO_DSP)
{
track_gain = 0;
album_gain = 0;
@ -1321,6 +1429,7 @@ intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value)
dsp_update_functions(dsp);
resampler_new_delta(dsp);
tdspeed_setup(dsp);
break;
case DSP_FLUSH:
@ -1328,25 +1437,26 @@ intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value)
sizeof (dsp->data.resample_data));
resampler_new_delta(dsp);
dither_init(dsp);
tdspeed_setup(dsp);
break;
case DSP_SET_TRACK_GAIN:
if (dsp == &audio_dsp)
if (dsp == &AUDIO_DSP)
dsp_set_gain_var(&track_gain, value);
break;
case DSP_SET_ALBUM_GAIN:
if (dsp == &audio_dsp)
if (dsp == &AUDIO_DSP)
dsp_set_gain_var(&album_gain, value);
break;
case DSP_SET_TRACK_PEAK:
if (dsp == &audio_dsp)
if (dsp == &AUDIO_DSP)
dsp_set_gain_var(&track_peak, value);
break;
case DSP_SET_ALBUM_PEAK:
if (dsp == &audio_dsp)
if (dsp == &AUDIO_DSP)
dsp_set_gain_var(&album_peak, value);
break;
@ -1403,5 +1513,5 @@ void dsp_set_replaygain(void)
/* Store in S8.23 format to simplify calculations. */
replaygain = gain;
set_gain(&audio_dsp);
set_gain(&AUDIO_DSP);
}

View File

@ -164,5 +164,9 @@ void sound_set_pitch(int r);
int sound_get_pitch(void);
int dsp_callback(int msg, intptr_t param);
void dsp_dither_enable(bool enable);
void dsp_timestretch_enable(bool enable);
void dsp_set_timestretch(int percent);
bool dsp_timestretch_enabled(void);
int dsp_get_timestretch(void);
#endif

View File

@ -86,7 +86,7 @@ channels_process_sound_chan_karaoke:
/****************************************************************************
* void sample_output_mono(int count, struct dsp_data *data,
int32_t *src[], int16_t *dst)
* const int32_t *src[], int16_t *dst)
* NOTE: The following code processes two samples at once. When count is odd,
* there is an additional obsolete sample processed, which will not be
* used by the calling functions.
@ -136,7 +136,7 @@ sample_output_mono:
/****************************************************************************
* void sample_output_stereo(int count, struct dsp_data *data,
int32_t *src[], int16_t *dst)
* const int32_t *src[], int16_t *dst)
* NOTE: The following code processes two samples at once. When count is odd,
* there is an additional obsolete sample processed, which will not be
* used by the calling functions.

View File

@ -54,9 +54,9 @@ void dsp_apply_gain(int count, struct dsp_data *data, int32_t *buf[]);
#ifdef DSP_HAVE_ASM_RESAMPLING
int dsp_upsample(int count, struct dsp_data *data,
int32_t *src[], int32_t *dst[]);
const int32_t *src[], int32_t *dst[]);
int dsp_downsample(int count, struct dsp_data *data,
int32_t *src[], int32_t *dst[]);
const int32_t *src[], int32_t *dst[]);
#endif /* DSP_HAVE_ASM_RESAMPLING */
#ifdef DSP_HAVE_ASM_SOUND_CHAN_MONO
@ -73,12 +73,12 @@ void channels_process_sound_chan_karaoke(int count, int32_t *buf[]);
#ifdef DSP_HAVE_ASM_SAMPLE_OUTPUT_STEREO
void sample_output_stereo(int count, struct dsp_data *data,
int32_t *src[], int16_t *dst);
const int32_t *src[], int16_t *dst);
#endif
#ifdef DSP_HAVE_ASM_SAMPLE_OUTPUT_MONO
void sample_output_mono(int count, struct dsp_data *data,
int32_t *src[], int16_t *dst);
const int32_t *src[], int16_t *dst);
#endif
#endif /* _DSP_ASM_H */

View File

@ -191,7 +191,7 @@ dsp_downsample:
/****************************************************************************
* int dsp_upsample(int count, struct dsp_data *dsp,
* int32_t *src[], int32_t *dst[])
* const int32_t *src[], int32_t *dst[])
*/
.section .text
.align 2
@ -395,7 +395,7 @@ channels_process_sound_chan_karaoke:
/****************************************************************************
* void sample_output_stereo(int count, struct dsp_data *data,
* int32_t *src[], int16_t *dst)
* const int32_t *src[], int16_t *dst)
*
* Framework based on the ubiquitous Rockbox line transfer logic for
* Coldfire CPUs.
@ -517,7 +517,7 @@ sample_output_stereo:
/****************************************************************************
* void sample_output_mono(int count, struct dsp_data *data,
* int32_t *src[], int16_t *dst)
* const int32_t *src[], int16_t *dst)
*
* Same treatment as sample_output_stereo but for one channel.
*/

View File

@ -36,12 +36,13 @@
#include "system.h"
#include "misc.h"
#include "pitchscreen.h"
#if CONFIG_CODEC == SWCODEC
#include "tdspeed.h"
#endif
#define PITCH_MODE_ABSOLUTE 1
#define PITCH_MODE_SEMITONE -PITCH_MODE_ABSOLUTE
#define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */
/* on both sides when drawing */
#define PITCH_MAX 2000
#define PITCH_MIN 500
@ -49,8 +50,14 @@
#define PITCH_BIG_DELTA 10
#define PITCH_NUDGE_DELTA 20
static int pitch_mode = PITCH_MODE_ABSOLUTE; /* 1 - absolute, -1 - semitone */
static enum
{
PITCH_MODE_ABSOLUTE,
PITCH_MODE_SEMITONE,
#if CONFIG_CODEC == SWCODEC
PITCH_MODE_TIMESTRETCH,
#endif
} pitch_mode = PITCH_MODE_ABSOLUTE;
enum
{
@ -83,8 +90,8 @@ static void pitchscreen_fix_viewports(struct viewport *parent,
/* must be called before pitchscreen_draw, or within
* since it neither clears nor updates the display */
static void pitchscreen_draw_icons (struct screen *display,
struct viewport *parent)
static void pitchscreen_draw_icons(struct screen *display,
struct viewport *parent)
{
display->set_viewport(parent);
display->mono_bitmap(bitmap_icons_7x8[Icon_UpArrow],
@ -102,25 +109,29 @@ static void pitchscreen_draw_icons (struct screen *display,
display->update_viewport();
}
static void pitchscreen_draw (struct screen *display, int max_lines,
struct viewport pitch_viewports[PITCH_ITEM_COUNT], int pitch)
static void pitchscreen_draw(struct screen *display, int max_lines,
struct viewport pitch_viewports[PITCH_ITEM_COUNT],
int pitch
#if CONFIG_CODEC == SWCODEC
,int speed
#endif
)
{
unsigned char* ptr;
unsigned char buf[32];
int width_val, w, h;
char buf[32];
int w, h;
bool show_lang_pitch;
/* Hide "Pitch up/Pitch down" for a small screen */
/* "Pitch up/Pitch down" - hide for a small screen */
if (max_lines >= 5)
{
/* UP: Pitch Up */
display->set_viewport(&pitch_viewports[PITCH_TOP]);
if (pitch_mode == PITCH_MODE_ABSOLUTE) {
ptr = str(LANG_PITCH_UP);
} else {
if (pitch_mode == PITCH_MODE_SEMITONE)
ptr = str(LANG_PITCH_UP_SEMITONE);
}
display->getstringsize(ptr,&w,&h);
else
ptr = str(LANG_PITCH_UP);
display->getstringsize(ptr, &w, &h);
display->clear_viewport();
/* draw text */
display->putsxy((pitch_viewports[PITCH_TOP].width / 2) -
@ -129,81 +140,125 @@ static void pitchscreen_draw (struct screen *display, int max_lines,
/* DOWN: Pitch Down */
display->set_viewport(&pitch_viewports[PITCH_BOTTOM]);
if (pitch_mode == PITCH_MODE_ABSOLUTE) {
ptr = str(LANG_PITCH_DOWN);
} else {
if (pitch_mode == PITCH_MODE_SEMITONE)
ptr = str(LANG_PITCH_DOWN_SEMITONE);
}
display->getstringsize(ptr,&w,&h);
else
ptr = str(LANG_PITCH_DOWN);
display->getstringsize(ptr, &w, &h);
display->clear_viewport();
/* draw text */
display->putsxy((pitch_viewports[PITCH_BOTTOM].width / 2) -
(w / 2), 0, ptr);
display->update_viewport();
}
/* Middle section */
display->set_viewport(&pitch_viewports[PITCH_MID]);
snprintf((char *)buf, sizeof(buf), "%s", str(LANG_PITCH));
display->getstringsize(buf,&w,&h);
/* lets hide LANG_PITCH for smaller screens */
display->clear_viewport();
int width_used = 0;
/* Middle section upper line - hide for a small screen */
if ((show_lang_pitch = (max_lines >= 3)))
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
0, buf);
/* "XXX.X%" */
snprintf((char *)buf, sizeof(buf), "%d.%d%%",
pitch / 10, pitch % 10 );
display->getstringsize(buf,&width_val,&h);
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (width_val / 2),
(show_lang_pitch? h : h/2), buf);
/* What's wider? LANG_PITCH or the value?
* Only interesting if LANG_PITCH is actually drawn */
if (show_lang_pitch && width_val > w)
w = width_val;
/* Let's treat '+' and '-' as equally wide
* This saves a getstringsize call
* Also, it wouldn't look nice if -2% shows up, but +2% not */
display->getstringsize("+2%",&width_val,&h);
w += width_val*2;
/* hide +2%/-2% for a narrow screens */
if (w <= pitch_viewports[PITCH_MID].width)
{
/* RIGHT: +2% */
display->putsxy(pitch_viewports[PITCH_MID].width - width_val, h /2, "+2%");
/* LEFT: -2% */
display->putsxy(0, h / 2, "-2%");
#if CONFIG_CODEC == SWCODEC
if (pitch_mode != PITCH_MODE_TIMESTRETCH)
{
#endif
/* LANG_PITCH */
snprintf(buf, sizeof(buf), "%s", str(LANG_PITCH));
#if CONFIG_CODEC == SWCODEC
}
else
{
/* Pitch:XXX.X% */
snprintf(buf, sizeof(buf), "%s:%d.%d%%", str(LANG_PITCH),
pitch / 10, pitch % 10);
}
#endif
display->getstringsize(buf, &w, &h);
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
0, buf);
if (w > width_used)
width_used = w;
}
/* Middle section lower line */
#if CONFIG_CODEC == SWCODEC
if (pitch_mode != PITCH_MODE_TIMESTRETCH)
{
#endif
/* "XXX.X%" */
snprintf(buf, sizeof(buf), "%d.%d%%",
pitch / 10, pitch % 10);
#if CONFIG_CODEC == SWCODEC
}
else
{
/* "Speed:XXX%" */
snprintf(buf, sizeof(buf), "%s:%d%%", str(LANG_SPEED), speed);
}
#endif
display->getstringsize(buf, &w, &h);
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
(show_lang_pitch ? h : h/2), buf);
if (w > width_used)
width_used = w;
/* Middle section left/right labels */
const char *leftlabel = "-2%";
const char *rightlabel = "+2%";
#if CONFIG_CODEC == SWCODEC
if (pitch_mode == PITCH_MODE_TIMESTRETCH)
{
leftlabel = "<<";
rightlabel = ">>";
}
#endif
/* Only display if they fit */
display->getstringsize(leftlabel, &w, &h);
width_used += w;
display->getstringsize(rightlabel, &w, &h);
width_used += w;
if (width_used <= pitch_viewports[PITCH_MID].width)
{
display->putsxy(0, h / 2, leftlabel);
display->putsxy(pitch_viewports[PITCH_MID].width - w, h /2, rightlabel);
}
display->update_viewport();
display->set_viewport(NULL);
}
static int pitch_increase(int pitch, int delta, bool allow_cutoff)
static int pitch_increase(int pitch, int pitch_delta, bool allow_cutoff)
{
int new_pitch;
if (delta < 0) {
if (pitch + delta >= PITCH_MIN) {
new_pitch = pitch + delta;
} else {
if (!allow_cutoff) {
if (pitch_delta < 0)
{
if (pitch + pitch_delta >= PITCH_MIN)
new_pitch = pitch + pitch_delta;
else
{
if (!allow_cutoff)
return pitch;
}
new_pitch = PITCH_MIN;
}
} else if (delta > 0) {
if (pitch + delta <= PITCH_MAX) {
new_pitch = pitch + delta;
} else {
if (!allow_cutoff) {
}
else if (pitch_delta > 0)
{
if (pitch + pitch_delta <= PITCH_MAX)
new_pitch = pitch + pitch_delta;
else
{
if (!allow_cutoff)
return pitch;
}
new_pitch = PITCH_MAX;
}
} else {
/* delta == 0 -> no real change */
}
else
{
/* pitch_delta == 0 -> no real change */
return pitch;
}
sound_set_pitch(new_pitch);
@ -234,10 +289,13 @@ static int pitch_increase_semitone(int pitch, bool up)
uint32_t tmp;
uint32_t round_fct; /* How much to scale down at the end */
tmp = pitch;
if (up) {
if (up)
{
tmp = tmp * PITCH_SEMITONE_FACTOR;
round_fct = PITCH_K_FCT;
} else {
}
else
{
tmp = (tmp * PITCH_KN_FCT) / PITCH_SEMITONE_FACTOR;
round_fct = PITCH_N_FCT;
}
@ -256,7 +314,12 @@ int gui_syncpitchscreen_run(void)
{
int button, i;
int pitch = sound_get_pitch();
int new_pitch, delta = 0;
#if CONFIG_CODEC == SWCODEC
int speed = dsp_get_timestretch();
int maintain_speed_pitch = speed * pitch; /* speed * pitch to maintain */
#endif
int new_pitch;
int pitch_delta = 0;
bool nudged = false;
bool exit = false;
/* should maybe be passed per parameter later, not needed for now */
@ -283,58 +346,118 @@ int gui_syncpitchscreen_run(void)
{
FOR_NB_SCREENS(i)
pitchscreen_draw(&screens[i], max_lines[i],
pitch_viewports[i], pitch);
button = get_action(CONTEXT_PITCHSCREEN,HZ);
switch (button) {
pitch_viewports[i], pitch
#if CONFIG_CODEC == SWCODEC
, speed
#endif
);
button = get_action(CONTEXT_PITCHSCREEN, HZ);
switch (button)
{
case ACTION_PS_INC_SMALL:
delta = PITCH_SMALL_DELTA;
pitch_delta = PITCH_SMALL_DELTA;
break;
case ACTION_PS_INC_BIG:
delta = PITCH_BIG_DELTA;
pitch_delta = PITCH_BIG_DELTA;
break;
case ACTION_PS_DEC_SMALL:
delta = -PITCH_SMALL_DELTA;
pitch_delta = -PITCH_SMALL_DELTA;
break;
case ACTION_PS_DEC_BIG:
delta = -PITCH_BIG_DELTA;
pitch_delta = -PITCH_BIG_DELTA;
break;
case ACTION_PS_NUDGE_RIGHT:
new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false);
nudged = (new_pitch != pitch);
pitch = new_pitch;
#if CONFIG_CODEC == SWCODEC
if (pitch_mode != PITCH_MODE_TIMESTRETCH)
{
#endif
new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false);
nudged = (new_pitch != pitch);
pitch = new_pitch;
break;
#if CONFIG_CODEC == SWCODEC
}
case ACTION_PS_FASTER:
if (pitch_mode == PITCH_MODE_TIMESTRETCH)
{
if (speed < SPEED_MAX)
{
speed++;
dsp_set_timestretch(speed);
maintain_speed_pitch = speed * pitch;
}
}
break;
#endif
case ACTION_PS_NUDGE_RIGHTOFF:
if (nudged) {
if (nudged)
{
pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false);
nudged = false;
}
nudged = false;
break;
case ACTION_PS_NUDGE_LEFT:
new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false);
nudged = (new_pitch != pitch);
pitch = new_pitch;
#if CONFIG_CODEC == SWCODEC
if (pitch_mode != PITCH_MODE_TIMESTRETCH)
{
#endif
new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false);
nudged = (new_pitch != pitch);
pitch = new_pitch;
break;
#if CONFIG_CODEC == SWCODEC
}
case ACTION_PS_SLOWER:
if (pitch_mode == PITCH_MODE_TIMESTRETCH)
{
if (speed > SPEED_MIN)
{
speed--;
dsp_set_timestretch(speed);
maintain_speed_pitch = speed * pitch;
}
}
break;
#endif
case ACTION_PS_NUDGE_LEFTOFF:
if (nudged) {
if (nudged)
{
pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false);
nudged = false;
}
nudged = false;
break;
case ACTION_PS_RESET:
pitch = 1000;
sound_set_pitch( pitch );
sound_set_pitch(pitch);
#if CONFIG_CODEC == SWCODEC
speed = 100;
dsp_set_timestretch(speed);
maintain_speed_pitch = speed * pitch;
#endif
break;
case ACTION_PS_TOGGLE_MODE:
pitch_mode = -pitch_mode;
++pitch_mode;
#if CONFIG_CODEC == SWCODEC
if (dsp_timestretch_enabled())
{
if (pitch_mode > PITCH_MODE_TIMESTRETCH)
pitch_mode = PITCH_MODE_ABSOLUTE;
break;
}
#endif
if (pitch_mode > PITCH_MODE_SEMITONE)
pitch_mode = PITCH_MODE_ABSOLUTE;
break;
case ACTION_PS_EXIT:
@ -342,19 +465,32 @@ int gui_syncpitchscreen_run(void)
break;
default:
if(default_event_handler(button) == SYS_USB_CONNECTED)
if (default_event_handler(button) == SYS_USB_CONNECTED)
return 1;
break;
}
if(delta)
if (pitch_delta)
{
if (pitch_mode == PITCH_MODE_ABSOLUTE) {
pitch = pitch_increase(pitch, delta, true);
} else {
pitch = pitch_increase_semitone(pitch, delta > 0);
if (pitch_mode == PITCH_MODE_SEMITONE)
pitch = pitch_increase_semitone(pitch, pitch_delta > 0);
else
pitch = pitch_increase(pitch, pitch_delta, true);
#if CONFIG_CODEC == SWCODEC
if (pitch_mode == PITCH_MODE_TIMESTRETCH)
{
/* Set speed to maintain time dimension */
/* i.e. increase pitch, slow down speed */
int new_speed = maintain_speed_pitch / pitch;
if (new_speed >= SPEED_MIN && new_speed <= SPEED_MAX)
{
speed = new_speed;
dsp_set_timestretch(speed);
}
}
delta = 0;
else
maintain_speed_pitch = speed * pitch;
#endif
pitch_delta = 0;
}
}
#if CONFIG_CODEC == SWCODEC

View File

@ -159,6 +159,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_F1, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_ON, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_OFF, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */

View File

@ -174,6 +174,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_NUDGE_RIGHTOFF, BUTTON_RIGHT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_pitchscreen */

View File

@ -194,19 +194,17 @@ static const struct button_mapping button_context_settings_right_is_inc[] = {
static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_INC_SMALL, BUTTON_UP, BUTTON_NONE },
{ ACTION_PS_INC_BIG, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_DEC_SMALL, BUTTON_DOWN, BUTTON_NONE },
{ ACTION_PS_DEC_BIG, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_DEC_BIG, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_NUDGE_LEFT, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_PS_NUDGE_LEFTOFF, BUTTON_LEFT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_NUDGE_RIGHT, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_PS_NUDGE_RIGHTOFF, BUTTON_RIGHT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_TOGGLE_MODE, BUTTON_REC, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_pitchscreen */

View File

@ -182,19 +182,19 @@ static const struct button_mapping button_context_settings_right_is_inc[] = {
}; /* button_context_settings_right_is_inc */
static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_INC_SMALL, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_PS_INC_BIG, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_DEC_SMALL, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_PS_DEC_BIG, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_NUDGE_LEFT, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_PS_NUDGE_LEFTOFF, BUTTON_LEFT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_NUDGE_RIGHT, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_PS_NUDGE_RIGHTOFF, BUTTON_RIGHT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_TOGGLE_MODE, BUTTON_HOME, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_UP, BUTTON_NONE },
{ ACTION_PS_INC_SMALL, BUTTON_UP, BUTTON_NONE },
{ ACTION_PS_INC_BIG, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_DEC_SMALL, BUTTON_DOWN, BUTTON_NONE },
{ ACTION_PS_DEC_BIG, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_NUDGE_LEFT, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_PS_NUDGE_LEFTOFF, BUTTON_LEFT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_NUDGE_RIGHT, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_PS_NUDGE_RIGHTOFF, BUTTON_RIGHT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_TOGGLE_MODE, BUTTON_HOME, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_pitchscreen */

View File

@ -203,6 +203,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_UP, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT,BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_pitchscreen */

View File

@ -203,6 +203,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_HOME|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_UP, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_pitchscreen */

View File

@ -252,6 +252,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_MENU, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_PLAY, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_BACK, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT,BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */
@ -445,6 +447,8 @@ static const struct button_mapping remote_button_context_pitchscreen[] = {
{ ACTION_PS_RESET, BUTTON_RC_PLAY|BUTTON_REL, BUTTON_RC_PLAY },
{ ACTION_PS_TOGGLE_MODE, BUTTON_RC_PLAY|BUTTON_REPEAT, BUTTON_RC_PLAY },
{ ACTION_PS_EXIT, BUTTON_RC_DSP|BUTTON_REL, BUTTON_RC_DSP },
{ ACTION_PS_SLOWER, BUTTON_RC_RW|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST
}; /* remote_button_context_pitchscreen */

View File

@ -240,6 +240,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_MENU, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_A, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */

View File

@ -260,6 +260,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_PLAY, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_LEFT, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_REW|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_FF|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_pitchscreen */
@ -274,6 +276,8 @@ static const struct button_mapping remote_button_context_pitchscreen[] = {
{ ACTION_PS_NUDGE_RIGHT, BUTTON_RC_FF, BUTTON_NONE },
{ ACTION_PS_NUDGE_RIGHTOFF, BUTTON_RC_FF|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_RC_PLAY, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_RC_REW|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_pitchscreen */

View File

@ -230,6 +230,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_ON, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_OFF, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */
@ -561,6 +563,8 @@ static const struct button_mapping button_context_pitchscreen_nonlcdremote[] =
{ ACTION_PS_RESET, BUTTON_RC_MENU, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_RC_ON, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_RC_STOP, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */
@ -577,6 +581,8 @@ static const struct button_mapping button_context_pitchscreen_h100lcdremote[] =
{ ACTION_PS_RESET, BUTTON_RC_MENU, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_RC_ON, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_RC_STOP, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_RC_SOURCE|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RC_BITRATE|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
};
@ -593,6 +599,8 @@ static const struct button_mapping button_context_pitchscreen_h300lcdremote[] =
{ ACTION_PS_RESET, BUTTON_RC_MENU, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_RC_ON, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_RC_STOP, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_RC_REW|BUTTON_REPEAT,BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
};

View File

@ -238,8 +238,10 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_NUDGE_RIGHT, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_PS_NUDGE_RIGHTOFF, BUTTON_RIGHT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_TOGGLE_MODE, BUTTON_MENU, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_VIEW, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_VIEW, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */

View File

@ -197,6 +197,10 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_RESET, BUTTON_PLAY, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_MENU, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
#endif
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_pitchscreen */

View File

@ -132,6 +132,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_NUDGE_RIGHTOFF, BUTTON_RIGHT|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_MODE, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_PLAY, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */

View File

@ -146,6 +146,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_PLAY, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_MENU, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchscreen */

View File

@ -190,6 +190,9 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_MODE, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_pitchscreen */

View File

@ -207,6 +207,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_RESET, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD),
}; /* button_context_pitchscreen */

View File

@ -162,6 +162,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_MODE, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_REC, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_PLAY, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchscreen */
@ -178,6 +180,8 @@ static const struct button_mapping remote_button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_RC_MODE, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_RC_REC, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_RC_PLAY, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_RC_REW|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* remote_button_context_pitchscreen */

View File

@ -227,6 +227,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_MENU, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_PLAY, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */

View File

@ -237,6 +237,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_MENU, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_DISPLAY, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_PLAY, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */

View File

@ -240,6 +240,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_MENU, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_RIGHT, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_REW|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_FFWD|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchcreen */

View File

@ -206,6 +206,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_BOTTOMRIGHT, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_CENTER, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_TOPLEFT, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_MIDLEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_MIDRIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM2|CONTEXT_PITCHSCREEN)
}; /* button_context_pitchcreen */

View File

@ -151,6 +151,8 @@ static const struct button_mapping button_context_pitchscreen[] = {
{ ACTION_PS_TOGGLE_MODE, BUTTON_SELECT, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_POWER, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_PLAY, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* button_context_pitchscreen */
@ -166,6 +168,8 @@ static const struct button_mapping remote_button_context_pitchscreen[] = {
{ ACTION_PS_NUDGE_RIGHTOFF, BUTTON_RC_FF|BUTTON_REL, BUTTON_NONE },
{ ACTION_PS_RESET, BUTTON_RC_MODE, BUTTON_NONE },
{ ACTION_PS_EXIT, BUTTON_RC_PLAY, BUTTON_NONE },
{ ACTION_PS_SLOWER, BUTTON_RC_REW|BUTTON_REPEAT, BUTTON_NONE },
{ ACTION_PS_FASTER, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE },
LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD)
}; /* remote_button_context_pitchscreen */

View File

@ -12468,3 +12468,37 @@
*: "Prevent Track Skipping"
</voice>
</phrase>
<phrase>
id: LANG_TIMESTRETCH
desc: timestretch enable
user: core
<source>
*: none
swcodec: "Timestretch"
</source>
<dest>
*: none
swcodec: "Timestretch"
</dest>
<voice>
*: none
swcodec: "Timestretch"
</voice>
</phrase>
<phrase>
id: LANG_SPEED
desc: timestretch speed
user: core
<source>
*: none
swcodec: "Speed"
</source>
<dest>
*: none
swcodec: "Speed"
</dest>
<voice>
*: none
swcodec: "Speed"
</voice>
</phrase>

View File

@ -1,4 +1,3 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
@ -32,6 +31,9 @@
#include "eq_menu.h"
#include "exported_menus.h"
#include "menu_common.h"
#include "splash.h"
#include "kernel.h"
#include "dsp.h"
/***********************************/
/* SOUND MENU */
@ -57,14 +59,14 @@ MENUITEM_SETTING(treble, &global_settings.treble,
MENUITEM_SETTING(treble_cutoff, &global_settings.treble_cutoff, NULL);
#endif
MENUITEM_SETTING(balance, &global_settings.balance, NULL);
MENUITEM_SETTING(channel_config, &global_settings.channel_config,
MENUITEM_SETTING(channel_config, &global_settings.channel_config,
#if CONFIG_CODEC == SWCODEC
lowlatency_callback
#else
NULL
#endif
);
MENUITEM_SETTING(stereo_width, &global_settings.stereo_width,
MENUITEM_SETTING(stereo_width, &global_settings.stereo_width,
#if CONFIG_CODEC == SWCODEC
lowlatency_callback
#else
@ -86,7 +88,21 @@ MENUITEM_SETTING(stereo_width, &global_settings.stereo_width,
MAKE_MENU(crossfeed_menu,ID2P(LANG_CROSSFEED), NULL, Icon_NOICON,
&crossfeed, &crossfeed_direct_gain, &crossfeed_cross_gain,
&crossfeed_hf_attenuation, &crossfeed_hf_cutoff);
static int timestretch_callback(int action,const struct menu_item_ex *this_item)
{
switch (action)
{
case ACTION_EXIT_MENUITEM: /* on exit */
if (global_settings.timestretch_enabled && !dsp_timestretch_enabled())
splash(HZ*2, ID2P(LANG_PLEASE_REBOOT));
break;
}
lowlatency_callback(action, this_item);
return action;
}
MENUITEM_SETTING(timestretch_enabled,
&global_settings.timestretch_enabled, timestretch_callback);
MENUITEM_SETTING(dithering_enabled,
&global_settings.dithering_enabled, lowlatency_callback);
#endif
@ -120,7 +136,8 @@ MAKE_MENU(sound_settings, ID2P(LANG_SOUND_SETTINGS), NULL, Icon_Audio,
#endif
&balance,&channel_config,&stereo_width
#if CONFIG_CODEC == SWCODEC
,&crossfeed_menu, &equalizer_menu, &dithering_enabled
,&crossfeed_menu, &equalizer_menu, &dithering_enabled
,&timestretch_enabled
#endif
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
,&loudness,&avc,&superbass,&mdb_enable,&mdb_strength

View File

@ -936,6 +936,7 @@ void settings_apply(bool read_disk)
}
dsp_dither_enable(global_settings.dithering_enabled);
dsp_timestretch_enable(global_settings.timestretch_enabled);
#endif
#ifdef HAVE_SPDIF_POWER

View File

@ -374,6 +374,7 @@ struct user_settings
int keyclick; /* keyclick volume */
int keyclick_repeats; /* keyclick on repeats */
bool dithering_enabled;
bool timestretch_enabled;
#endif /* CONFIG_CODEC == SWCODEC */
#ifdef HAVE_RECORDING

View File

@ -1181,6 +1181,10 @@ const struct settings_list settings[] = {
/* dithering */
OFFON_SETTING(F_SOUNDSETTING, dithering_enabled, LANG_DITHERING, false,
"dithering enabled", dsp_dither_enable),
/* timestretch */
OFFON_SETTING(F_SOUNDSETTING, timestretch_enabled, LANG_TIMESTRETCH, false,
"timestretch enabled", dsp_timestretch_enable),
#endif
#ifdef HAVE_WM8758
SOUND_SETTING(F_NO_WRAP, bass_cutoff, LANG_BASS_CUTOFF,

319
apps/tdspeed.c Normal file
View File

@ -0,0 +1,319 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2006 by Nicolas Pitre <nico@cam.org>
* Copyright (C) 2006-2007 by Stéphane Doyon <s.doyon@videotron.ca>
*
* 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 <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "buffer.h"
#include "debug.h"
#include "system.h"
#include "tdspeed.h"
#define assert(cond)
#define MIN_RATE 8000
#define MAX_RATE 48000 /* double buffer for double rate */
#define MINFREQ 100
#define FIXED_BUFSIZE 3072 /* 48KHz factor 3.0 */
struct tdspeed_state_s
{
bool stereo;
int32_t shift_max; /* maximum displacement on a frame */
int32_t src_step; /* source window pace */
int32_t dst_step; /* destination window pace */
int32_t dst_order; /* power of two for dst_step */
int32_t ovl_shift; /* overlap buffer frame shift */
int32_t ovl_size; /* overlap buffer used size */
int32_t ovl_space; /* overlap buffer size */
int32_t *ovl_buff[2]; /* overlap buffer */
};
static struct tdspeed_state_s tdspeed_state;
static int32_t *overlap_buffer[2] = { NULL, NULL };
static int32_t *outbuf[2] = { NULL, NULL };
bool tdspeed_init(int samplerate, bool stereo, int factor)
{
struct tdspeed_state_s *st = &tdspeed_state;
int src_frame_sz;
/* Allocate buffers */
if (overlap_buffer[0] == NULL)
overlap_buffer[0] = (int32_t *) buffer_alloc(FIXED_BUFSIZE * sizeof(int32_t));
if (overlap_buffer[1] == NULL)
overlap_buffer[1] = (int32_t *) buffer_alloc(FIXED_BUFSIZE * sizeof(int32_t));
if (outbuf[0] == NULL)
outbuf[0] = (int32_t *) buffer_alloc(TDSPEED_OUTBUFSIZE * sizeof(int32_t));
if (outbuf[1] == NULL)
outbuf[1] = (int32_t *) buffer_alloc(TDSPEED_OUTBUFSIZE * sizeof(int32_t));
/* Check parameters */
if (factor == 100)
return false;
if (samplerate < MIN_RATE || samplerate > MAX_RATE)
return false;
if (factor < SPEED_MIN || factor > SPEED_MAX)
return false;
st->stereo = stereo;
st->dst_step = samplerate / MINFREQ;
if (factor > 100)
st->dst_step = st->dst_step * 100 / factor;
st->dst_order = 1;
while (st->dst_step >>= 1)
st->dst_order++;
st->dst_step = (1 << st->dst_order);
st->src_step = st->dst_step * factor / 100;
st->shift_max = (st->dst_step > st->src_step) ? st->dst_step : st->src_step;
src_frame_sz = st->shift_max + st->dst_step;
if (st->dst_step > st->src_step)
src_frame_sz += st->dst_step - st->src_step;
st->ovl_space = ((src_frame_sz - 2)/st->src_step) * st->src_step
+ src_frame_sz;
if (st->src_step > st->dst_step)
st->ovl_space += 2*st->src_step - st->dst_step;
if (st->ovl_space > FIXED_BUFSIZE)
st->ovl_space = FIXED_BUFSIZE;
st->ovl_size = 0;
st->ovl_shift = 0;
st->ovl_buff[0] = overlap_buffer[0];
if (stereo)
st->ovl_buff[1] = overlap_buffer[1];
else
st->ovl_buff[1] = st->ovl_buff[0];
return true;
}
static int tdspeed_apply(int32_t *buf_out[2], int32_t *buf_in[2],
int data_len, int last, int out_size)
/* data_len in samples */
{
struct tdspeed_state_s *st = &tdspeed_state;
int32_t *curr, *prev, *dest[2], *d;
int32_t i, j, next_frame, prev_frame, shift, src_frame_sz;
bool stereo = buf_in[0] != buf_in[1];
assert(stereo == st->stereo);
src_frame_sz = st->shift_max + st->dst_step;
if (st->dst_step > st->src_step)
src_frame_sz += st->dst_step - st->src_step;
/* deal with overlap data first, if any */
if (st->ovl_size)
{
int32_t have, copy, steps;
have = st->ovl_size;
if (st->ovl_shift > 0)
have -= st->ovl_shift;
/* append just enough data to have all of the overlap buffer consumed */
steps = (have - 1) / st->src_step;
copy = steps * st->src_step + src_frame_sz - have;
if (copy < src_frame_sz - st->dst_step)
copy += st->src_step; /* one more step to allow for pregap data */
if (copy > data_len) copy = data_len;
assert(st->ovl_size +copy <= FIXED_BUFSIZE);
memcpy(st->ovl_buff[0] + st->ovl_size, buf_in[0],
copy * sizeof(int32_t));
if (stereo)
memcpy(st->ovl_buff[1] + st->ovl_size, buf_in[1],
copy * sizeof(int32_t));
if (!last && have + copy < src_frame_sz)
{
/* still not enough to process at least one frame */
st->ovl_size += copy;
return 0;
}
/* recursively call ourselves to process the overlap buffer */
have = st->ovl_size;
st->ovl_size = 0;
if (copy == data_len)
{
assert( (have+copy) <= FIXED_BUFSIZE);
return tdspeed_apply(buf_out, st->ovl_buff, have+copy, last,
out_size);
}
assert( (have+copy) <= FIXED_BUFSIZE);
i = tdspeed_apply(buf_out, st->ovl_buff, have+copy, -1, out_size);
dest[0] = buf_out[0] + i;
dest[1] = buf_out[1] + i;
/* readjust pointers to account for data already consumed */
next_frame = copy - src_frame_sz + st->src_step;
prev_frame = next_frame - st->ovl_shift;
}
else
{
dest[0] = buf_out[0];
dest[1] = buf_out[1];
next_frame = prev_frame = 0;
if (st->ovl_shift > 0)
next_frame += st->ovl_shift;
else
prev_frame += -st->ovl_shift;
}
st->ovl_shift = 0;
/* process all complete frames */
while (data_len - next_frame >= src_frame_sz)
{
/* find frame overlap by autocorelation */
int64_t min_delta = ~(1ll << 63); /* most positive */
shift = 0;
#define INC1 8
#define INC2 32
/* Power of 2 of a 28bit number requires 56bits, can accumulate
256times in a 64bit variable. */
assert(st->dst_step / INC2 <= 256);
assert(next_frame + st->shift_max - 1 + st->dst_step-1 < data_len);
assert(prev_frame + st->dst_step - 1 < data_len);
for (i = 0; i < st->shift_max; i += INC1)
{
int64_t delta = 0;
curr = buf_in[0] + next_frame + i;
prev = buf_in[0] + prev_frame;
for (j = 0; j < st->dst_step; j += INC2, curr += INC2, prev += INC2)
{
int32_t diff = *curr - *prev;
delta += (int64_t)diff * diff;
if (delta >= min_delta)
goto skip;
}
if (stereo)
{
curr = buf_in[1] +next_frame + i;
prev = buf_in[1] +prev_frame;
for (j = 0; j < st->dst_step; j += INC2, curr += INC2, prev += INC2)
{
int32_t diff = *curr - *prev;
delta += (int64_t)diff * diff;
if (delta >= min_delta)
goto skip;
}
}
min_delta = delta;
shift = i;
skip:;
}
/* overlap fading-out previous frame with fading-in current frame */
curr = buf_in[0] + next_frame + shift;
prev = buf_in[0] + prev_frame;
d = dest[0];
assert(next_frame + shift + st->dst_step - 1 < data_len);
assert(prev_frame + st->dst_step - 1 < data_len);
assert(dest[0] - buf_out[0] + st->dst_step - 1 < out_size);
for (i = 0, j = st->dst_step; j; i++, j--)
{
*d++ = (*curr++ * (int64_t)i
+ *prev++ * (int64_t)j) >> st->dst_order;
}
dest[0] = d;
if (stereo)
{
curr = buf_in[1] +next_frame + shift;
prev = buf_in[1] +prev_frame;
d = dest[1];
for (i = 0, j = st->dst_step; j; i++, j--)
{
assert(d < buf_out[1] +out_size);
*d++ = (*curr++ * (int64_t) i
+ *prev++ * (int64_t) j) >> st->dst_order;
}
dest[1] = d;
}
/* adjust pointers for next frame */
prev_frame = next_frame + shift + st->dst_step;
next_frame += st->src_step;
/* here next_frame - prev_frame = src_step - dst_step - shift */
assert(next_frame - prev_frame == st->src_step - st->dst_step - shift);
}
/* now deal with remaining partial frames */
if (last == -1)
{
/* special overlap buffer processing: remember frame shift only */
st->ovl_shift = next_frame - prev_frame;
}
else if (last != 0)
{
/* last call: purge all remaining data to output buffer */
i = data_len -prev_frame;
assert(dest[0] +i <= buf_out[0] +out_size);
memcpy(dest[0], buf_in[0] +prev_frame, i * sizeof(int32_t));
dest[0] += i;
if (stereo)
{
assert(dest[1] +i <= buf_out[1] +out_size);
memcpy(dest[1], buf_in[1] +prev_frame, i * sizeof(int32_t));
dest[1] += i;
}
}
else
{
/* preserve remaining data + needed overlap data for next call */
st->ovl_shift = next_frame - prev_frame;
i = (st->ovl_shift < 0) ? next_frame : prev_frame;
st->ovl_size = data_len - i;
assert(st->ovl_size <= FIXED_BUFSIZE);
memcpy(st->ovl_buff[0], buf_in[0]+i, st->ovl_size * sizeof(int32_t));
if (stereo)
memcpy(st->ovl_buff[1], buf_in[1]+i, st->ovl_size * sizeof(int32_t));
}
return dest[0] - buf_out[0];
}
long tdspeed_est_output_size()
{
return TDSPEED_OUTBUFSIZE;
}
long tdspeed_est_input_size(long size)
{
struct tdspeed_state_s *st = &tdspeed_state;
size = (size -st->ovl_size) *st->src_step / st->dst_step;
if (size < 0)
size = 0;
return size;
}
int tdspeed_doit(int32_t *src[], int count)
{
count = tdspeed_apply( (int32_t *[2]) { outbuf[0], outbuf[1] },
src, count, 0, TDSPEED_OUTBUFSIZE);
src[0] = outbuf[0];
src[1] = outbuf[1];
return count;
}

36
apps/tdspeed.h Normal file
View File

@ -0,0 +1,36 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2006 by Nicolas Pitre <nico@cam.org>
* Copyright (C) 2006-2007 by Stéphane Doyon <s.doyon@videotron.ca>
*
* 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.
*
****************************************************************************/
#ifndef _TDSPEED_H
#define _TDSPEED_H
#define TDSPEED_OUTBUFSIZE 4096
bool tdspeed_init(int samplerate, bool stereo, int factor);
long tdspeed_est_output_size(void);
long tdspeed_est_input_size(long size);
int tdspeed_doit(int32_t *src[], int count);
#define SPEED_MAX 250
#define SPEED_MIN 35
#endif

View File

@ -468,6 +468,8 @@ Michael Carr
Eric Clayton
Marko Pahlke
Vytenis Sabelka
Nicolas Pitre
Benedikt Goos
The libmad team
The wavpack team

View File

@ -421,3 +421,13 @@ and not easily noticable.
Rockbox uses highpass triangular distribution noise as the dithering noise
source, and a third order noise shaper.
}
\opt{swcodec}{
\section{Timestretch}
Enabling \setting{Timestretch} allows you to change the playback speed without it
affecting the pitch of the recording.
After enabling this feature and rebooting, you can access this via the \setting{Pitch Screen}.
This function is intended for speech playback and may significantly dilute your listening
experience with more complex audio.
}

View File

@ -239,44 +239,78 @@ Delete the currently playing file.
\nopt{player}{
\subsubsection{\label{sec:pitchscreen}Pitch}
The \setting{Pitch Screen} allows you to change the pitch and (at the same
time) the playback speed of your \dap. The pitch value can be adjusted
between 50\% and 200\%. 50\% means half the normal playback speed and the
pitch that is an octave lower than the normal pitch. 200\% means double
playback speed and the pitch that is an octave higher than the normal pitch.
It is not possible to change the pitch without changing the playback speed and
vice versa. Changing the pitch can be done in two modes: procentual and
semitone. Initially (after the \dap{} is switched on), procentual mode
is active.
\begin{table}
\begin{btnmap}{}{}
\ActionPsToggleMode
& Toggle pitch changing mode \\
%
\ActionPsIncSmall{} / \ActionPsDecSmall
& Increase / Decrease pitch by 0.1\% (in procentual mode) or a semitone
(in semitone mode)\\
%
\ActionPsIncBig{} / \ActionPsDecBig
& Increase / Decrease pitch by 1\% (in procentual mode) or a semitone
(in semitone mode)\\
%
\ActionPsNudgeRight{} / \ActionPsNudgeLeft
& Temporarily increase / decrease pitch by 2.0\% \\
%
\ActionPsReset
& Reset pitch to 100\% \\
%
\ActionPsExit
& Leave the Pitch Screen \\
%
\end{btnmap}
\end{table}
The \setting{Pitch Screen} allows you to change the pitch and the playback
speed of your \dap. The pitch value can be adjusted between 50\% and 200\%.
50\% means half the normal playback speed and the pitch that is an octave lower
than the normal pitch. 200\% means double playback speed and the pitch that
is an octave higher than the normal pitch.
\opt{masf}{
Changing the pitch can be done in two modes: procentual and semitone.
Initially (after the \dap{} is switched on), procentual mode is active.
\begin{table}
\begin{btnmap}{}{}
\ActionPsToggleMode
& Toggle pitch changing mode \\
%
\ActionPsIncSmall{} / \ActionPsDecSmall
& Increase / Decrease pitch by 0.1\% (in procentual mode) or a semitone
(in semitone mode)\\
%
\ActionPsIncBig{} / \ActionPsDecBig
& Increase / Decrease pitch by 1\% (in procentual mode) or a semitone
(in semitone mode)\\
%
\ActionPsNudgeLeft{} / \ActionPsNudgeRight
& Temporarily change pitch by 2.0\% (beatmatch) \\
%
\ActionPsReset
& Reset pitch to 100\% \\
%
\ActionPsExit
& Leave the Pitch Screen \\
%
\end{btnmap}
\end{table}
\opt{MASCODEC}{
\warn{Changing the pitch can cause audible 'Artifacts' or 'Dropouts'.}
}
\opt{swcodec}{
Changing the pitch can be done in three modes: procentual, semitone and
timestretch. Initially (after the \dap{} is switched on), procentual mode is active.
Timestretch mode allows you to change the playback speed of your recording without
affecting the pitch, and vice versa. To access this you must enable the \setting{Timestretch}
option in \setting{Sound Settings} and reboot.
\begin{table}
\begin{btnmap}{}{}
\ActionPsToggleMode
& Toggle pitch changing mode \\
%
\ActionPsIncSmall{} / \ActionPsDecSmall
& Increase / Decrease pitch by 0.1\% (in procentual mode) or a semitone
(in semitone mode)\\
%
\ActionPsIncBig{} / \ActionPsDecBig
& Increase / Decrease pitch by 1\% (in procentual mode) or a semitone
(in semitone mode)\\
%
\ActionPsNudgeLeft{} / \ActionPsNudgeRight
& Temporarily change pitch by 2.0\% (beatmatch), or modify speed (in timestretch mode) \\
%
\ActionPsReset
& Reset pitch and speed to 100\% \\
%
\ActionPsExit
& Leave the Pitch Screen \\
%
\end{btnmap}
\end{table}
}
}
%********************QUICKSCREENS***********************************************