1
0
Fork 0
forked from len0rd/rockbox

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
This commit is contained in:
Alexander Levin 2009-07-11 16:46:19 +00:00
parent 17ac0d7ff9
commit cc7c665d9b
14 changed files with 701 additions and 192 deletions

View file

@ -162,7 +162,7 @@ struct dsp_config
int sample_depth; int sample_depth;
int sample_bytes; int sample_bytes;
int stereo_mode; int stereo_mode;
int tdspeed_percent; /* Speed % */ int32_t tdspeed_percent; /* Speed% * PITCH_SPEED_PRECISION */
bool tdspeed_active; /* Timestretch is in use */ bool tdspeed_active; /* Timestretch is in use */
int frac_bits; int frac_bits;
#ifdef HAVE_SW_TONE_CONTROLS #ifdef HAVE_SW_TONE_CONTROLS
@ -205,7 +205,7 @@ static int treble; /* A/V */
#endif #endif
/* Settings applicable to audio codec only */ /* Settings applicable to audio codec only */
static int pitch_ratio = 1000; static int32_t pitch_ratio = PITCH_SPEED_100;
static int channels_mode; static int channels_mode;
long dsp_sw_gain; long dsp_sw_gain;
long dsp_sw_cross; long dsp_sw_cross;
@ -254,14 +254,14 @@ static inline int32_t clip_sample_16(int32_t sample)
return sample; return sample;
} }
int sound_get_pitch(void) int32_t sound_get_pitch(void)
{ {
return pitch_ratio; 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, dsp_configure(&AUDIO_DSP, DSP_SWITCH_FREQUENCY,
AUDIO_DSP.codec_frequency); AUDIO_DSP.codec_frequency);
} }
@ -277,7 +277,7 @@ static void tdspeed_setup(struct dsp_config *dspc)
if(!dsp_timestretch_available()) if(!dsp_timestretch_available())
return; /* Timestretch not enabled or buffer not allocated */ return; /* Timestretch not enabled or buffer not allocated */
if (dspc->tdspeed_percent == 0) if (dspc->tdspeed_percent == 0)
dspc->tdspeed_percent = 100; dspc->tdspeed_percent = PITCH_SPEED_100;
if (!tdspeed_config( if (!tdspeed_config(
dspc->codec_frequency == 0 ? NATIVE_FREQUENCY : dspc->codec_frequency, dspc->codec_frequency == 0 ? NATIVE_FREQUENCY : dspc->codec_frequency,
dspc->stereo_mode != STEREO_MONO, 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; AUDIO_DSP.tdspeed_percent = percent;
tdspeed_setup(&AUDIO_DSP); tdspeed_setup(&AUDIO_DSP);
} }
int dsp_get_timestretch() int32_t dsp_get_timestretch()
{ {
return AUDIO_DSP.tdspeed_percent; 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. not need this feature.
*/ */
if (dsp == &AUDIO_DSP) if (dsp == &AUDIO_DSP)
dsp->frequency = pitch_ratio * dsp->codec_frequency / 1000; dsp->frequency = pitch_ratio * dsp->codec_frequency / PITCH_SPEED_100;
else else
dsp->frequency = dsp->codec_frequency; dsp->frequency = dsp->codec_frequency;

View file

@ -83,10 +83,10 @@ void dsp_set_eq_coefs(int band);
void dsp_dither_enable(bool enable); void dsp_dither_enable(bool enable);
void dsp_timestretch_enable(bool enable); void dsp_timestretch_enable(bool enable);
bool dsp_timestretch_available(void); bool dsp_timestretch_available(void);
void sound_set_pitch(int r); void sound_set_pitch(int32_t r);
int sound_get_pitch(void); int32_t sound_get_pitch(void);
void dsp_set_timestretch(int percent); void dsp_set_timestretch(int32_t percent);
int dsp_get_timestretch(void); int32_t dsp_get_timestretch(void);
int dsp_callback(int msg, intptr_t param); int dsp_callback(int msg, intptr_t param);
#endif #endif

View file

@ -22,6 +22,7 @@
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <math.h>
#include "config.h" #include "config.h"
#include "sprintf.h" #include "sprintf.h"
#include "action.h" #include "action.h"
@ -36,24 +37,27 @@
#include "system.h" #include "system.h"
#include "misc.h" #include "misc.h"
#include "pitchscreen.h" #include "pitchscreen.h"
#include "settings.h"
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
#include "tdspeed.h" #include "tdspeed.h"
#endif #endif
#define ABS(x) ((x) > 0 ? (x) : -(x))
#define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */ #define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */
/* on both sides when drawing */ /* on both sides when drawing */
#define PITCH_MAX 2000 #define PITCH_MAX (200 * PITCH_SPEED_PRECISION)
#define PITCH_MIN 500 #define PITCH_MIN (50 * PITCH_SPEED_PRECISION)
#define PITCH_SMALL_DELTA 1 #define PITCH_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
#define PITCH_BIG_DELTA 10 #define PITCH_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
#define PITCH_NUDGE_DELTA 20 #define PITCH_NUDGE_DELTA (2 * PITCH_SPEED_PRECISION) /* 2% */
static bool pitch_mode_semitone = false; #define SPEED_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
#if CONFIG_CODEC == SWCODEC #define SPEED_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
static bool pitch_mode_timestretch = false;
#endif #define SEMITONE_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* 10 cents */
#define SEMITONE_BIG_DELTA PITCH_SPEED_PRECISION /* 1 semitone */
enum enum
{ {
@ -63,25 +67,111 @@ enum
PITCH_ITEM_COUNT, 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, static void pitchscreen_fix_viewports(struct viewport *parent,
struct viewport pitch_viewports[PITCH_ITEM_COUNT]) struct viewport pitch_viewports[PITCH_ITEM_COUNT])
{ {
int i, height; int i, font_height;
height = font_get(parent->font)->height; font_height = font_get(parent->font)->height;
for (i = 0; i < PITCH_ITEM_COUNT; i++) for (i = 0; i < PITCH_ITEM_COUNT; i++)
{ {
pitch_viewports[i] = *parent; 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_TOP].y += ICON_BORDER;
pitch_viewports[PITCH_MID].x += ICON_BORDER; pitch_viewports[PITCH_MID].x += ICON_BORDER;
pitch_viewports[PITCH_MID].width = parent->width - ICON_BORDER*2; 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].y += parent->height / 2 -
pitch_viewports[PITCH_MID].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 /* 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, static void pitchscreen_draw(struct screen *display, int max_lines,
struct viewport pitch_viewports[PITCH_ITEM_COUNT], struct viewport pitch_viewports[PITCH_ITEM_COUNT],
int pitch int32_t pitch, int32_t semitone
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
,int speed ,int32_t speed
#endif #endif
) )
{ {
@ -123,7 +213,7 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
{ {
/* UP: Pitch Up */ /* UP: Pitch Up */
display->set_viewport(&pitch_viewports[PITCH_TOP]); display->set_viewport(&pitch_viewports[PITCH_TOP]);
if (pitch_mode_semitone) if (global_settings.pitch_mode_semitone)
ptr = str(LANG_PITCH_UP_SEMITONE); ptr = str(LANG_PITCH_UP_SEMITONE);
else else
ptr = str(LANG_PITCH_UP); ptr = str(LANG_PITCH_UP);
@ -136,7 +226,7 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
/* DOWN: Pitch Down */ /* DOWN: Pitch Down */
display->set_viewport(&pitch_viewports[PITCH_BOTTOM]); display->set_viewport(&pitch_viewports[PITCH_BOTTOM]);
if (pitch_mode_semitone) if (global_settings.pitch_mode_semitone)
ptr = str(LANG_PITCH_DOWN_SEMITONE); ptr = str(LANG_PITCH_DOWN_SEMITONE);
else else
ptr = str(LANG_PITCH_DOWN); 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 ((show_lang_pitch = (max_lines >= 3)))
{ {
#if CONFIG_CODEC == SWCODEC #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
}
else
{ {
/* Pitch:XXX.X% */ /* Pitch:XXX.X% */
snprintf(buf, sizeof(buf), "%s:%d.%d%%", str(LANG_PITCH), if(global_settings.pitch_mode_semitone)
pitch / 10, pitch % 10); {
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 #endif
{
/* Rate */
snprintf(buf, sizeof(buf), "%s:", str(LANG_PLAYBACK_RATE));
}
display->getstringsize(buf, &w, &h); display->getstringsize(buf, &w, &h);
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2), display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
0, buf); (pitch_viewports[PITCH_MID].height / 2) - h, buf);
if (w > width_used) if (w > width_used)
width_used = w; width_used = w;
} }
/* Middle section lower line */ /* Middle section lower line */
/* "Speed:XXX%" */
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
if (!pitch_mode_timestretch) if(global_settings.pitch_mode_timestretch)
{ {
#endif snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_SPEED),
/* "XXX.X%" */ speed / PITCH_SPEED_PRECISION,
snprintf(buf, sizeof(buf), "%d.%d%%", (speed % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
pitch / 10, pitch % 10);
#if CONFIG_CODEC == SWCODEC
} }
else else
{
/* "Speed:XXX%" */
snprintf(buf, sizeof(buf), "%s:%d%%", str(LANG_SPEED),
speed / 1000);
}
#endif #endif
{
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));
}
}
display->getstringsize(buf, &w, &h); display->getstringsize(buf, &w, &h);
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2), 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) if (w > width_used)
width_used = w; 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 */ /* Middle section left/right labels */
const char *leftlabel = "-2%"; const char *leftlabel = "-2%";
const char *rightlabel = "+2%"; const char *rightlabel = "+2%";
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
if (pitch_mode_timestretch) if (global_settings.pitch_mode_timestretch)
{ {
leftlabel = "<<"; leftlabel = "<<";
rightlabel = ">>"; rightlabel = ">>";
@ -220,37 +350,67 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
if (width_used <= pitch_viewports[PITCH_MID].width) if (width_used <= pitch_viewports[PITCH_MID].width)
{ {
display->putsxy(0, h / 2, leftlabel); display->putsxy(0, (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
display->putsxy(pitch_viewports[PITCH_MID].width - w, h /2, rightlabel); leftlabel);
display->putsxy((pitch_viewports[PITCH_MID].width - w),
(pitch_viewports[PITCH_MID].height / 2) - (h / 2),
rightlabel);
} }
display->update_viewport(); display->update_viewport();
display->set_viewport(NULL); 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_delta < 0)
{ {
if (pitch + pitch_delta >= PITCH_MIN) /* for large jumps, snap up to whole numbers */
new_pitch = pitch + pitch_delta; if(allow_cutoff && pitch_delta <= -PITCH_SPEED_PRECISION &&
else (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) if (!allow_cutoff)
{
return pitch; return pitch;
}
new_pitch = PITCH_MIN; new_pitch = PITCH_MIN;
at_limit = true;
} }
} }
else if (pitch_delta > 0) else if (pitch_delta > 0)
{ {
if (pitch + pitch_delta <= PITCH_MAX) /* for large jumps, snap down to whole numbers */
new_pitch = pitch + pitch_delta; if(allow_cutoff && pitch_delta >= PITCH_SPEED_PRECISION &&
else (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) if (!allow_cutoff)
return pitch; return pitch;
new_pitch = PITCH_MAX; new_pitch = PITCH_MAX;
at_limit = true;
} }
} }
else else
@ -258,47 +418,164 @@ static int pitch_increase(int pitch, int pitch_delta, bool allow_cutoff)
/* pitch_delta == 0 -> no real change */ /* pitch_delta == 0 -> no real change */
return pitch; 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); sound_set_pitch(new_pitch);
return new_pitch; return new_pitch;
} }
/* Factor for changing the pitch one half tone up. static int32_t get_semitone_from_pitch(int32_t pitch)
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)
{ {
uint32_t tmp; int semitone = 0;
uint32_t round_fct; /* How much to scale down at the end */ int32_t fractional_index = 0;
tmp = pitch;
if (up) while(semitone < NUM_SEMITONES - 1 &&
pitch >= semitone_table[semitone + 1])
{ {
tmp = tmp * PITCH_SEMITONE_FACTOR; semitone++;
round_fct = PITCH_K_FCT; }
/* now find the fractional part */
while(pitch > (cent_interp[fractional_index + 1] *
semitone_table[semitone] / PITCH_SPEED_100))
{
/* 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 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))
{ {
tmp = (tmp * PITCH_KN_FCT) / PITCH_SEMITONE_FACTOR; new_semitone = SEMITONE_START * PITCH_SPEED_PRECISION;
round_fct = PITCH_N_FCT; at_limit = true;
} }
/* Scaling down with rounding */ else if(new_semitone > (SEMITONE_END * PITCH_SPEED_PRECISION))
tmp = (tmp + round_fct / 2) / round_fct; {
return pitch_increase(pitch, tmp - pitch, false); new_semitone = SEMITONE_END * PITCH_SPEED_PRECISION;
at_limit = true;
}
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 gui_syncpitchscreen_run(void)
{ {
int button, i; int button, i;
int pitch = sound_get_pitch(); int32_t pitch = sound_get_pitch();
#if CONFIG_CODEC == SWCODEC int32_t semitone;
int stretch = dsp_get_timestretch();
int speed = stretch * pitch; /* speed to maintain */ int32_t new_pitch;
#endif int32_t pitch_delta;
int new_pitch;
int pitch_delta;
bool nudged = false; bool nudged = false;
bool exit = false; bool exit = false;
/* should maybe be passed per parameter later, not needed for now */ /* 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]; struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT];
int max_lines[NB_SCREENS]; 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 */ /* initialize pitchscreen vps */
FOR_NB_SCREENS(i) FOR_NB_SCREENS(i)
{ {
@ -343,49 +643,80 @@ int gui_syncpitchscreen_run(void)
{ {
FOR_NB_SCREENS(i) FOR_NB_SCREENS(i)
pitchscreen_draw(&screens[i], max_lines[i], pitchscreen_draw(&screens[i], max_lines[i],
pitch_viewports[i], pitch pitch_viewports[i], pitch, semitone
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
, speed , speed
#endif #endif
); );
pitch_delta = 0; pitch_delta = 0;
#if CONFIG_CODEC == SWCODEC
new_speed = 0;
#endif
button = get_action(CONTEXT_PITCHSCREEN, HZ); button = get_action(CONTEXT_PITCHSCREEN, HZ);
switch (button) switch (button)
{ {
case ACTION_PS_INC_SMALL: 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; break;
case ACTION_PS_INC_BIG: 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; break;
case ACTION_PS_DEC_SMALL: 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; break;
case ACTION_PS_DEC_BIG: 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; break;
case ACTION_PS_NUDGE_RIGHT: case ACTION_PS_NUDGE_RIGHT:
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
if (!pitch_mode_timestretch) if (!global_settings.pitch_mode_timestretch)
{ {
#endif #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); nudged = (new_pitch != pitch);
pitch = new_pitch; pitch = new_pitch;
semitone = get_semitone_from_pitch(pitch);
#if CONFIG_CODEC == SWCODEC
speed = pitch;
#endif
break; break;
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
} }
else
{
new_speed = speed + SPEED_SMALL_DELTA;
at_limit = false;
}
break;
case ACTION_PS_FASTER: case ACTION_PS_FASTER:
if (pitch_mode_timestretch && stretch < STRETCH_MAX) if (global_settings.pitch_mode_timestretch)
{ {
stretch++; new_speed = speed + SPEED_BIG_DELTA;
dsp_set_timestretch(stretch); /* snap to whole numbers */
speed = stretch * pitch; if(new_speed % PITCH_SPEED_PRECISION != 0)
new_speed -= new_speed % PITCH_SPEED_PRECISION;
at_limit = false;
} }
break; break;
#endif #endif
@ -393,29 +724,53 @@ int gui_syncpitchscreen_run(void)
case ACTION_PS_NUDGE_RIGHTOFF: case ACTION_PS_NUDGE_RIGHTOFF:
if (nudged) 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; nudged = false;
} }
break; break;
case ACTION_PS_NUDGE_LEFT: case ACTION_PS_NUDGE_LEFT:
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
if (!pitch_mode_timestretch) if (!global_settings.pitch_mode_timestretch)
{ {
#endif #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); nudged = (new_pitch != pitch);
pitch = new_pitch; pitch = new_pitch;
semitone = get_semitone_from_pitch(pitch);
#if CONFIG_CODEC == SWCODEC
speed = pitch;
#endif
break; break;
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
} }
else
{
new_speed = speed - SPEED_SMALL_DELTA;
at_limit = false;
}
break;
case ACTION_PS_SLOWER: case ACTION_PS_SLOWER:
if (pitch_mode_timestretch && stretch > STRETCH_MIN) if (global_settings.pitch_mode_timestretch)
{ {
stretch--; new_speed = speed - SPEED_BIG_DELTA;
dsp_set_timestretch(stretch); /* snap to whole numbers */
speed = stretch * pitch; if(new_speed % PITCH_SPEED_PRECISION != 0)
new_speed += PITCH_SPEED_PRECISION - speed % PITCH_SPEED_PRECISION;
at_limit = false;
} }
break; break;
#endif #endif
@ -423,27 +778,49 @@ int gui_syncpitchscreen_run(void)
case ACTION_PS_NUDGE_LEFTOFF: case ACTION_PS_NUDGE_LEFTOFF:
if (nudged) 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; nudged = false;
} }
break; break;
case ACTION_PS_RESET: case ACTION_PS_RESET:
pitch = 1000; pitch = PITCH_SPEED_100;
sound_set_pitch(pitch); sound_set_pitch(pitch);
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
stretch = 100; speed = PITCH_SPEED_100;
dsp_set_timestretch(stretch); if (dsp_timestretch_available())
speed = stretch * pitch; {
dsp_set_timestretch(PITCH_SPEED_100);
at_limit = false;
}
#endif #endif
semitone = get_semitone_from_pitch(pitch);
break; break;
case ACTION_PS_TOGGLE_MODE: case ACTION_PS_TOGGLE_MODE:
global_settings.pitch_mode_semitone = !global_settings.pitch_mode_semitone;
#if CONFIG_CODEC == SWCODEC #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 #endif
pitch_mode_semitone = !pitch_mode_semitone;
break; break;
case ACTION_PS_EXIT: case ACTION_PS_EXIT:
@ -457,27 +834,73 @@ int gui_syncpitchscreen_run(void)
} }
if (pitch_delta) if (pitch_delta)
{ {
if (pitch_mode_semitone) if (global_settings.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_timestretch)
{ {
/* Set stretch to maintain speed */ semitone = pitch_increase_semitone(pitch, semitone, pitch_delta
/* i.e. increase pitch, reduce stretch */ #if CONFIG_CODEC == SWCODEC
int new_stretch = speed / pitch; , speed
if (new_stretch >= STRETCH_MIN && new_stretch <= STRETCH_MAX) #endif
{ );
stretch = new_stretch; pitch = get_pitch_from_semitone(semitone);
dsp_set_timestretch(stretch);
}
} }
else else
speed = stretch * pitch; {
#endif pitch = pitch_increase(pitch, pitch_delta, true
#if CONFIG_CODEC == SWCODEC
, speed
#endif
);
semitone = get_semitone_from_pitch(pitch);
}
#if CONFIG_CODEC == SWCODEC
if (global_settings.pitch_mode_timestretch)
{
/* do this to make sure we properly obey the stretch limits */
new_speed = speed;
}
else
{
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 #if CONFIG_CODEC == SWCODEC
pcmbuf_set_low_latency(false); pcmbuf_set_low_latency(false);
#endif #endif

View file

@ -22,6 +22,11 @@
#ifndef _PITCHSCREEN_H_ #ifndef _PITCHSCREEN_H_
#define _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); int gui_syncpitchscreen_run(void);
#endif /* _PITCHSCREEN_H_ */ #endif /* _PITCHSCREEN_H_ */

View file

@ -12604,3 +12604,45 @@
remote: "Remote Statusbar" remote: "Remote Statusbar"
</voice> </voice>
</phrase> </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>

View file

@ -128,12 +128,12 @@ void* plugin_get_buffer(size_t *buffer_size);
#define PLUGIN_MAGIC 0x526F634B /* RocK */ #define PLUGIN_MAGIC 0x526F634B /* RocK */
/* increase this every time the api struct changes */ /* 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 /* update this to latest version if a change to the api struct breaks
backwards compatibility (and please take the opportunity to sort in any backwards compatibility (and please take the opportunity to sort in any
new function which are "waiting" at the end of the function table) */ 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 */ /* plugin return codes */
enum plugin_status { enum plugin_status {
@ -618,7 +618,7 @@ struct plugin_api {
#endif #endif
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) || \ #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) || \
(CONFIG_CODEC == SWCODEC) (CONFIG_CODEC == SWCODEC)
void (*sound_set_pitch)(int pitch); void (*sound_set_pitch)(int32_t pitch);
#endif #endif
/* MAS communication */ /* MAS communication */

View file

@ -739,6 +739,11 @@ struct user_settings
struct touchscreen_parameter ts_calibration_data; struct touchscreen_parameter ts_calibration_data;
#endif #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 /* If values are just added to the end, no need to bump plugin API
version. */ version. */
/* new stuff to be added at the end */ /* new stuff to be added at the end */

View file

@ -33,7 +33,6 @@
#include "settings_list.h" #include "settings_list.h"
#include "sound.h" #include "sound.h"
#include "dsp.h" #include "dsp.h"
#include "debug.h"
#include "mpeg.h" #include "mpeg.h"
#include "audio.h" #include "audio.h"
#include "power.h" #include "power.h"
@ -1528,6 +1527,14 @@ const struct settings_list settings[] = {
tsc_is_changed, tsc_set_default), tsc_is_changed, tsc_set_default),
#endif #endif
OFFON_SETTING(0, prevent_skip, LANG_PREVENT_SKIPPING, false, "prevent track skip", NULL), 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); const int nb_settings = sizeof(settings)/sizeof(*settings);

View file

@ -25,7 +25,6 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "buffer.h" #include "buffer.h"
#include "debug.h"
#include "system.h" #include "system.h"
#include "tdspeed.h" #include "tdspeed.h"
#include "settings.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; struct tdspeed_state_s *st = &tdspeed_state;
int src_frame_sz; int src_frame_sz;
@ -84,7 +83,7 @@ bool tdspeed_config(int samplerate, bool stereo, int factor)
return false; return false;
/* Check parameters */ /* Check parameters */
if (factor == 100) if (factor == PITCH_SPEED_100)
return false; return false;
if (samplerate < MIN_RATE || samplerate > MAX_RATE) if (samplerate < MIN_RATE || samplerate > MAX_RATE)
return false; return false;
@ -94,14 +93,14 @@ bool tdspeed_config(int samplerate, bool stereo, int factor)
st->stereo = stereo; st->stereo = stereo;
st->dst_step = samplerate / MINFREQ; st->dst_step = samplerate / MINFREQ;
if (factor > 100) if (factor > PITCH_SPEED_100)
st->dst_step = st->dst_step * 100 / factor; st->dst_step = st->dst_step * PITCH_SPEED_100 / factor;
st->dst_order = 1; st->dst_order = 1;
while (st->dst_step >>= 1) while (st->dst_step >>= 1)
st->dst_order++; st->dst_order++;
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; st->shift_max = (st->dst_step > st->src_step) ? st->dst_step : st->src_step;
src_frame_sz = st->shift_max + st->dst_step; src_frame_sz = st->shift_max + st->dst_step;

View file

@ -23,15 +23,28 @@
#ifndef _TDSPEED_H #ifndef _TDSPEED_H
#define _TDSPEED_H #define _TDSPEED_H
#include "dsp.h"
/* for the precision #defines: */
#include "pitchscreen.h"
#define TDSPEED_OUTBUFSIZE 4096 #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); 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_output_size(void);
long tdspeed_est_input_size(long size); long tdspeed_est_input_size(long size);
int tdspeed_doit(int32_t *src[], int count); int tdspeed_doit(int32_t *src[], int count);
#define STRETCH_MAX 250 #define STRETCH_MAX (250L * PITCH_SPEED_PRECISION) /* 250% */
#define STRETCH_MIN 35 #define STRETCH_MIN (35L * PITCH_SPEED_PRECISION) /* 35% */
#endif #endif

View file

@ -479,6 +479,7 @@ Andre Lupa
Hilton Shumway Hilton Shumway
Matthew Bonnett Matthew Bonnett
Nick Tryon Nick Tryon
David Johnston
The libmad team The libmad team
The wavpack team The wavpack team

View file

@ -60,8 +60,8 @@ void sound_set(int setting, int value);
int sound_val2phys(int setting, int value); int sound_val2phys(int setting, int value);
#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
void sound_set_pitch(int permille); void sound_set_pitch(int32_t pitch);
int sound_get_pitch(void); int32_t sound_get_pitch(void);
#endif #endif
#endif #endif

View file

@ -25,6 +25,8 @@
#include "sound.h" #include "sound.h"
#include "logf.h" #include "logf.h"
#include "system.h" #include "system.h"
/* for the pitch and speed precision #defines: */
#include "pitchscreen.h"
#ifndef SIMULATOR #ifndef SIMULATOR
#include "i2c.h" #include "i2c.h"
#include "mas.h" #include "mas.h"
@ -159,6 +161,7 @@ sound_set_type* sound_get_fn(int setting)
#if CONFIG_CODEC == SWCODEC #if CONFIG_CODEC == SWCODEC
/* Copied from dsp.h, nasty nasty, but we don't want to include dsp.h */ /* Copied from dsp.h, nasty nasty, but we don't want to include dsp.h */
enum { enum {
DSP_CALLBACK_SET_PRESCALE = 0, DSP_CALLBACK_SET_PRESCALE = 0,
DSP_CALLBACK_SET_BASS, DSP_CALLBACK_SET_BASS,
@ -698,18 +701,18 @@ int sound_val2phys(int setting, int value)
crystal frequency than we actually have. It will adjust its internal crystal frequency than we actually have. It will adjust its internal
parameters and the result is that the audio is played at another pitch. parameters and the result is that the audio is played at another pitch.
The pitch value is in tenths of percent. The pitch value precision is based on PITCH_SPEED_PRECISION (in dsp.h)
*/ */
static int last_pitch = 1000; static int last_pitch = PITCH_SPEED_100;
void sound_set_pitch(int pitch) void sound_set_pitch(int32_t pitch)
{ {
unsigned long val; unsigned long val;
if (pitch != last_pitch) if (pitch != last_pitch)
{ {
/* Calculate the new (bogus) frequency */ /* Calculate the new (bogus) frequency */
val = 18432 * 1000 / pitch; val = 18432 * PITCH_SPEED_100 / pitch;
mas_writemem(MAS_BANK_D0, MAS_D0_OFREQ_CONTROL, &val, 1); mas_writemem(MAS_BANK_D0, MAS_D0_OFREQ_CONTROL, &val, 1);
@ -721,19 +724,19 @@ void sound_set_pitch(int pitch)
} }
} }
int sound_get_pitch(void) int32_t sound_get_pitch(void)
{ {
return last_pitch; return last_pitch;
} }
#else /* SIMULATOR */ #else /* SIMULATOR */
void sound_set_pitch(int pitch) void sound_set_pitch(int32_t pitch)
{ {
(void)pitch; (void)pitch;
} }
int sound_get_pitch(void) int32_t sound_get_pitch(void)
{ {
return 1000; return PITCH_SPEED_100;
} }
#endif /* SIMULATOR */ #endif /* SIMULATOR */
#endif /* (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) */ #endif /* (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) */

View file

@ -273,34 +273,52 @@ Delete the currently playing file.
\nopt{player}{ \nopt{player}{
\subsubsection{\label{sec:pitchscreen}Pitch} \subsubsection{\label{sec:pitchscreen}Pitch}
The \setting{Pitch Screen} allows you to change the pitch and the playback The \setting{Pitch Screen} allows you to change the rate of playback
speed of your \dap. The pitch value can be adjusted between 50\% and 200\%. (i.e. the playback speed and at the same time the pitch) of your
50\% means half the normal playback speed and the pitch that is an octave lower \dap. The rate value can be adjusted between 50\% and 200\%. 50\%
than the normal pitch. 200\% means double playback speed and the pitch that means half the normal playback speed and a pitch that is an octave
is an octave higher than the normal pitch. lower than the normal pitch. 200\% means double playback speed and a
pitch that is an octave higher than the normal pitch.
The rate can be changed in two modes: procentual and semitone.
Initially, procentual mode is active.
\opt{swcodec}{
If you've enabled the \setting{Timestretch} option in
\setting{Sound Settings} and have since rebooted, you can also use
timestretch mode. This allows you to change the playback speed
without affecting the pitch, and vice versa.
In timestretch mode there are separate displays for pitch and
speed, and each can be altered independently. Due to the
limitations of the algorithm, speed is limited to be between 35\%
and 250\% of the current pitch value. Pitch must maintain the
same ratio as well as remain between 50\% and 200\%.
}
The value of the \opt{swcodec}{rate, pitch and speed}\nopt{swcodec}{rate}
is not persisted, i.e. after the \dap\ is turned on it will
always be set to 100\%.
\opt{masf}{ \opt{masf}{
Changing the pitch can be done in two modes: procentual and semitone.
Initially (after the \dap{} is switched on), procentual mode is active.
\begin{table} \begin{table}
\begin{btnmap}{}{} \begin{btnmap}{}{}
\ActionPsToggleMode \ActionPsToggleMode
& Toggle pitch changing mode \\ & Toggle pitch changing mode \\
% %
\ActionPsIncSmall{} / \ActionPsDecSmall \ActionPsIncSmall{} / \ActionPsDecSmall
& Increase / Decrease pitch by 0.1\% (in procentual mode) or a semitone & Increase / Decrease pitch by 0.1\% (in procentual mode) or by 0.1
(in semitone mode)\\ semitone (in semitone mode)\\
% %
\ActionPsIncBig{} / \ActionPsDecBig \ActionPsIncBig{} / \ActionPsDecBig
& Increase / Decrease pitch by 1\% (in procentual mode) or a semitone & Increase / Decrease pitch by 1\% (in procentual mode) or a semitone
(in semitone mode)\\ (in semitone mode)\\
% %
\ActionPsNudgeLeft{} / \ActionPsNudgeRight \ActionPsNudgeLeft{} / \ActionPsNudgeRight
& Temporarily change pitch by 2.0\% (beatmatch) \\ & Temporarily change pitch by 2\% (beatmatch) \\
% %
\ActionPsReset \ActionPsReset
& Reset pitch to 100\% \\ & Reset rate to 100\% \\
% %
\ActionPsExit \ActionPsExit
& Leave the Pitch Screen \\ & Leave the Pitch Screen \\
@ -312,23 +330,16 @@ Delete the currently playing file.
} }
\opt{swcodec}{ \opt{swcodec}{
Changing the pitch can be done in three modes: procentual, semitone and
timestretch. Initially (after the \dap{} is switched on), procentual mode is active.
Timestretch mode allows you to change the playback speed of your recording without
affecting the pitch, and vice versa. To access this you must enable the \setting{Timestretch}
option in \setting{Sound Settings} and reboot.
\begin{table} \begin{table}
\begin{btnmap}{}{} \begin{btnmap}{}{}
\ActionPsToggleMode \ActionPsToggleMode
\opt{HAVEREMOTEKEYMAP}{& \ActionRCPsToggleMode} \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsToggleMode}
& Toggle pitch changing mode \\ & Toggle pitch changing mode (cycles through all available modes)\\
% %
\ActionPsIncSmall{} / \ActionPsDecSmall \ActionPsIncSmall{} / \ActionPsDecSmall
\opt{HAVEREMOTEKEYMAP}{& \ActionRCPsIncSmall{} / \ActionRCPsDecSmall} \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsIncSmall{} / \ActionRCPsDecSmall}
& Increase / Decrease pitch by 0.1\% (in procentual mode) or a semitone & Increase / Decrease pitch by 0.1\% (in procentual mode) or 0.1
(in semitone mode)\\ semitone (in semitone mode)\\
% %
\ActionPsIncBig{} / \ActionPsDecBig \ActionPsIncBig{} / \ActionPsDecBig
\opt{HAVEREMOTEKEYMAP}{& \ActionRCPsIncBig{} / \ActionRCPsDecBig} \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsIncBig{} / \ActionRCPsDecBig}
@ -337,7 +348,7 @@ Delete the currently playing file.
% %
\ActionPsNudgeLeft{} / \ActionPsNudgeRight \ActionPsNudgeLeft{} / \ActionPsNudgeRight
\opt{HAVEREMOTEKEYMAP}{& \ActionRCPsNudgeLeft{} / \ActionPsNudgeRight} \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsNudgeLeft{} / \ActionPsNudgeRight}
& Temporarily change pitch by 2.0\% (beatmatch), or modify speed (in timestretch mode) \\ & Temporarily change pitch by 2\% (beatmatch), or modify speed (in timestretch mode) \\
% %
\ActionPsReset \ActionPsReset
\opt{HAVEREMOTEKEYMAP}{& \ActionRCPsReset} \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsReset}