summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorAlexander Levin <al.le@rockbox.org>2009-07-11 16:46:19 +0000
committerAlexander Levin <al.le@rockbox.org>2009-07-11 16:46:19 +0000
commitcc7c665d9b5e6d801f248799dabe05e3729bb1c8 (patch)
tree3c82c6774acc78e814bad736496c693877295866 /apps
parent17ac0d7ff9604664a1894fd49883e44291f06451 (diff)
Improvements to the pitch screen UI (FS#10359 by David Johnston)
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@21781 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps')
-rw-r--r--apps/dsp.c18
-rw-r--r--apps/dsp.h8
-rw-r--r--apps/gui/pitchscreen.c681
-rw-r--r--apps/gui/pitchscreen.h5
-rw-r--r--apps/lang/english.lang42
-rw-r--r--apps/plugin.h6
-rw-r--r--apps/settings.h5
-rw-r--r--apps/settings_list.c9
-rw-r--r--apps/tdspeed.c11
-rw-r--r--apps/tdspeed.h19
10 files changed, 649 insertions, 155 deletions
diff --git a/apps/dsp.c b/apps/dsp.c
index 30b4ed357b..ec59417621 100644
--- a/apps/dsp.c
+++ b/apps/dsp.c
@@ -162,7 +162,7 @@ struct dsp_config
int sample_depth;
int sample_bytes;
int stereo_mode;
- int tdspeed_percent; /* Speed % */
+ int32_t tdspeed_percent; /* Speed% * PITCH_SPEED_PRECISION */
bool tdspeed_active; /* Timestretch is in use */
int frac_bits;
#ifdef HAVE_SW_TONE_CONTROLS
@@ -205,7 +205,7 @@ static int treble; /* A/V */
#endif
/* Settings applicable to audio codec only */
-static int pitch_ratio = 1000;
+static int32_t pitch_ratio = PITCH_SPEED_100;
static int channels_mode;
long dsp_sw_gain;
long dsp_sw_cross;
@@ -254,14 +254,14 @@ static inline int32_t clip_sample_16(int32_t sample)
return sample;
}
-int sound_get_pitch(void)
+int32_t sound_get_pitch(void)
{
return pitch_ratio;
}
-void sound_set_pitch(int permille)
+void sound_set_pitch(int32_t percent)
{
- pitch_ratio = permille;
+ pitch_ratio = percent;
dsp_configure(&AUDIO_DSP, DSP_SWITCH_FREQUENCY,
AUDIO_DSP.codec_frequency);
}
@@ -277,7 +277,7 @@ static void tdspeed_setup(struct dsp_config *dspc)
if(!dsp_timestretch_available())
return; /* Timestretch not enabled or buffer not allocated */
if (dspc->tdspeed_percent == 0)
- dspc->tdspeed_percent = 100;
+ dspc->tdspeed_percent = PITCH_SPEED_100;
if (!tdspeed_config(
dspc->codec_frequency == 0 ? NATIVE_FREQUENCY : dspc->codec_frequency,
dspc->stereo_mode != STEREO_MONO,
@@ -312,13 +312,13 @@ void dsp_timestretch_enable(bool enabled)
}
}
-void dsp_set_timestretch(int percent)
+void dsp_set_timestretch(int32_t percent)
{
AUDIO_DSP.tdspeed_percent = percent;
tdspeed_setup(&AUDIO_DSP);
}
-int dsp_get_timestretch()
+int32_t dsp_get_timestretch()
{
return AUDIO_DSP.tdspeed_percent;
}
@@ -1347,7 +1347,7 @@ intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value)
not need this feature.
*/
if (dsp == &AUDIO_DSP)
- dsp->frequency = pitch_ratio * dsp->codec_frequency / 1000;
+ dsp->frequency = pitch_ratio * dsp->codec_frequency / PITCH_SPEED_100;
else
dsp->frequency = dsp->codec_frequency;
diff --git a/apps/dsp.h b/apps/dsp.h
index 3d24b24245..7d1e2b3ddc 100644
--- a/apps/dsp.h
+++ b/apps/dsp.h
@@ -83,10 +83,10 @@ void dsp_set_eq_coefs(int band);
void dsp_dither_enable(bool enable);
void dsp_timestretch_enable(bool enable);
bool dsp_timestretch_available(void);
-void sound_set_pitch(int r);
-int sound_get_pitch(void);
-void dsp_set_timestretch(int percent);
-int dsp_get_timestretch(void);
+void sound_set_pitch(int32_t r);
+int32_t sound_get_pitch(void);
+void dsp_set_timestretch(int32_t percent);
+int32_t dsp_get_timestretch(void);
int dsp_callback(int msg, intptr_t param);
#endif
diff --git a/apps/gui/pitchscreen.c b/apps/gui/pitchscreen.c
index 16fac0c3b5..a699d4a7b4 100644
--- a/apps/gui/pitchscreen.c
+++ b/apps/gui/pitchscreen.c
@@ -22,6 +22,7 @@
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
+#include <math.h>
#include "config.h"
#include "sprintf.h"
#include "action.h"
@@ -36,24 +37,27 @@
#include "system.h"
#include "misc.h"
#include "pitchscreen.h"
+#include "settings.h"
#if CONFIG_CODEC == SWCODEC
#include "tdspeed.h"
#endif
+#define ABS(x) ((x) > 0 ? (x) : -(x))
#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
-#define PITCH_SMALL_DELTA 1
-#define PITCH_BIG_DELTA 10
-#define PITCH_NUDGE_DELTA 20
+#define PITCH_MAX (200 * PITCH_SPEED_PRECISION)
+#define PITCH_MIN (50 * PITCH_SPEED_PRECISION)
+#define PITCH_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
+#define PITCH_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
+#define PITCH_NUDGE_DELTA (2 * PITCH_SPEED_PRECISION) /* 2% */
-static bool pitch_mode_semitone = false;
-#if CONFIG_CODEC == SWCODEC
-static bool pitch_mode_timestretch = false;
-#endif
+#define SPEED_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
+#define SPEED_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
+
+#define SEMITONE_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* 10 cents */
+#define SEMITONE_BIG_DELTA PITCH_SPEED_PRECISION /* 1 semitone */
enum
{
@@ -63,25 +67,111 @@ enum
PITCH_ITEM_COUNT,
};
+
+/* This is a table of semitone percentage values of the appropriate
+ precision (based on PITCH_SPEED_PRECISION). Note that these are
+ all constant expressions, which will be evaluated at compile time,
+ so no need to worry about how complex the expressions look.
+ That's just to get the precision right.
+
+ I calculated these values, starting from 50, as
+
+ x(n) = 50 * 2^(n/12)
+
+ All that math in each entry simply converts the float constant
+ to an integer equal to PITCH_SPEED_PRECISION times the float value,
+ with as little precision loss as possible.
+*/
+#define SEMITONE_VALUE(x) \
+ ( (int)(((x) + 0.5 / PITCH_SPEED_PRECISION) * PITCH_SPEED_PRECISION) )
+
+static const int semitone_table[] =
+{
+ SEMITONE_VALUE(50),
+ SEMITONE_VALUE(52.97315472),
+ SEMITONE_VALUE(56.12310242),
+ SEMITONE_VALUE(59.46035575),
+ SEMITONE_VALUE(62.99605249),
+ SEMITONE_VALUE(66.74199271),
+ SEMITONE_VALUE(70.71067812),
+ SEMITONE_VALUE(74.91535384),
+ SEMITONE_VALUE(79.3700526 ),
+ SEMITONE_VALUE(84.08964153),
+ SEMITONE_VALUE(89.08987181),
+ SEMITONE_VALUE(94.38743127),
+ SEMITONE_VALUE(100 ),
+ SEMITONE_VALUE(105.9463094),
+ SEMITONE_VALUE(112.2462048),
+ SEMITONE_VALUE(118.9207115),
+ SEMITONE_VALUE(125.992105 ),
+ SEMITONE_VALUE(133.4839854),
+ SEMITONE_VALUE(141.4213562),
+ SEMITONE_VALUE(149.8307077),
+ SEMITONE_VALUE(158.7401052),
+ SEMITONE_VALUE(168.1792831),
+ SEMITONE_VALUE(178.1797436),
+ SEMITONE_VALUE(188.7748625),
+ SEMITONE_VALUE(200 )
+};
+
+#define NUM_SEMITONES ((int)(sizeof(semitone_table) / sizeof(int)))
+#define SEMITONE_START -12
+#define SEMITONE_END 12
+
+/* A table of values for approximating the cent curve with
+ linear interpolation. Multipy the next lowest semitone
+ by this much to find the corresponding cent percentage.
+
+ These values were calculated as
+ x(n) = 100 * 2^(n * 20/1200)
+*/
+
+#define CENT_INTERP(x) \
+ ( (int)(((x) + 0.5 / PITCH_SPEED_PRECISION) * PITCH_SPEED_PRECISION) )
+
+
+static const int cent_interp[] =
+{
+ PITCH_SPEED_100,
+ CENT_INTERP(101.1619440),
+ CENT_INTERP(102.3373892),
+ CENT_INTERP(103.5264924),
+ CENT_INTERP(104.7294123),
+ /* this one's the next semitone but we have it here for convenience */
+ CENT_INTERP(105.9463094),
+};
+
+/* Number of cents between entries in the cent_interp table */
+#define CENT_INTERP_INTERVAL 20
+#define CENT_INTERP_NUM ((int)(sizeof(cent_interp)/sizeof(int)))
+
+/* This stores whether the pitch and speed are at their own limits */
+/* or that of the timestretching algorithm */
+static bool at_limit = false;
+
static void pitchscreen_fix_viewports(struct viewport *parent,
struct viewport pitch_viewports[PITCH_ITEM_COUNT])
{
- int i, height;
- height = font_get(parent->font)->height;
+ int i, font_height;
+ font_height = font_get(parent->font)->height;
for (i = 0; i < PITCH_ITEM_COUNT; i++)
{
pitch_viewports[i] = *parent;
- pitch_viewports[i].height = height;
+ pitch_viewports[i].height = font_height;
}
pitch_viewports[PITCH_TOP].y += ICON_BORDER;
pitch_viewports[PITCH_MID].x += ICON_BORDER;
pitch_viewports[PITCH_MID].width = parent->width - ICON_BORDER*2;
- pitch_viewports[PITCH_MID].height = height * 2;
+ pitch_viewports[PITCH_MID].height = parent->height - ICON_BORDER*2
+ - font_height * 2;
+ if(pitch_viewports[PITCH_MID].height < font_height * 2)
+ pitch_viewports[PITCH_MID].height = font_height * 2;
pitch_viewports[PITCH_MID].y += parent->height / 2 -
pitch_viewports[PITCH_MID].height / 2;
- pitch_viewports[PITCH_BOTTOM].y += parent->height - height - ICON_BORDER;
+ pitch_viewports[PITCH_BOTTOM].y += parent->height - font_height
+ - ICON_BORDER;
}
/* must be called before pitchscreen_draw, or within
@@ -107,9 +197,9 @@ static void pitchscreen_draw_icons(struct screen *display,
static void pitchscreen_draw(struct screen *display, int max_lines,
struct viewport pitch_viewports[PITCH_ITEM_COUNT],
- int pitch
+ int32_t pitch, int32_t semitone
#if CONFIG_CODEC == SWCODEC
- ,int speed
+ ,int32_t speed
#endif
)
{
@@ -123,7 +213,7 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
{
/* UP: Pitch Up */
display->set_viewport(&pitch_viewports[PITCH_TOP]);
- if (pitch_mode_semitone)
+ if (global_settings.pitch_mode_semitone)
ptr = str(LANG_PITCH_UP_SEMITONE);
else
ptr = str(LANG_PITCH_UP);
@@ -136,7 +226,7 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
/* DOWN: Pitch Down */
display->set_viewport(&pitch_viewports[PITCH_BOTTOM]);
- if (pitch_mode_semitone)
+ if (global_settings.pitch_mode_semitone)
ptr = str(LANG_PITCH_DOWN_SEMITONE);
else
ptr = str(LANG_PITCH_DOWN);
@@ -157,55 +247,95 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
if ((show_lang_pitch = (max_lines >= 3)))
{
#if CONFIG_CODEC == SWCODEC
- if (!pitch_mode_timestretch)
+ if(global_settings.pitch_mode_timestretch)
{
-#endif
- /* LANG_PITCH */
- snprintf(buf, sizeof(buf), "%s", str(LANG_PITCH));
-#if CONFIG_CODEC == SWCODEC
+ /* Pitch:XXX.X% */
+ if(global_settings.pitch_mode_semitone)
+ {
+ snprintf(buf, sizeof(buf), "%s: %s%ld.%02ld", str(LANG_PITCH),
+ semitone >= 0 ? "+" : "-",
+ ABS(semitone / PITCH_SPEED_PRECISION),
+ ABS((semitone % PITCH_SPEED_PRECISION) /
+ (PITCH_SPEED_PRECISION / 100))
+ );
+ }
+ else
+ {
+ snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_PITCH),
+ pitch / PITCH_SPEED_PRECISION,
+ (pitch % PITCH_SPEED_PRECISION) /
+ (PITCH_SPEED_PRECISION / 10));
+ }
}
else
+#endif
{
- /* Pitch:XXX.X% */
- snprintf(buf, sizeof(buf), "%s:%d.%d%%", str(LANG_PITCH),
- pitch / 10, pitch % 10);
+ /* Rate */
+ snprintf(buf, sizeof(buf), "%s:", str(LANG_PLAYBACK_RATE));
}
-#endif
display->getstringsize(buf, &w, &h);
- display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
- 0, buf);
+ display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
+ (pitch_viewports[PITCH_MID].height / 2) - h, buf);
if (w > width_used)
width_used = w;
}
/* Middle section lower line */
+ /* "Speed:XXX%" */
#if CONFIG_CODEC == SWCODEC
- if (!pitch_mode_timestretch)
+ if(global_settings.pitch_mode_timestretch)
{
-#endif
- /* "XXX.X%" */
- snprintf(buf, sizeof(buf), "%d.%d%%",
- pitch / 10, pitch % 10);
-#if CONFIG_CODEC == SWCODEC
+ snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_SPEED),
+ speed / PITCH_SPEED_PRECISION,
+ (speed % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
}
else
+#endif
{
- /* "Speed:XXX%" */
- snprintf(buf, sizeof(buf), "%s:%d%%", str(LANG_SPEED),
- speed / 1000);
+ if(global_settings.pitch_mode_semitone)
+ {
+ snprintf(buf, sizeof(buf), "%s%ld.%02ld",
+ semitone >= 0 ? "+" : "-",
+ ABS(semitone / PITCH_SPEED_PRECISION),
+ ABS((semitone % PITCH_SPEED_PRECISION) /
+ (PITCH_SPEED_PRECISION / 100))
+ );
+ }
+ else
+ {
+ snprintf(buf, sizeof(buf), "%ld.%ld%%",
+ pitch / PITCH_SPEED_PRECISION,
+ (pitch % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
+ }
}
-#endif
+
display->getstringsize(buf, &w, &h);
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
- (show_lang_pitch ? h : h/2), buf);
+ show_lang_pitch ? (pitch_viewports[PITCH_MID].height / 2) :
+ (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
+ buf);
if (w > width_used)
width_used = w;
+ /* "limit" and "timestretch" labels */
+ if (max_lines >= 7)
+ {
+ if(at_limit)
+ {
+ snprintf(buf, sizeof(buf), "%s", str(LANG_STRETCH_LIMIT));
+ display->getstringsize(buf, &w, &h);
+ display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
+ (pitch_viewports[PITCH_MID].height / 2) + h, 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_timestretch)
+ if (global_settings.pitch_mode_timestretch)
{
leftlabel = "<<";
rightlabel = ">>";
@@ -220,37 +350,67 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
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->putsxy(0, (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
+ leftlabel);
+ display->putsxy((pitch_viewports[PITCH_MID].width - w),
+ (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
+ rightlabel);
}
display->update_viewport();
display->set_viewport(NULL);
}
-static int pitch_increase(int pitch, int pitch_delta, bool allow_cutoff)
+static int32_t pitch_increase(int32_t pitch, int32_t pitch_delta, bool allow_cutoff
+#if CONFIG_CODEC == SWCODEC
+ /* need this to maintain correct pitch/speed caps */
+ , int32_t speed
+#endif
+ )
{
- int new_pitch;
+ int32_t new_pitch;
+#if CONFIG_CODEC == SWCODEC
+ int32_t new_stretch;
+#endif
+ at_limit = false;
if (pitch_delta < 0)
{
- if (pitch + pitch_delta >= PITCH_MIN)
- new_pitch = pitch + pitch_delta;
- else
+ /* for large jumps, snap up to whole numbers */
+ if(allow_cutoff && pitch_delta <= -PITCH_SPEED_PRECISION &&
+ (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
+ {
+ pitch_delta += PITCH_SPEED_PRECISION - ((pitch + pitch_delta) % PITCH_SPEED_PRECISION);
+ }
+
+ new_pitch = pitch + pitch_delta;
+
+ if (new_pitch < PITCH_MIN)
{
if (!allow_cutoff)
+ {
return pitch;
+ }
new_pitch = PITCH_MIN;
+ at_limit = true;
}
}
else if (pitch_delta > 0)
{
- if (pitch + pitch_delta <= PITCH_MAX)
- new_pitch = pitch + pitch_delta;
- else
+ /* for large jumps, snap down to whole numbers */
+ if(allow_cutoff && pitch_delta >= PITCH_SPEED_PRECISION &&
+ (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
+ {
+ pitch_delta -= (pitch + pitch_delta) % PITCH_SPEED_PRECISION;
+ }
+
+ new_pitch = pitch + pitch_delta;
+
+ if (new_pitch > PITCH_MAX)
{
if (!allow_cutoff)
return pitch;
new_pitch = PITCH_MAX;
+ at_limit = true;
}
}
else
@@ -258,47 +418,164 @@ static int pitch_increase(int pitch, int pitch_delta, bool allow_cutoff)
/* pitch_delta == 0 -> no real change */
return pitch;
}
+#if CONFIG_CODEC == SWCODEC
+ if (dsp_timestretch_available())
+ {
+ /* increase the multiple to increase precision of this calculation */
+ new_stretch = GET_STRETCH(new_pitch, speed);
+ if(new_stretch < STRETCH_MIN)
+ {
+ /* we have to ignore allow_cutoff, because we can't have the */
+ /* stretch go higher than STRETCH_MAX */
+ new_pitch = GET_PITCH(speed, STRETCH_MIN);
+ }
+ else if(new_stretch > STRETCH_MAX)
+ {
+ /* we have to ignore allow_cutoff, because we can't have the */
+ /* stretch go higher than STRETCH_MAX */
+ new_pitch = GET_PITCH(speed, STRETCH_MAX);
+ }
+
+ if(new_stretch >= STRETCH_MAX ||
+ new_stretch <= STRETCH_MIN)
+ {
+ at_limit = true;
+ }
+ }
+#endif
+
sound_set_pitch(new_pitch);
return new_pitch;
}
-/* Factor for changing the pitch one half tone up.
- The exact value is 2^(1/12) = 1.05946309436
- But we use only integer arithmetics, so take
- rounded factor multiplied by 10^5=100,000. This is
- enough to get the same promille values as if we
- had used floating point (checked with a spread
- sheet).
- */
-#define PITCH_SEMITONE_FACTOR 105946L
-
-/* Some helpful constants. K is the scaling factor for SEMITONE.
- N is for more accurate rounding
- KN is K * N
- */
-#define PITCH_K_FCT 100000UL
-#define PITCH_N_FCT 10
-#define PITCH_KN_FCT 1000000UL
-
-static int pitch_increase_semitone(int pitch, bool up)
+static int32_t get_semitone_from_pitch(int32_t pitch)
{
- uint32_t tmp;
- uint32_t round_fct; /* How much to scale down at the end */
- tmp = pitch;
- if (up)
+ int semitone = 0;
+ int32_t fractional_index = 0;
+
+ while(semitone < NUM_SEMITONES - 1 &&
+ pitch >= semitone_table[semitone + 1])
+ {
+ semitone++;
+ }
+
+
+ /* now find the fractional part */
+ while(pitch > (cent_interp[fractional_index + 1] *
+ semitone_table[semitone] / PITCH_SPEED_100))
{
- tmp = tmp * PITCH_SEMITONE_FACTOR;
- round_fct = PITCH_K_FCT;
+ /* Check to make sure fractional_index isn't too big */
+ /* This should never happen. */
+ if(fractional_index >= CENT_INTERP_NUM - 1)
+ {
+ break;
+ }
+ fractional_index++;
+ }
+
+ int32_t semitone_pitch_a = cent_interp[fractional_index] *
+ semitone_table[semitone] /
+ PITCH_SPEED_100;
+ int32_t semitone_pitch_b = cent_interp[fractional_index + 1] *
+ semitone_table[semitone] /
+ PITCH_SPEED_100;
+ /* this will be the integer offset from the cent_interp entry */
+ int32_t semitone_frac_ofs = (pitch - semitone_pitch_a) * CENT_INTERP_INTERVAL /
+ (semitone_pitch_b - semitone_pitch_a);
+ semitone = (semitone + SEMITONE_START) * PITCH_SPEED_PRECISION +
+ fractional_index * CENT_INTERP_INTERVAL +
+ semitone_frac_ofs;
+
+ return semitone;
+}
+
+static int32_t get_pitch_from_semitone(int32_t semitone)
+{
+ int32_t adjusted_semitone = semitone - SEMITONE_START * PITCH_SPEED_PRECISION;
+
+ /* Find the index into the semitone table */
+ int32_t semitone_index = (adjusted_semitone / PITCH_SPEED_PRECISION);
+
+ /* set pitch to the semitone's integer part value */
+ int32_t pitch = semitone_table[semitone_index];
+ /* get the range of the cent modification for future calculation */
+ int32_t pitch_mod_a =
+ cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
+ CENT_INTERP_INTERVAL];
+ int32_t pitch_mod_b =
+ cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
+ CENT_INTERP_INTERVAL + 1];
+ /* figure out the cent mod amount based on the semitone fractional value */
+ int32_t pitch_mod = pitch_mod_a + (pitch_mod_b - pitch_mod_a) *
+ (adjusted_semitone % CENT_INTERP_INTERVAL) / CENT_INTERP_INTERVAL;
+
+ /* modify pitch based on the mod amount we just calculated */
+ return (pitch * pitch_mod + PITCH_SPEED_100 / 2) / PITCH_SPEED_100;
+}
+
+static int32_t pitch_increase_semitone(int32_t pitch,
+ int32_t current_semitone,
+ int32_t semitone_delta
+#if CONFIG_CODEC == SWCODEC
+ , int32_t speed
+#endif
+ )
+{
+ int32_t new_semitone = current_semitone;
+
+ /* snap to the delta interval */
+ if(current_semitone % semitone_delta != 0)
+ {
+ if(current_semitone > 0 && semitone_delta > 0)
+ new_semitone += semitone_delta;
+ else if(current_semitone < 0 && semitone_delta < 0)
+ new_semitone += semitone_delta;
+
+ new_semitone -= new_semitone % semitone_delta;
}
else
+ new_semitone += semitone_delta;
+
+ /* clamp the pitch so it doesn't go beyond the pitch limits */
+ if(new_semitone < (SEMITONE_START * PITCH_SPEED_PRECISION))
+ {
+ new_semitone = SEMITONE_START * PITCH_SPEED_PRECISION;
+ at_limit = true;
+ }
+ else if(new_semitone > (SEMITONE_END * PITCH_SPEED_PRECISION))
{
- tmp = (tmp * PITCH_KN_FCT) / PITCH_SEMITONE_FACTOR;
- round_fct = PITCH_N_FCT;
+ new_semitone = SEMITONE_END * PITCH_SPEED_PRECISION;
+ at_limit = true;
}
- /* Scaling down with rounding */
- tmp = (tmp + round_fct / 2) / round_fct;
- return pitch_increase(pitch, tmp - pitch, false);
+
+ int32_t new_pitch = get_pitch_from_semitone(new_semitone);
+
+#if CONFIG_CODEC == SWCODEC
+ int32_t new_stretch = GET_STRETCH(new_pitch, speed);
+
+ /* clamp the pitch so it doesn't go beyond the stretch limits */
+ if( new_stretch > STRETCH_MAX)
+ {
+ new_pitch = GET_PITCH(speed, STRETCH_MAX);
+ new_semitone = get_semitone_from_pitch(new_pitch);
+ at_limit = true;
+ }
+ else if (new_stretch < STRETCH_MIN)
+ {
+ new_pitch = GET_PITCH(speed, STRETCH_MIN);
+ new_semitone = get_semitone_from_pitch(new_pitch);
+ at_limit = true;
+ }
+#endif
+
+ pitch_increase(pitch, new_pitch - pitch, false
+#if CONFIG_CODEC == SWCODEC
+ , speed
+#endif
+ );
+
+ return new_semitone;
}
/*
@@ -310,13 +587,11 @@ static int pitch_increase_semitone(int pitch, bool up)
int gui_syncpitchscreen_run(void)
{
int button, i;
- int pitch = sound_get_pitch();
-#if CONFIG_CODEC == SWCODEC
- int stretch = dsp_get_timestretch();
- int speed = stretch * pitch; /* speed to maintain */
-#endif
- int new_pitch;
- int pitch_delta;
+ int32_t pitch = sound_get_pitch();
+ int32_t semitone;
+
+ int32_t new_pitch;
+ int32_t pitch_delta;
bool nudged = false;
bool exit = false;
/* should maybe be passed per parameter later, not needed for now */
@@ -324,6 +599,31 @@ int gui_syncpitchscreen_run(void)
struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT];
int max_lines[NB_SCREENS];
+#if CONFIG_CODEC == SWCODEC
+ int32_t new_speed = 0, new_stretch;
+
+ /* the speed variable holds the apparent speed of the playback */
+ int32_t speed;
+ if (dsp_timestretch_available())
+ {
+ speed = GET_SPEED(pitch, dsp_get_timestretch());
+ }
+ else
+ {
+ speed = pitch;
+ }
+
+ /* Figure out whether to be in timestretch mode */
+ if (global_settings.pitch_mode_timestretch && !dsp_timestretch_available())
+ {
+ global_settings.pitch_mode_timestretch = false;
+ settings_save();
+ }
+#endif
+
+ /* set the semitone index based on the current pitch */
+ semitone = get_semitone_from_pitch(pitch);
+
/* initialize pitchscreen vps */
FOR_NB_SCREENS(i)
{
@@ -343,49 +643,80 @@ int gui_syncpitchscreen_run(void)
{
FOR_NB_SCREENS(i)
pitchscreen_draw(&screens[i], max_lines[i],
- pitch_viewports[i], pitch
+ pitch_viewports[i], pitch, semitone
#if CONFIG_CODEC == SWCODEC
, speed
#endif
);
pitch_delta = 0;
+#if CONFIG_CODEC == SWCODEC
+ new_speed = 0;
+#endif
button = get_action(CONTEXT_PITCHSCREEN, HZ);
switch (button)
{
case ACTION_PS_INC_SMALL:
- pitch_delta = PITCH_SMALL_DELTA;
+ if(global_settings.pitch_mode_semitone)
+ pitch_delta = SEMITONE_SMALL_DELTA;
+ else
+ pitch_delta = PITCH_SMALL_DELTA;
break;
case ACTION_PS_INC_BIG:
- pitch_delta = PITCH_BIG_DELTA;
+ if(global_settings.pitch_mode_semitone)
+ pitch_delta = SEMITONE_BIG_DELTA;
+ else
+ pitch_delta = PITCH_BIG_DELTA;
break;
case ACTION_PS_DEC_SMALL:
- pitch_delta = -PITCH_SMALL_DELTA;
+ if(global_settings.pitch_mode_semitone)
+ pitch_delta = -SEMITONE_SMALL_DELTA;
+ else
+ pitch_delta = -PITCH_SMALL_DELTA;
break;
case ACTION_PS_DEC_BIG:
- pitch_delta = -PITCH_BIG_DELTA;
+ if(global_settings.pitch_mode_semitone)
+ pitch_delta = -SEMITONE_BIG_DELTA;
+ else
+ pitch_delta = -PITCH_BIG_DELTA;
break;
case ACTION_PS_NUDGE_RIGHT:
#if CONFIG_CODEC == SWCODEC
- if (!pitch_mode_timestretch)
+ if (!global_settings.pitch_mode_timestretch)
{
#endif
- new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false);
+ new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
+#if CONFIG_CODEC == SWCODEC
+ , speed
+#endif
+ );
nudged = (new_pitch != pitch);
pitch = new_pitch;
+ semitone = get_semitone_from_pitch(pitch);
+#if CONFIG_CODEC == SWCODEC
+ speed = pitch;
+#endif
break;
#if CONFIG_CODEC == SWCODEC
}
+ else
+ {
+ new_speed = speed + SPEED_SMALL_DELTA;
+ at_limit = false;
+ }
+ break;
case ACTION_PS_FASTER:
- if (pitch_mode_timestretch && stretch < STRETCH_MAX)
+ if (global_settings.pitch_mode_timestretch)
{
- stretch++;
- dsp_set_timestretch(stretch);
- speed = stretch * pitch;
+ new_speed = speed + SPEED_BIG_DELTA;
+ /* snap to whole numbers */
+ if(new_speed % PITCH_SPEED_PRECISION != 0)
+ new_speed -= new_speed % PITCH_SPEED_PRECISION;
+ at_limit = false;
}
break;
#endif
@@ -393,29 +724,53 @@ int gui_syncpitchscreen_run(void)
case ACTION_PS_NUDGE_RIGHTOFF:
if (nudged)
{
- pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false);
+ pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
+#if CONFIG_CODEC == SWCODEC
+ , speed
+#endif
+ );
+#if CONFIG_CODEC == SWCODEC
+ speed = pitch;
+#endif
+ semitone = get_semitone_from_pitch(pitch);
nudged = false;
}
break;
case ACTION_PS_NUDGE_LEFT:
#if CONFIG_CODEC == SWCODEC
- if (!pitch_mode_timestretch)
+ if (!global_settings.pitch_mode_timestretch)
{
#endif
- new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false);
+ new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
+#if CONFIG_CODEC == SWCODEC
+ , speed
+#endif
+ );
nudged = (new_pitch != pitch);
pitch = new_pitch;
+ semitone = get_semitone_from_pitch(pitch);
+#if CONFIG_CODEC == SWCODEC
+ speed = pitch;
+#endif
break;
#if CONFIG_CODEC == SWCODEC
}
+ else
+ {
+ new_speed = speed - SPEED_SMALL_DELTA;
+ at_limit = false;
+ }
+ break;
case ACTION_PS_SLOWER:
- if (pitch_mode_timestretch && stretch > STRETCH_MIN)
+ if (global_settings.pitch_mode_timestretch)
{
- stretch--;
- dsp_set_timestretch(stretch);
- speed = stretch * pitch;
+ new_speed = speed - SPEED_BIG_DELTA;
+ /* snap to whole numbers */
+ if(new_speed % PITCH_SPEED_PRECISION != 0)
+ new_speed += PITCH_SPEED_PRECISION - speed % PITCH_SPEED_PRECISION;
+ at_limit = false;
}
break;
#endif
@@ -423,27 +778,49 @@ int gui_syncpitchscreen_run(void)
case ACTION_PS_NUDGE_LEFTOFF:
if (nudged)
{
- pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false);
+ pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
+#if CONFIG_CODEC == SWCODEC
+ , speed
+#endif
+ );
+#if CONFIG_CODEC == SWCODEC
+ speed = pitch;
+#endif
+ semitone = get_semitone_from_pitch(pitch);
nudged = false;
}
break;
case ACTION_PS_RESET:
- pitch = 1000;
+ pitch = PITCH_SPEED_100;
sound_set_pitch(pitch);
#if CONFIG_CODEC == SWCODEC
- stretch = 100;
- dsp_set_timestretch(stretch);
- speed = stretch * pitch;
+ speed = PITCH_SPEED_100;
+ if (dsp_timestretch_available())
+ {
+ dsp_set_timestretch(PITCH_SPEED_100);
+ at_limit = false;
+ }
#endif
+ semitone = get_semitone_from_pitch(pitch);
break;
case ACTION_PS_TOGGLE_MODE:
+ global_settings.pitch_mode_semitone = !global_settings.pitch_mode_semitone;
#if CONFIG_CODEC == SWCODEC
- if (dsp_timestretch_available() && pitch_mode_semitone)
- pitch_mode_timestretch = !pitch_mode_timestretch;
+
+ if (dsp_timestretch_available() && !global_settings.pitch_mode_semitone)
+ {
+ global_settings.pitch_mode_timestretch = !global_settings.pitch_mode_timestretch;
+ if(!global_settings.pitch_mode_timestretch)
+ {
+ /* no longer in timestretch mode. Reset speed */
+ speed = pitch;
+ dsp_set_timestretch(PITCH_SPEED_100);
+ }
+ }
+ settings_save();
#endif
- pitch_mode_semitone = !pitch_mode_semitone;
break;
case ACTION_PS_EXIT:
@@ -457,27 +834,73 @@ int gui_syncpitchscreen_run(void)
}
if (pitch_delta)
{
- if (pitch_mode_semitone)
- pitch = pitch_increase_semitone(pitch, pitch_delta > 0);
+ if (global_settings.pitch_mode_semitone)
+ {
+ semitone = pitch_increase_semitone(pitch, semitone, pitch_delta
+#if CONFIG_CODEC == SWCODEC
+ , speed
+#endif
+ );
+ pitch = get_pitch_from_semitone(semitone);
+ }
else
- pitch = pitch_increase(pitch, pitch_delta, true);
+ {
+ pitch = pitch_increase(pitch, pitch_delta, true
#if CONFIG_CODEC == SWCODEC
- if (pitch_mode_timestretch)
+ , speed
+#endif
+ );
+ semitone = get_semitone_from_pitch(pitch);
+ }
+#if CONFIG_CODEC == SWCODEC
+ if (global_settings.pitch_mode_timestretch)
{
- /* Set stretch to maintain speed */
- /* i.e. increase pitch, reduce stretch */
- int new_stretch = speed / pitch;
- if (new_stretch >= STRETCH_MIN && new_stretch <= STRETCH_MAX)
- {
- stretch = new_stretch;
- dsp_set_timestretch(stretch);
- }
+ /* do this to make sure we properly obey the stretch limits */
+ new_speed = speed;
}
else
- speed = stretch * pitch;
-#endif
+ {
+ speed = pitch;
+ }
+#endif
}
- }
+
+#if CONFIG_CODEC == SWCODEC
+ if(new_speed)
+ {
+ new_stretch = GET_STRETCH(pitch, new_speed);
+
+ /* limit the amount of stretch */
+ if(new_stretch > STRETCH_MAX)
+ {
+ new_stretch = STRETCH_MAX;
+ new_speed = GET_SPEED(pitch, new_stretch);
+ }
+ else if(new_stretch < STRETCH_MIN)
+ {
+ new_stretch = STRETCH_MIN;
+ new_speed = GET_SPEED(pitch, new_stretch);
+ }
+
+ new_stretch = GET_STRETCH(pitch, new_speed);
+ if(new_stretch >= STRETCH_MAX ||
+ new_stretch <= STRETCH_MIN)
+ {
+ at_limit = true;
+ }
+
+ /* set the amount of stretch */
+ dsp_set_timestretch(new_stretch);
+
+ /* update the speed variable with the new speed */
+ speed = new_speed;
+
+ /* Reset new_speed so we only call dsp_set_timestretch */
+ /* when needed */
+ new_speed = 0;
+ }
+#endif
+}
#if CONFIG_CODEC == SWCODEC
pcmbuf_set_low_latency(false);
#endif
diff --git a/apps/gui/pitchscreen.h b/apps/gui/pitchscreen.h
index 41eb1fd415..e421c6559e 100644
--- a/apps/gui/pitchscreen.h
+++ b/apps/gui/pitchscreen.h
@@ -22,6 +22,11 @@
#ifndef _PITCHSCREEN_H_
#define _PITCHSCREEN_H_
+/* precision of the pitch and speed variables */
+/* One zero per decimal (100 means two decimal places */
+#define PITCH_SPEED_PRECISION 100L
+#define PITCH_SPEED_100 (100L * PITCH_SPEED_PRECISION) /* 100% speed */
+
int gui_syncpitchscreen_run(void);
#endif /* _PITCHSCREEN_H_ */
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 7d1e242c6c..68a562f002 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -12604,3 +12604,45 @@
remote: "Remote Statusbar"
</voice>
</phrase>
+<phrase>
+ id: LANG_SEMITONE
+ desc:
+ user: core
+ <source>
+ *: "Semitone"
+ </source>
+ <dest>
+ *: "Semitone"
+ </dest>
+ <voice>
+ *: "Semitone"
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_STRETCH_LIMIT
+ desc: "limit" in pitch screen
+ user: core
+ <source>
+ *: "Limit"
+ </source>
+ <dest>
+ *: "Limit"
+ </dest>
+ <voice>
+ *: "Limit"
+ </voice>
+</phrase>
+<phrase>
+ id: LANG_PLAYBACK_RATE
+ desc: "rate" in pitch screen
+ user: core
+ <source>
+ *: "Rate"
+ </source>
+ <dest>
+ *: "Rate"
+ </dest>
+ <voice>
+ *: "Rate"
+ </voice>
+</phrase>
diff --git a/apps/plugin.h b/apps/plugin.h
index d1a57129a5..bb74d73334 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -128,12 +128,12 @@ void* plugin_get_buffer(size_t *buffer_size);
#define PLUGIN_MAGIC 0x526F634B /* RocK */
/* increase this every time the api struct changes */
-#define PLUGIN_API_VERSION 159
+#define PLUGIN_API_VERSION 160
/* update this to latest version if a change to the api struct breaks
backwards compatibility (and please take the opportunity to sort in any
new function which are "waiting" at the end of the function table) */
-#define PLUGIN_MIN_API_VERSION 159
+#define PLUGIN_MIN_API_VERSION 160
/* plugin return codes */
enum plugin_status {
@@ -618,7 +618,7 @@ struct plugin_api {
#endif
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) || \
(CONFIG_CODEC == SWCODEC)
- void (*sound_set_pitch)(int pitch);
+ void (*sound_set_pitch)(int32_t pitch);
#endif
/* MAS communication */
diff --git a/apps/settings.h b/apps/settings.h
index 93a998ea1c..8cf9bcffdf 100644
--- a/apps/settings.h
+++ b/apps/settings.h
@@ -739,6 +739,11 @@ struct user_settings
struct touchscreen_parameter ts_calibration_data;
#endif
+ /* pitch screen settings */
+ bool pitch_mode_semitone;
+#if CONFIG_CODEC == SWCODEC
+ bool pitch_mode_timestretch;
+#endif
/* If values are just added to the end, no need to bump plugin API
version. */
/* new stuff to be added at the end */
diff --git a/apps/settings_list.c b/apps/settings_list.c
index 9cfd9aafc5..78d1fc8700 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -33,7 +33,6 @@
#include "settings_list.h"
#include "sound.h"
#include "dsp.h"
-#include "debug.h"
#include "mpeg.h"
#include "audio.h"
#include "power.h"
@@ -1528,6 +1527,14 @@ const struct settings_list settings[] = {
tsc_is_changed, tsc_set_default),
#endif
OFFON_SETTING(0, prevent_skip, LANG_PREVENT_SKIPPING, false, "prevent track skip", NULL),
+
+ OFFON_SETTING(0, pitch_mode_semitone, LANG_SEMITONE, false,
+ "Semitone pitch change", NULL),
+#if CONFIG_CODEC == SWCODEC
+ OFFON_SETTING(0, pitch_mode_timestretch, LANG_TIMESTRETCH, false,
+ "Timestretch mode", NULL),
+#endif
+
};
const int nb_settings = sizeof(settings)/sizeof(*settings);
diff --git a/apps/tdspeed.c b/apps/tdspeed.c
index 07f8beb132..cd01099a76 100644
--- a/apps/tdspeed.c
+++ b/apps/tdspeed.c
@@ -25,7 +25,6 @@
#include <stdio.h>
#include <string.h>
#include "buffer.h"
-#include "debug.h"
#include "system.h"
#include "tdspeed.h"
#include "settings.h"
@@ -72,7 +71,7 @@ void tdspeed_init()
}
-bool tdspeed_config(int samplerate, bool stereo, int factor)
+bool tdspeed_config(int samplerate, bool stereo, int32_t factor)
{
struct tdspeed_state_s *st = &tdspeed_state;
int src_frame_sz;
@@ -84,7 +83,7 @@ bool tdspeed_config(int samplerate, bool stereo, int factor)
return false;
/* Check parameters */
- if (factor == 100)
+ if (factor == PITCH_SPEED_100)
return false;
if (samplerate < MIN_RATE || samplerate > MAX_RATE)
return false;
@@ -94,14 +93,14 @@ bool tdspeed_config(int samplerate, bool stereo, int factor)
st->stereo = stereo;
st->dst_step = samplerate / MINFREQ;
- if (factor > 100)
- st->dst_step = st->dst_step * 100 / factor;
+ if (factor > PITCH_SPEED_100)
+ st->dst_step = st->dst_step * PITCH_SPEED_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->src_step = st->dst_step * factor / PITCH_SPEED_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;
diff --git a/apps/tdspeed.h b/apps/tdspeed.h
index 1a3df126f7..2fd9498448 100644
--- a/apps/tdspeed.h
+++ b/apps/tdspeed.h
@@ -23,15 +23,28 @@
#ifndef _TDSPEED_H
#define _TDSPEED_H
+#include "dsp.h"
+/* for the precision #defines: */
+#include "pitchscreen.h"
+
#define TDSPEED_OUTBUFSIZE 4096
+/* some #define functions to get the pitch, stretch and speed values based on */
+/* two known values. Remember that params are alphabetical. */
+#define GET_SPEED(pitch, stretch) \
+ ((pitch * stretch + PITCH_SPEED_100 / 2L) / PITCH_SPEED_100)
+#define GET_PITCH(speed, stretch) \
+ ((speed * PITCH_SPEED_100 + stretch / 2L) / stretch)
+#define GET_STRETCH(pitch, speed) \
+ ((speed * PITCH_SPEED_100 + pitch / 2L) / pitch)
+
void tdspeed_init(void);
-bool tdspeed_config(int samplerate, bool stereo, int factor);
+bool tdspeed_config(int samplerate, bool stereo, int32_t factor);
long tdspeed_est_output_size(void);
long tdspeed_est_input_size(long size);
int tdspeed_doit(int32_t *src[], int count);
-#define STRETCH_MAX 250
-#define STRETCH_MIN 35
+#define STRETCH_MAX (250L * PITCH_SPEED_PRECISION) /* 250% */
+#define STRETCH_MIN (35L * PITCH_SPEED_PRECISION) /* 35% */
#endif