From fb2380790edbdb2176a21fe28739a41fb978ce6e Mon Sep 17 00:00:00 2001 From: Steve Bavin Date: Fri, 12 Jun 2009 07:20:50 +0000 Subject: 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 --- apps/SOURCES | 1 + apps/action.h | 2 + apps/dsp.c | 300 +++++++++++++++++++++++----------- apps/dsp.h | 4 + apps/dsp_arm.S | 4 +- apps/dsp_asm.h | 8 +- apps/dsp_cf.S | 6 +- apps/gui/pitchscreen.c | 328 +++++++++++++++++++++++++++----------- apps/keymaps/keymap-av300.c | 2 + apps/keymaps/keymap-c100.c | 2 + apps/keymaps/keymap-c200.c | 10 +- apps/keymaps/keymap-clip.c | 26 +-- apps/keymaps/keymap-e200.c | 2 + apps/keymaps/keymap-fuze.c | 2 + apps/keymaps/keymap-gigabeat-s.c | 4 + apps/keymaps/keymap-gigabeat.c | 2 + apps/keymaps/keymap-h10.c | 4 + apps/keymaps/keymap-h1x0_h3x0.c | 8 + apps/keymaps/keymap-hdd1630.c | 6 +- apps/keymaps/keymap-iaudio67.c | 4 + apps/keymaps/keymap-ifp7xx.c | 2 + apps/keymaps/keymap-ipod.c | 2 + apps/keymaps/keymap-logikdax.c | 3 + apps/keymaps/keymap-m200.c | 2 + apps/keymaps/keymap-m3.c | 4 + apps/keymaps/keymap-meizu-m6sl.c | 2 + apps/keymaps/keymap-mr100.c | 2 + apps/keymaps/keymap-sa9200.c | 2 + apps/keymaps/keymap-touchscreen.c | 2 + apps/keymaps/keymap-x5.c | 4 + apps/lang/english.lang | 34 ++++ apps/menus/sound_menu.c | 27 +++- apps/settings.c | 1 + apps/settings.h | 1 + apps/settings_list.c | 4 + apps/tdspeed.c | 319 ++++++++++++++++++++++++++++++++++++ apps/tdspeed.h | 36 +++++ 37 files changed, 946 insertions(+), 226 deletions(-) create mode 100644 apps/tdspeed.c create mode 100644 apps/tdspeed.h (limited to 'apps') diff --git a/apps/SOURCES b/apps/SOURCES index 0fe001b242..527b0b20a9 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -126,6 +126,7 @@ pcmbuf.c playback.c codecs.c dsp.c +tdspeed.c #ifdef HAVE_RECORDING enc_config.c #ifndef SIMULATOR diff --git a/apps/action.h b/apps/action.h index 61c214c609..3e53b6d7a8 100644 --- a/apps/action.h +++ b/apps/action.h @@ -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, diff --git a/apps/dsp.c b/apps/dsp.c index cbae49ab69..b32b641693 100644 --- a/apps/dsp.c +++ b/apps/dsp.c @@ -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 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 WORD_SHIFT 12 +#define WORD_FRACBITS 27 + +#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; + + int chunk = MIN(sample_buf_count/2, samples); + chunk_offset += chunk; + samples -= chunk; + + if (dsp->apply_gain) + dsp->apply_gain(chunk, &dsp->data, t2); + + if (dsp->resample && (chunk = resample(dsp, chunk, t2)) <= 0) + break; /* I'm pretty sure we're downsampling here */ - if (dsp->apply_crossfeed) - dsp->apply_crossfeed(samples, tmp); + if (dsp->apply_crossfeed) + dsp->apply_crossfeed(chunk, t2); - if (dsp->eq_process) - dsp->eq_process(samples, tmp); + 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); } diff --git a/apps/dsp.h b/apps/dsp.h index c3239360b0..5c4211f251 100644 --- a/apps/dsp.h +++ b/apps/dsp.h @@ -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 diff --git a/apps/dsp_arm.S b/apps/dsp_arm.S index 5b43c3bb5b..11b7ba7141 100644 --- a/apps/dsp_arm.S +++ b/apps/dsp_arm.S @@ -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. diff --git a/apps/dsp_asm.h b/apps/dsp_asm.h index 64373d3eea..85db57b6ef 100644 --- a/apps/dsp_asm.h +++ b/apps/dsp_asm.h @@ -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 */ diff --git a/apps/dsp_cf.S b/apps/dsp_cf.S index d8869fcb09..cda811a7d5 100644 --- a/apps/dsp_cf.S +++ b/apps/dsp_cf.S @@ -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. */ diff --git a/apps/gui/pitchscreen.c b/apps/gui/pitchscreen.c index 485eb7861c..5072031652 100644 --- a/apps/gui/pitchscreen.c +++ b/apps/gui/pitchscreen.c @@ -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(); } - 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 */ + /* Middle section */ + display->set_viewport(&pitch_viewports[PITCH_MID]); display->clear_viewport(); + int width_used = 0; + + /* Middle section upper line - hide for a small screen */ if ((show_lang_pitch = (max_lines >= 3))) + { +#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); - - /* "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) + 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) { - /* RIGHT: +2% */ - display->putsxy(pitch_viewports[PITCH_MID].width - width_val, h /2, "+2%"); - /* LEFT: -2% */ - display->putsxy(0, h / 2, "-2%"); + 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 diff --git a/apps/keymaps/keymap-av300.c b/apps/keymaps/keymap-av300.c index 6c91499a50..1cca2c2a8b 100644 --- a/apps/keymaps/keymap-av300.c +++ b/apps/keymaps/keymap-av300.c @@ -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 */ diff --git a/apps/keymaps/keymap-c100.c b/apps/keymaps/keymap-c100.c index c8ba16cdfb..80948c0e19 100644 --- a/apps/keymaps/keymap-c100.c +++ b/apps/keymaps/keymap-c100.c @@ -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 */ diff --git a/apps/keymaps/keymap-c200.c b/apps/keymaps/keymap-c200.c index 56504584f8..c9e5d4394e 100644 --- a/apps/keymaps/keymap-c200.c +++ b/apps/keymaps/keymap-c200.c @@ -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 */ diff --git a/apps/keymaps/keymap-clip.c b/apps/keymaps/keymap-clip.c index 4b778beeb7..002cc3c36f 100644 --- a/apps/keymaps/keymap-clip.c +++ b/apps/keymaps/keymap-clip.c @@ -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 */ diff --git a/apps/keymaps/keymap-e200.c b/apps/keymaps/keymap-e200.c index a2017de6f3..569862827d 100644 --- a/apps/keymaps/keymap-e200.c +++ b/apps/keymaps/keymap-e200.c @@ -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 */ diff --git a/apps/keymaps/keymap-fuze.c b/apps/keymaps/keymap-fuze.c index 697598843b..783446429f 100644 --- a/apps/keymaps/keymap-fuze.c +++ b/apps/keymaps/keymap-fuze.c @@ -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 */ diff --git a/apps/keymaps/keymap-gigabeat-s.c b/apps/keymaps/keymap-gigabeat-s.c index 66b58615c5..da7baa29cb 100644 --- a/apps/keymaps/keymap-gigabeat-s.c +++ b/apps/keymaps/keymap-gigabeat-s.c @@ -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 */ diff --git a/apps/keymaps/keymap-gigabeat.c b/apps/keymaps/keymap-gigabeat.c index 31abce5bb2..3e5f404c71 100644 --- a/apps/keymaps/keymap-gigabeat.c +++ b/apps/keymaps/keymap-gigabeat.c @@ -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 */ diff --git a/apps/keymaps/keymap-h10.c b/apps/keymaps/keymap-h10.c index aed55dbfa2..0c1d4b2331 100644 --- a/apps/keymaps/keymap-h10.c +++ b/apps/keymaps/keymap-h10.c @@ -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 */ diff --git a/apps/keymaps/keymap-h1x0_h3x0.c b/apps/keymaps/keymap-h1x0_h3x0.c index e4eb6c8168..cd18bf0041 100644 --- a/apps/keymaps/keymap-h1x0_h3x0.c +++ b/apps/keymaps/keymap-h1x0_h3x0.c @@ -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) }; diff --git a/apps/keymaps/keymap-hdd1630.c b/apps/keymaps/keymap-hdd1630.c index deb77307b8..86a6a58797 100644 --- a/apps/keymaps/keymap-hdd1630.c +++ b/apps/keymaps/keymap-hdd1630.c @@ -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 */ diff --git a/apps/keymaps/keymap-iaudio67.c b/apps/keymaps/keymap-iaudio67.c index b0dac93386..a1eeb08886 100644 --- a/apps/keymaps/keymap-iaudio67.c +++ b/apps/keymaps/keymap-iaudio67.c @@ -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 */ diff --git a/apps/keymaps/keymap-ifp7xx.c b/apps/keymaps/keymap-ifp7xx.c index 06631438bc..3b09df49fc 100644 --- a/apps/keymaps/keymap-ifp7xx.c +++ b/apps/keymaps/keymap-ifp7xx.c @@ -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 */ diff --git a/apps/keymaps/keymap-ipod.c b/apps/keymaps/keymap-ipod.c index 65c5703451..8170e8c7e1 100644 --- a/apps/keymaps/keymap-ipod.c +++ b/apps/keymaps/keymap-ipod.c @@ -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 */ diff --git a/apps/keymaps/keymap-logikdax.c b/apps/keymaps/keymap-logikdax.c index fdf51e1652..cda6107b45 100644 --- a/apps/keymaps/keymap-logikdax.c +++ b/apps/keymaps/keymap-logikdax.c @@ -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 */ diff --git a/apps/keymaps/keymap-m200.c b/apps/keymaps/keymap-m200.c index 86144c0b0c..8318bd42e7 100644 --- a/apps/keymaps/keymap-m200.c +++ b/apps/keymaps/keymap-m200.c @@ -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 */ diff --git a/apps/keymaps/keymap-m3.c b/apps/keymaps/keymap-m3.c index 4da6855644..6b68f1f881 100644 --- a/apps/keymaps/keymap-m3.c +++ b/apps/keymaps/keymap-m3.c @@ -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 */ diff --git a/apps/keymaps/keymap-meizu-m6sl.c b/apps/keymaps/keymap-meizu-m6sl.c index ec19dd63a8..0741f632b7 100644 --- a/apps/keymaps/keymap-meizu-m6sl.c +++ b/apps/keymaps/keymap-meizu-m6sl.c @@ -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 */ diff --git a/apps/keymaps/keymap-mr100.c b/apps/keymaps/keymap-mr100.c index f9bb8c2419..4f178b7793 100644 --- a/apps/keymaps/keymap-mr100.c +++ b/apps/keymaps/keymap-mr100.c @@ -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 */ diff --git a/apps/keymaps/keymap-sa9200.c b/apps/keymaps/keymap-sa9200.c index 3091ec6156..c77f3e9bb4 100644 --- a/apps/keymaps/keymap-sa9200.c +++ b/apps/keymaps/keymap-sa9200.c @@ -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 */ diff --git a/apps/keymaps/keymap-touchscreen.c b/apps/keymaps/keymap-touchscreen.c index 70009794a5..b285446546 100644 --- a/apps/keymaps/keymap-touchscreen.c +++ b/apps/keymaps/keymap-touchscreen.c @@ -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 */ diff --git a/apps/keymaps/keymap-x5.c b/apps/keymaps/keymap-x5.c index 9495bf3306..4401f790d4 100644 --- a/apps/keymaps/keymap-x5.c +++ b/apps/keymaps/keymap-x5.c @@ -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 */ diff --git a/apps/lang/english.lang b/apps/lang/english.lang index ddddce328a..09cbdbfc09 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -12468,3 +12468,37 @@ *: "Prevent Track Skipping" + + id: LANG_TIMESTRETCH + desc: timestretch enable + user: core + + *: none + swcodec: "Timestretch" + + + *: none + swcodec: "Timestretch" + + + *: none + swcodec: "Timestretch" + + + + id: LANG_SPEED + desc: timestretch speed + user: core + + *: none + swcodec: "Speed" + + + *: none + swcodec: "Speed" + + + *: none + swcodec: "Speed" + + diff --git a/apps/menus/sound_menu.c b/apps/menus/sound_menu.c index 70730a1e5a..bfba8171da 100644 --- a/apps/menus/sound_menu.c +++ b/apps/menus/sound_menu.c @@ -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 + ,×tretch_enabled #endif #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) ,&loudness,&avc,&superbass,&mdb_enable,&mdb_strength diff --git a/apps/settings.c b/apps/settings.c index 6652141f9c..9594bd164e 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -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 diff --git a/apps/settings.h b/apps/settings.h index 3b13ff8e6b..6ccaeed92e 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -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 diff --git a/apps/settings_list.c b/apps/settings_list.c index 0485ec4ab3..74e2cab3cd 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -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, diff --git a/apps/tdspeed.c b/apps/tdspeed.c new file mode 100644 index 0000000000..67f749f6c3 --- /dev/null +++ b/apps/tdspeed.c @@ -0,0 +1,319 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 by Nicolas Pitre + * Copyright (C) 2006-2007 by Stéphane Doyon + * + * 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 +#include +#include +#include +#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; +} diff --git a/apps/tdspeed.h b/apps/tdspeed.h new file mode 100644 index 0000000000..6d7cecdcdf --- /dev/null +++ b/apps/tdspeed.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 by Nicolas Pitre + * Copyright (C) 2006-2007 by Stéphane Doyon + * + * 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 -- cgit