summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--docs/CREDITS1
-rw-r--r--firmware/export/sound.h4
-rw-r--r--firmware/sound.c19
-rw-r--r--manual/rockbox_interface/wps.tex57
14 files changed, 697 insertions, 188 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
162 int sample_depth; 162 int sample_depth;
163 int sample_bytes; 163 int sample_bytes;
164 int stereo_mode; 164 int stereo_mode;
165 int tdspeed_percent; /* Speed % */ 165 int32_t tdspeed_percent; /* Speed% * PITCH_SPEED_PRECISION */
166 bool tdspeed_active; /* Timestretch is in use */ 166 bool tdspeed_active; /* Timestretch is in use */
167 int frac_bits; 167 int frac_bits;
168#ifdef HAVE_SW_TONE_CONTROLS 168#ifdef HAVE_SW_TONE_CONTROLS
@@ -205,7 +205,7 @@ static int treble; /* A/V */
205#endif 205#endif
206 206
207/* Settings applicable to audio codec only */ 207/* Settings applicable to audio codec only */
208static int pitch_ratio = 1000; 208static int32_t pitch_ratio = PITCH_SPEED_100;
209static int channels_mode; 209static int channels_mode;
210 long dsp_sw_gain; 210 long dsp_sw_gain;
211 long dsp_sw_cross; 211 long dsp_sw_cross;
@@ -254,14 +254,14 @@ static inline int32_t clip_sample_16(int32_t sample)
254 return sample; 254 return sample;
255} 255}
256 256
257int sound_get_pitch(void) 257int32_t sound_get_pitch(void)
258{ 258{
259 return pitch_ratio; 259 return pitch_ratio;
260} 260}
261 261
262void sound_set_pitch(int permille) 262void sound_set_pitch(int32_t percent)
263{ 263{
264 pitch_ratio = permille; 264 pitch_ratio = percent;
265 dsp_configure(&AUDIO_DSP, DSP_SWITCH_FREQUENCY, 265 dsp_configure(&AUDIO_DSP, DSP_SWITCH_FREQUENCY,
266 AUDIO_DSP.codec_frequency); 266 AUDIO_DSP.codec_frequency);
267} 267}
@@ -277,7 +277,7 @@ static void tdspeed_setup(struct dsp_config *dspc)
277 if(!dsp_timestretch_available()) 277 if(!dsp_timestretch_available())
278 return; /* Timestretch not enabled or buffer not allocated */ 278 return; /* Timestretch not enabled or buffer not allocated */
279 if (dspc->tdspeed_percent == 0) 279 if (dspc->tdspeed_percent == 0)
280 dspc->tdspeed_percent = 100; 280 dspc->tdspeed_percent = PITCH_SPEED_100;
281 if (!tdspeed_config( 281 if (!tdspeed_config(
282 dspc->codec_frequency == 0 ? NATIVE_FREQUENCY : dspc->codec_frequency, 282 dspc->codec_frequency == 0 ? NATIVE_FREQUENCY : dspc->codec_frequency,
283 dspc->stereo_mode != STEREO_MONO, 283 dspc->stereo_mode != STEREO_MONO,
@@ -312,13 +312,13 @@ void dsp_timestretch_enable(bool enabled)
312 } 312 }
313} 313}
314 314
315void dsp_set_timestretch(int percent) 315void dsp_set_timestretch(int32_t percent)
316{ 316{
317 AUDIO_DSP.tdspeed_percent = percent; 317 AUDIO_DSP.tdspeed_percent = percent;
318 tdspeed_setup(&AUDIO_DSP); 318 tdspeed_setup(&AUDIO_DSP);
319} 319}
320 320
321int dsp_get_timestretch() 321int32_t dsp_get_timestretch()
322{ 322{
323 return AUDIO_DSP.tdspeed_percent; 323 return AUDIO_DSP.tdspeed_percent;
324} 324}
@@ -1347,7 +1347,7 @@ intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value)
1347 not need this feature. 1347 not need this feature.
1348 */ 1348 */
1349 if (dsp == &AUDIO_DSP) 1349 if (dsp == &AUDIO_DSP)
1350 dsp->frequency = pitch_ratio * dsp->codec_frequency / 1000; 1350 dsp->frequency = pitch_ratio * dsp->codec_frequency / PITCH_SPEED_100;
1351 else 1351 else
1352 dsp->frequency = dsp->codec_frequency; 1352 dsp->frequency = dsp->codec_frequency;
1353 1353
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);
83void dsp_dither_enable(bool enable); 83void dsp_dither_enable(bool enable);
84void dsp_timestretch_enable(bool enable); 84void dsp_timestretch_enable(bool enable);
85bool dsp_timestretch_available(void); 85bool dsp_timestretch_available(void);
86void sound_set_pitch(int r); 86void sound_set_pitch(int32_t r);
87int sound_get_pitch(void); 87int32_t sound_get_pitch(void);
88void dsp_set_timestretch(int percent); 88void dsp_set_timestretch(int32_t percent);
89int dsp_get_timestretch(void); 89int32_t dsp_get_timestretch(void);
90int dsp_callback(int msg, intptr_t param); 90int dsp_callback(int msg, intptr_t param);
91 91
92#endif 92#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 @@
22#include <stdbool.h> 22#include <stdbool.h>
23#include <string.h> 23#include <string.h>
24#include <stdio.h> 24#include <stdio.h>
25#include <math.h>
25#include "config.h" 26#include "config.h"
26#include "sprintf.h" 27#include "sprintf.h"
27#include "action.h" 28#include "action.h"
@@ -36,24 +37,27 @@
36#include "system.h" 37#include "system.h"
37#include "misc.h" 38#include "misc.h"
38#include "pitchscreen.h" 39#include "pitchscreen.h"
40#include "settings.h"
39#if CONFIG_CODEC == SWCODEC 41#if CONFIG_CODEC == SWCODEC
40#include "tdspeed.h" 42#include "tdspeed.h"
41#endif 43#endif
42 44
45#define ABS(x) ((x) > 0 ? (x) : -(x))
43 46
44#define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */ 47#define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */
45 /* on both sides when drawing */ 48 /* on both sides when drawing */
46 49
47#define PITCH_MAX 2000 50#define PITCH_MAX (200 * PITCH_SPEED_PRECISION)
48#define PITCH_MIN 500 51#define PITCH_MIN (50 * PITCH_SPEED_PRECISION)
49#define PITCH_SMALL_DELTA 1 52#define PITCH_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
50#define PITCH_BIG_DELTA 10 53#define PITCH_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
51#define PITCH_NUDGE_DELTA 20 54#define PITCH_NUDGE_DELTA (2 * PITCH_SPEED_PRECISION) /* 2% */
52 55
53static bool pitch_mode_semitone = false; 56#define SPEED_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
54#if CONFIG_CODEC == SWCODEC 57#define SPEED_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
55static bool pitch_mode_timestretch = false; 58
56#endif 59#define SEMITONE_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* 10 cents */
60#define SEMITONE_BIG_DELTA PITCH_SPEED_PRECISION /* 1 semitone */
57 61
58enum 62enum
59{ 63{
@@ -63,25 +67,111 @@ enum
63 PITCH_ITEM_COUNT, 67 PITCH_ITEM_COUNT,
64}; 68};
65 69
70
71/* This is a table of semitone percentage values of the appropriate
72 precision (based on PITCH_SPEED_PRECISION). Note that these are
73 all constant expressions, which will be evaluated at compile time,
74 so no need to worry about how complex the expressions look.
75 That's just to get the precision right.
76
77 I calculated these values, starting from 50, as
78
79 x(n) = 50 * 2^(n/12)
80
81 All that math in each entry simply converts the float constant
82 to an integer equal to PITCH_SPEED_PRECISION times the float value,
83 with as little precision loss as possible.
84*/
85#define SEMITONE_VALUE(x) \
86 ( (int)(((x) + 0.5 / PITCH_SPEED_PRECISION) * PITCH_SPEED_PRECISION) )
87
88static const int semitone_table[] =
89{
90 SEMITONE_VALUE(50),
91 SEMITONE_VALUE(52.97315472),
92 SEMITONE_VALUE(56.12310242),
93 SEMITONE_VALUE(59.46035575),
94 SEMITONE_VALUE(62.99605249),
95 SEMITONE_VALUE(66.74199271),
96 SEMITONE_VALUE(70.71067812),
97 SEMITONE_VALUE(74.91535384),
98 SEMITONE_VALUE(79.3700526 ),
99 SEMITONE_VALUE(84.08964153),
100 SEMITONE_VALUE(89.08987181),
101 SEMITONE_VALUE(94.38743127),
102 SEMITONE_VALUE(100 ),
103 SEMITONE_VALUE(105.9463094),
104 SEMITONE_VALUE(112.2462048),
105 SEMITONE_VALUE(118.9207115),
106 SEMITONE_VALUE(125.992105 ),
107 SEMITONE_VALUE(133.4839854),
108 SEMITONE_VALUE(141.4213562),
109 SEMITONE_VALUE(149.8307077),
110 SEMITONE_VALUE(158.7401052),
111 SEMITONE_VALUE(168.1792831),
112 SEMITONE_VALUE(178.1797436),
113 SEMITONE_VALUE(188.7748625),
114 SEMITONE_VALUE(200 )
115};
116
117#define NUM_SEMITONES ((int)(sizeof(semitone_table) / sizeof(int)))
118#define SEMITONE_START -12
119#define SEMITONE_END 12
120
121/* A table of values for approximating the cent curve with
122 linear interpolation. Multipy the next lowest semitone
123 by this much to find the corresponding cent percentage.
124
125 These values were calculated as
126 x(n) = 100 * 2^(n * 20/1200)
127*/
128
129#define CENT_INTERP(x) \
130 ( (int)(((x) + 0.5 / PITCH_SPEED_PRECISION) * PITCH_SPEED_PRECISION) )
131
132
133static const int cent_interp[] =
134{
135 PITCH_SPEED_100,
136 CENT_INTERP(101.1619440),
137 CENT_INTERP(102.3373892),
138 CENT_INTERP(103.5264924),
139 CENT_INTERP(104.7294123),
140 /* this one's the next semitone but we have it here for convenience */
141 CENT_INTERP(105.9463094),
142};
143
144/* Number of cents between entries in the cent_interp table */
145#define CENT_INTERP_INTERVAL 20
146#define CENT_INTERP_NUM ((int)(sizeof(cent_interp)/sizeof(int)))
147
148/* This stores whether the pitch and speed are at their own limits */
149/* or that of the timestretching algorithm */
150static bool at_limit = false;
151
66static void pitchscreen_fix_viewports(struct viewport *parent, 152static void pitchscreen_fix_viewports(struct viewport *parent,
67 struct viewport pitch_viewports[PITCH_ITEM_COUNT]) 153 struct viewport pitch_viewports[PITCH_ITEM_COUNT])
68{ 154{
69 int i, height; 155 int i, font_height;
70 height = font_get(parent->font)->height; 156 font_height = font_get(parent->font)->height;
71 for (i = 0; i < PITCH_ITEM_COUNT; i++) 157 for (i = 0; i < PITCH_ITEM_COUNT; i++)
72 { 158 {
73 pitch_viewports[i] = *parent; 159 pitch_viewports[i] = *parent;
74 pitch_viewports[i].height = height; 160 pitch_viewports[i].height = font_height;
75 } 161 }
76 pitch_viewports[PITCH_TOP].y += ICON_BORDER; 162 pitch_viewports[PITCH_TOP].y += ICON_BORDER;
77 163
78 pitch_viewports[PITCH_MID].x += ICON_BORDER; 164 pitch_viewports[PITCH_MID].x += ICON_BORDER;
79 pitch_viewports[PITCH_MID].width = parent->width - ICON_BORDER*2; 165 pitch_viewports[PITCH_MID].width = parent->width - ICON_BORDER*2;
80 pitch_viewports[PITCH_MID].height = height * 2; 166 pitch_viewports[PITCH_MID].height = parent->height - ICON_BORDER*2
167 - font_height * 2;
168 if(pitch_viewports[PITCH_MID].height < font_height * 2)
169 pitch_viewports[PITCH_MID].height = font_height * 2;
81 pitch_viewports[PITCH_MID].y += parent->height / 2 - 170 pitch_viewports[PITCH_MID].y += parent->height / 2 -
82 pitch_viewports[PITCH_MID].height / 2; 171 pitch_viewports[PITCH_MID].height / 2;
83 172
84 pitch_viewports[PITCH_BOTTOM].y += parent->height - height - ICON_BORDER; 173 pitch_viewports[PITCH_BOTTOM].y += parent->height - font_height
174 - ICON_BORDER;
85} 175}
86 176
87/* must be called before pitchscreen_draw, or within 177/* must be called before pitchscreen_draw, or within
@@ -107,9 +197,9 @@ static void pitchscreen_draw_icons(struct screen *display,
107 197
108static void pitchscreen_draw(struct screen *display, int max_lines, 198static void pitchscreen_draw(struct screen *display, int max_lines,
109 struct viewport pitch_viewports[PITCH_ITEM_COUNT], 199 struct viewport pitch_viewports[PITCH_ITEM_COUNT],
110 int pitch 200 int32_t pitch, int32_t semitone
111#if CONFIG_CODEC == SWCODEC 201#if CONFIG_CODEC == SWCODEC
112 ,int speed 202 ,int32_t speed
113#endif 203#endif
114 ) 204 )
115{ 205{
@@ -123,7 +213,7 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
123 { 213 {
124 /* UP: Pitch Up */ 214 /* UP: Pitch Up */
125 display->set_viewport(&pitch_viewports[PITCH_TOP]); 215 display->set_viewport(&pitch_viewports[PITCH_TOP]);
126 if (pitch_mode_semitone) 216 if (global_settings.pitch_mode_semitone)
127 ptr = str(LANG_PITCH_UP_SEMITONE); 217 ptr = str(LANG_PITCH_UP_SEMITONE);
128 else 218 else
129 ptr = str(LANG_PITCH_UP); 219 ptr = str(LANG_PITCH_UP);
@@ -136,7 +226,7 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
136 226
137 /* DOWN: Pitch Down */ 227 /* DOWN: Pitch Down */
138 display->set_viewport(&pitch_viewports[PITCH_BOTTOM]); 228 display->set_viewport(&pitch_viewports[PITCH_BOTTOM]);
139 if (pitch_mode_semitone) 229 if (global_settings.pitch_mode_semitone)
140 ptr = str(LANG_PITCH_DOWN_SEMITONE); 230 ptr = str(LANG_PITCH_DOWN_SEMITONE);
141 else 231 else
142 ptr = str(LANG_PITCH_DOWN); 232 ptr = str(LANG_PITCH_DOWN);
@@ -157,55 +247,95 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
157 if ((show_lang_pitch = (max_lines >= 3))) 247 if ((show_lang_pitch = (max_lines >= 3)))
158 { 248 {
159#if CONFIG_CODEC == SWCODEC 249#if CONFIG_CODEC == SWCODEC
160 if (!pitch_mode_timestretch) 250 if(global_settings.pitch_mode_timestretch)
161 { 251 {
162#endif 252 /* Pitch:XXX.X% */
163 /* LANG_PITCH */ 253 if(global_settings.pitch_mode_semitone)
164 snprintf(buf, sizeof(buf), "%s", str(LANG_PITCH)); 254 {
165#if CONFIG_CODEC == SWCODEC 255 snprintf(buf, sizeof(buf), "%s: %s%ld.%02ld", str(LANG_PITCH),
256 semitone >= 0 ? "+" : "-",
257 ABS(semitone / PITCH_SPEED_PRECISION),
258 ABS((semitone % PITCH_SPEED_PRECISION) /
259 (PITCH_SPEED_PRECISION / 100))
260 );
261 }
262 else
263 {
264 snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_PITCH),
265 pitch / PITCH_SPEED_PRECISION,
266 (pitch % PITCH_SPEED_PRECISION) /
267 (PITCH_SPEED_PRECISION / 10));
268 }
166 } 269 }
167 else 270 else
271#endif
168 { 272 {
169 /* Pitch:XXX.X% */ 273 /* Rate */
170 snprintf(buf, sizeof(buf), "%s:%d.%d%%", str(LANG_PITCH), 274 snprintf(buf, sizeof(buf), "%s:", str(LANG_PLAYBACK_RATE));
171 pitch / 10, pitch % 10);
172 } 275 }
173#endif
174 display->getstringsize(buf, &w, &h); 276 display->getstringsize(buf, &w, &h);
175 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2), 277 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
176 0, buf); 278 (pitch_viewports[PITCH_MID].height / 2) - h, buf);
177 if (w > width_used) 279 if (w > width_used)
178 width_used = w; 280 width_used = w;
179 } 281 }
180 282
181 /* Middle section lower line */ 283 /* Middle section lower line */
284 /* "Speed:XXX%" */
182#if CONFIG_CODEC == SWCODEC 285#if CONFIG_CODEC == SWCODEC
183 if (!pitch_mode_timestretch) 286 if(global_settings.pitch_mode_timestretch)
184 { 287 {
185#endif 288 snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_SPEED),
186 /* "XXX.X%" */ 289 speed / PITCH_SPEED_PRECISION,
187 snprintf(buf, sizeof(buf), "%d.%d%%", 290 (speed % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
188 pitch / 10, pitch % 10);
189#if CONFIG_CODEC == SWCODEC
190 } 291 }
191 else 292 else
293#endif
192 { 294 {
193 /* "Speed:XXX%" */ 295 if(global_settings.pitch_mode_semitone)
194 snprintf(buf, sizeof(buf), "%s:%d%%", str(LANG_SPEED), 296 {
195 speed / 1000); 297 snprintf(buf, sizeof(buf), "%s%ld.%02ld",
298 semitone >= 0 ? "+" : "-",
299 ABS(semitone / PITCH_SPEED_PRECISION),
300 ABS((semitone % PITCH_SPEED_PRECISION) /
301 (PITCH_SPEED_PRECISION / 100))
302 );
303 }
304 else
305 {
306 snprintf(buf, sizeof(buf), "%ld.%ld%%",
307 pitch / PITCH_SPEED_PRECISION,
308 (pitch % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
309 }
196 } 310 }
197#endif 311
198 display->getstringsize(buf, &w, &h); 312 display->getstringsize(buf, &w, &h);
199 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2), 313 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
200 (show_lang_pitch ? h : h/2), buf); 314 show_lang_pitch ? (pitch_viewports[PITCH_MID].height / 2) :
315 (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
316 buf);
201 if (w > width_used) 317 if (w > width_used)
202 width_used = w; 318 width_used = w;
203 319
320 /* "limit" and "timestretch" labels */
321 if (max_lines >= 7)
322 {
323 if(at_limit)
324 {
325 snprintf(buf, sizeof(buf), "%s", str(LANG_STRETCH_LIMIT));
326 display->getstringsize(buf, &w, &h);
327 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
328 (pitch_viewports[PITCH_MID].height / 2) + h, buf);
329 if (w > width_used)
330 width_used = w;
331 }
332 }
333
204 /* Middle section left/right labels */ 334 /* Middle section left/right labels */
205 const char *leftlabel = "-2%"; 335 const char *leftlabel = "-2%";
206 const char *rightlabel = "+2%"; 336 const char *rightlabel = "+2%";
207#if CONFIG_CODEC == SWCODEC 337#if CONFIG_CODEC == SWCODEC
208 if (pitch_mode_timestretch) 338 if (global_settings.pitch_mode_timestretch)
209 { 339 {
210 leftlabel = "<<"; 340 leftlabel = "<<";
211 rightlabel = ">>"; 341 rightlabel = ">>";
@@ -220,37 +350,67 @@ static void pitchscreen_draw(struct screen *display, int max_lines,
220 350
221 if (width_used <= pitch_viewports[PITCH_MID].width) 351 if (width_used <= pitch_viewports[PITCH_MID].width)
222 { 352 {
223 display->putsxy(0, h / 2, leftlabel); 353 display->putsxy(0, (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
224 display->putsxy(pitch_viewports[PITCH_MID].width - w, h /2, rightlabel); 354 leftlabel);
355 display->putsxy((pitch_viewports[PITCH_MID].width - w),
356 (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
357 rightlabel);
225 } 358 }
226 display->update_viewport(); 359 display->update_viewport();
227 display->set_viewport(NULL); 360 display->set_viewport(NULL);
228} 361}
229 362
230static int pitch_increase(int pitch, int pitch_delta, bool allow_cutoff) 363static int32_t pitch_increase(int32_t pitch, int32_t pitch_delta, bool allow_cutoff
364#if CONFIG_CODEC == SWCODEC
365 /* need this to maintain correct pitch/speed caps */
366 , int32_t speed
367#endif
368 )
231{ 369{
232 int new_pitch; 370 int32_t new_pitch;
371#if CONFIG_CODEC == SWCODEC
372 int32_t new_stretch;
373#endif
374 at_limit = false;
233 375
234 if (pitch_delta < 0) 376 if (pitch_delta < 0)
235 { 377 {
236 if (pitch + pitch_delta >= PITCH_MIN) 378 /* for large jumps, snap up to whole numbers */
237 new_pitch = pitch + pitch_delta; 379 if(allow_cutoff && pitch_delta <= -PITCH_SPEED_PRECISION &&
238 else 380 (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
381 {
382 pitch_delta += PITCH_SPEED_PRECISION - ((pitch + pitch_delta) % PITCH_SPEED_PRECISION);
383 }
384
385 new_pitch = pitch + pitch_delta;
386
387 if (new_pitch < PITCH_MIN)
239 { 388 {
240 if (!allow_cutoff) 389 if (!allow_cutoff)
390 {
241 return pitch; 391 return pitch;
392 }
242 new_pitch = PITCH_MIN; 393 new_pitch = PITCH_MIN;
394 at_limit = true;
243 } 395 }
244 } 396 }
245 else if (pitch_delta > 0) 397 else if (pitch_delta > 0)
246 { 398 {
247 if (pitch + pitch_delta <= PITCH_MAX) 399 /* for large jumps, snap down to whole numbers */
248 new_pitch = pitch + pitch_delta; 400 if(allow_cutoff && pitch_delta >= PITCH_SPEED_PRECISION &&
249 else 401 (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
402 {
403 pitch_delta -= (pitch + pitch_delta) % PITCH_SPEED_PRECISION;
404 }
405
406 new_pitch = pitch + pitch_delta;
407
408 if (new_pitch > PITCH_MAX)
250 { 409 {
251 if (!allow_cutoff) 410 if (!allow_cutoff)
252 return pitch; 411 return pitch;
253 new_pitch = PITCH_MAX; 412 new_pitch = PITCH_MAX;
413 at_limit = true;
254 } 414 }
255 } 415 }
256 else 416 else
@@ -258,47 +418,164 @@ static int pitch_increase(int pitch, int pitch_delta, bool allow_cutoff)
258 /* pitch_delta == 0 -> no real change */ 418 /* pitch_delta == 0 -> no real change */
259 return pitch; 419 return pitch;
260 } 420 }
421#if CONFIG_CODEC == SWCODEC
422 if (dsp_timestretch_available())
423 {
424 /* increase the multiple to increase precision of this calculation */
425 new_stretch = GET_STRETCH(new_pitch, speed);
426 if(new_stretch < STRETCH_MIN)
427 {
428 /* we have to ignore allow_cutoff, because we can't have the */
429 /* stretch go higher than STRETCH_MAX */
430 new_pitch = GET_PITCH(speed, STRETCH_MIN);
431 }
432 else if(new_stretch > STRETCH_MAX)
433 {
434 /* we have to ignore allow_cutoff, because we can't have the */
435 /* stretch go higher than STRETCH_MAX */
436 new_pitch = GET_PITCH(speed, STRETCH_MAX);
437 }
438
439 if(new_stretch >= STRETCH_MAX ||
440 new_stretch <= STRETCH_MIN)
441 {
442 at_limit = true;
443 }
444 }
445#endif
446
261 sound_set_pitch(new_pitch); 447 sound_set_pitch(new_pitch);
262 448
263 return new_pitch; 449 return new_pitch;
264} 450}
265 451
266/* Factor for changing the pitch one half tone up. 452static int32_t get_semitone_from_pitch(int32_t pitch)
267 The exact value is 2^(1/12) = 1.05946309436
268 But we use only integer arithmetics, so take
269 rounded factor multiplied by 10^5=100,000. This is
270 enough to get the same promille values as if we
271 had used floating point (checked with a spread
272 sheet).
273 */
274#define PITCH_SEMITONE_FACTOR 105946L
275
276/* Some helpful constants. K is the scaling factor for SEMITONE.
277 N is for more accurate rounding
278 KN is K * N
279 */
280#define PITCH_K_FCT 100000UL
281#define PITCH_N_FCT 10
282#define PITCH_KN_FCT 1000000UL
283
284static int pitch_increase_semitone(int pitch, bool up)
285{ 453{
286 uint32_t tmp; 454 int semitone = 0;
287 uint32_t round_fct; /* How much to scale down at the end */ 455 int32_t fractional_index = 0;
288 tmp = pitch; 456
289 if (up) 457 while(semitone < NUM_SEMITONES - 1 &&
458 pitch >= semitone_table[semitone + 1])
459 {
460 semitone++;
461 }
462
463
464 /* now find the fractional part */
465 while(pitch > (cent_interp[fractional_index + 1] *
466 semitone_table[semitone] / PITCH_SPEED_100))
290 { 467 {
291 tmp = tmp * PITCH_SEMITONE_FACTOR; 468 /* Check to make sure fractional_index isn't too big */
292 round_fct = PITCH_K_FCT; 469 /* This should never happen. */
470 if(fractional_index >= CENT_INTERP_NUM - 1)
471 {
472 break;
473 }
474 fractional_index++;
475 }
476
477 int32_t semitone_pitch_a = cent_interp[fractional_index] *
478 semitone_table[semitone] /
479 PITCH_SPEED_100;
480 int32_t semitone_pitch_b = cent_interp[fractional_index + 1] *
481 semitone_table[semitone] /
482 PITCH_SPEED_100;
483 /* this will be the integer offset from the cent_interp entry */
484 int32_t semitone_frac_ofs = (pitch - semitone_pitch_a) * CENT_INTERP_INTERVAL /
485 (semitone_pitch_b - semitone_pitch_a);
486 semitone = (semitone + SEMITONE_START) * PITCH_SPEED_PRECISION +
487 fractional_index * CENT_INTERP_INTERVAL +
488 semitone_frac_ofs;
489
490 return semitone;
491}
492
493static int32_t get_pitch_from_semitone(int32_t semitone)
494{
495 int32_t adjusted_semitone = semitone - SEMITONE_START * PITCH_SPEED_PRECISION;
496
497 /* Find the index into the semitone table */
498 int32_t semitone_index = (adjusted_semitone / PITCH_SPEED_PRECISION);
499
500 /* set pitch to the semitone's integer part value */
501 int32_t pitch = semitone_table[semitone_index];
502 /* get the range of the cent modification for future calculation */
503 int32_t pitch_mod_a =
504 cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
505 CENT_INTERP_INTERVAL];
506 int32_t pitch_mod_b =
507 cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
508 CENT_INTERP_INTERVAL + 1];
509 /* figure out the cent mod amount based on the semitone fractional value */
510 int32_t pitch_mod = pitch_mod_a + (pitch_mod_b - pitch_mod_a) *
511 (adjusted_semitone % CENT_INTERP_INTERVAL) / CENT_INTERP_INTERVAL;
512
513 /* modify pitch based on the mod amount we just calculated */
514 return (pitch * pitch_mod + PITCH_SPEED_100 / 2) / PITCH_SPEED_100;
515}
516
517static int32_t pitch_increase_semitone(int32_t pitch,
518 int32_t current_semitone,
519 int32_t semitone_delta
520#if CONFIG_CODEC == SWCODEC
521 , int32_t speed
522#endif
523 )
524{
525 int32_t new_semitone = current_semitone;
526
527 /* snap to the delta interval */
528 if(current_semitone % semitone_delta != 0)
529 {
530 if(current_semitone > 0 && semitone_delta > 0)
531 new_semitone += semitone_delta;
532 else if(current_semitone < 0 && semitone_delta < 0)
533 new_semitone += semitone_delta;
534
535 new_semitone -= new_semitone % semitone_delta;
293 } 536 }
294 else 537 else
538 new_semitone += semitone_delta;
539
540 /* clamp the pitch so it doesn't go beyond the pitch limits */
541 if(new_semitone < (SEMITONE_START * PITCH_SPEED_PRECISION))
542 {
543 new_semitone = SEMITONE_START * PITCH_SPEED_PRECISION;
544 at_limit = true;
545 }
546 else if(new_semitone > (SEMITONE_END * PITCH_SPEED_PRECISION))
295 { 547 {
296 tmp = (tmp * PITCH_KN_FCT) / PITCH_SEMITONE_FACTOR; 548 new_semitone = SEMITONE_END * PITCH_SPEED_PRECISION;
297 round_fct = PITCH_N_FCT; 549 at_limit = true;
298 } 550 }
299 /* Scaling down with rounding */ 551
300 tmp = (tmp + round_fct / 2) / round_fct; 552 int32_t new_pitch = get_pitch_from_semitone(new_semitone);
301 return pitch_increase(pitch, tmp - pitch, false); 553
554#if CONFIG_CODEC == SWCODEC
555 int32_t new_stretch = GET_STRETCH(new_pitch, speed);
556
557 /* clamp the pitch so it doesn't go beyond the stretch limits */
558 if( new_stretch > STRETCH_MAX)
559 {
560 new_pitch = GET_PITCH(speed, STRETCH_MAX);
561 new_semitone = get_semitone_from_pitch(new_pitch);
562 at_limit = true;
563 }
564 else if (new_stretch < STRETCH_MIN)
565 {
566 new_pitch = GET_PITCH(speed, STRETCH_MIN);
567 new_semitone = get_semitone_from_pitch(new_pitch);
568 at_limit = true;
569 }
570#endif
571
572 pitch_increase(pitch, new_pitch - pitch, false
573#if CONFIG_CODEC == SWCODEC
574 , speed
575#endif
576 );
577
578 return new_semitone;
302} 579}
303 580
304/* 581/*
@@ -310,13 +587,11 @@ static int pitch_increase_semitone(int pitch, bool up)
310int gui_syncpitchscreen_run(void) 587int gui_syncpitchscreen_run(void)
311{ 588{
312 int button, i; 589 int button, i;
313 int pitch = sound_get_pitch(); 590 int32_t pitch = sound_get_pitch();
314#if CONFIG_CODEC == SWCODEC 591 int32_t semitone;
315 int stretch = dsp_get_timestretch(); 592
316 int speed = stretch * pitch; /* speed to maintain */ 593 int32_t new_pitch;
317#endif 594 int32_t pitch_delta;
318 int new_pitch;
319 int pitch_delta;
320 bool nudged = false; 595 bool nudged = false;
321 bool exit = false; 596 bool exit = false;
322 /* should maybe be passed per parameter later, not needed for now */ 597 /* should maybe be passed per parameter later, not needed for now */
@@ -324,6 +599,31 @@ int gui_syncpitchscreen_run(void)
324 struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT]; 599 struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT];
325 int max_lines[NB_SCREENS]; 600 int max_lines[NB_SCREENS];
326 601
602#if CONFIG_CODEC == SWCODEC
603 int32_t new_speed = 0, new_stretch;
604
605 /* the speed variable holds the apparent speed of the playback */
606 int32_t speed;
607 if (dsp_timestretch_available())
608 {
609 speed = GET_SPEED(pitch, dsp_get_timestretch());
610 }
611 else
612 {
613 speed = pitch;
614 }
615
616 /* Figure out whether to be in timestretch mode */
617 if (global_settings.pitch_mode_timestretch && !dsp_timestretch_available())
618 {
619 global_settings.pitch_mode_timestretch = false;
620 settings_save();
621 }
622#endif
623
624 /* set the semitone index based on the current pitch */
625 semitone = get_semitone_from_pitch(pitch);
626
327 /* initialize pitchscreen vps */ 627 /* initialize pitchscreen vps */
328 FOR_NB_SCREENS(i) 628 FOR_NB_SCREENS(i)
329 { 629 {
@@ -343,49 +643,80 @@ int gui_syncpitchscreen_run(void)
343 { 643 {
344 FOR_NB_SCREENS(i) 644 FOR_NB_SCREENS(i)
345 pitchscreen_draw(&screens[i], max_lines[i], 645 pitchscreen_draw(&screens[i], max_lines[i],
346 pitch_viewports[i], pitch 646 pitch_viewports[i], pitch, semitone
347#if CONFIG_CODEC == SWCODEC 647#if CONFIG_CODEC == SWCODEC
348 , speed 648 , speed
349#endif 649#endif
350 ); 650 );
351 pitch_delta = 0; 651 pitch_delta = 0;
652#if CONFIG_CODEC == SWCODEC
653 new_speed = 0;
654#endif
352 button = get_action(CONTEXT_PITCHSCREEN, HZ); 655 button = get_action(CONTEXT_PITCHSCREEN, HZ);
353 switch (button) 656 switch (button)
354 { 657 {
355 case ACTION_PS_INC_SMALL: 658 case ACTION_PS_INC_SMALL:
356 pitch_delta = PITCH_SMALL_DELTA; 659 if(global_settings.pitch_mode_semitone)
660 pitch_delta = SEMITONE_SMALL_DELTA;
661 else
662 pitch_delta = PITCH_SMALL_DELTA;
357 break; 663 break;
358 664
359 case ACTION_PS_INC_BIG: 665 case ACTION_PS_INC_BIG:
360 pitch_delta = PITCH_BIG_DELTA; 666 if(global_settings.pitch_mode_semitone)
667 pitch_delta = SEMITONE_BIG_DELTA;
668 else
669 pitch_delta = PITCH_BIG_DELTA;
361 break; 670 break;
362 671
363 case ACTION_PS_DEC_SMALL: 672 case ACTION_PS_DEC_SMALL:
364 pitch_delta = -PITCH_SMALL_DELTA; 673 if(global_settings.pitch_mode_semitone)
674 pitch_delta = -SEMITONE_SMALL_DELTA;
675 else
676 pitch_delta = -PITCH_SMALL_DELTA;
365 break; 677 break;
366 678
367 case ACTION_PS_DEC_BIG: 679 case ACTION_PS_DEC_BIG:
368 pitch_delta = -PITCH_BIG_DELTA; 680 if(global_settings.pitch_mode_semitone)
681 pitch_delta = -SEMITONE_BIG_DELTA;
682 else
683 pitch_delta = -PITCH_BIG_DELTA;
369 break; 684 break;
370 685
371 case ACTION_PS_NUDGE_RIGHT: 686 case ACTION_PS_NUDGE_RIGHT:
372#if CONFIG_CODEC == SWCODEC 687#if CONFIG_CODEC == SWCODEC
373 if (!pitch_mode_timestretch) 688 if (!global_settings.pitch_mode_timestretch)
374 { 689 {
375#endif 690#endif
376 new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false); 691 new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
692#if CONFIG_CODEC == SWCODEC
693 , speed
694#endif
695 );
377 nudged = (new_pitch != pitch); 696 nudged = (new_pitch != pitch);
378 pitch = new_pitch; 697 pitch = new_pitch;
698 semitone = get_semitone_from_pitch(pitch);
699#if CONFIG_CODEC == SWCODEC
700 speed = pitch;
701#endif
379 break; 702 break;
380#if CONFIG_CODEC == SWCODEC 703#if CONFIG_CODEC == SWCODEC
381 } 704 }
705 else
706 {
707 new_speed = speed + SPEED_SMALL_DELTA;
708 at_limit = false;
709 }
710 break;
382 711
383 case ACTION_PS_FASTER: 712 case ACTION_PS_FASTER:
384 if (pitch_mode_timestretch && stretch < STRETCH_MAX) 713 if (global_settings.pitch_mode_timestretch)
385 { 714 {
386 stretch++; 715 new_speed = speed + SPEED_BIG_DELTA;
387 dsp_set_timestretch(stretch); 716 /* snap to whole numbers */
388 speed = stretch * pitch; 717 if(new_speed % PITCH_SPEED_PRECISION != 0)
718 new_speed -= new_speed % PITCH_SPEED_PRECISION;
719 at_limit = false;
389 } 720 }
390 break; 721 break;
391#endif 722#endif
@@ -393,29 +724,53 @@ int gui_syncpitchscreen_run(void)
393 case ACTION_PS_NUDGE_RIGHTOFF: 724 case ACTION_PS_NUDGE_RIGHTOFF:
394 if (nudged) 725 if (nudged)
395 { 726 {
396 pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false); 727 pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
728#if CONFIG_CODEC == SWCODEC
729 , speed
730#endif
731 );
732#if CONFIG_CODEC == SWCODEC
733 speed = pitch;
734#endif
735 semitone = get_semitone_from_pitch(pitch);
397 nudged = false; 736 nudged = false;
398 } 737 }
399 break; 738 break;
400 739
401 case ACTION_PS_NUDGE_LEFT: 740 case ACTION_PS_NUDGE_LEFT:
402#if CONFIG_CODEC == SWCODEC 741#if CONFIG_CODEC == SWCODEC
403 if (!pitch_mode_timestretch) 742 if (!global_settings.pitch_mode_timestretch)
404 { 743 {
405#endif 744#endif
406 new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false); 745 new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
746#if CONFIG_CODEC == SWCODEC
747 , speed
748#endif
749 );
407 nudged = (new_pitch != pitch); 750 nudged = (new_pitch != pitch);
408 pitch = new_pitch; 751 pitch = new_pitch;
752 semitone = get_semitone_from_pitch(pitch);
753#if CONFIG_CODEC == SWCODEC
754 speed = pitch;
755#endif
409 break; 756 break;
410#if CONFIG_CODEC == SWCODEC 757#if CONFIG_CODEC == SWCODEC
411 } 758 }
759 else
760 {
761 new_speed = speed - SPEED_SMALL_DELTA;
762 at_limit = false;
763 }
764 break;
412 765
413 case ACTION_PS_SLOWER: 766 case ACTION_PS_SLOWER:
414 if (pitch_mode_timestretch && stretch > STRETCH_MIN) 767 if (global_settings.pitch_mode_timestretch)
415 { 768 {
416 stretch--; 769 new_speed = speed - SPEED_BIG_DELTA;
417 dsp_set_timestretch(stretch); 770 /* snap to whole numbers */
418 speed = stretch * pitch; 771 if(new_speed % PITCH_SPEED_PRECISION != 0)
772 new_speed += PITCH_SPEED_PRECISION - speed % PITCH_SPEED_PRECISION;
773 at_limit = false;
419 } 774 }
420 break; 775 break;
421#endif 776#endif
@@ -423,27 +778,49 @@ int gui_syncpitchscreen_run(void)
423 case ACTION_PS_NUDGE_LEFTOFF: 778 case ACTION_PS_NUDGE_LEFTOFF:
424 if (nudged) 779 if (nudged)
425 { 780 {
426 pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false); 781 pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
782#if CONFIG_CODEC == SWCODEC
783 , speed
784#endif
785 );
786#if CONFIG_CODEC == SWCODEC
787 speed = pitch;
788#endif
789 semitone = get_semitone_from_pitch(pitch);
427 nudged = false; 790 nudged = false;
428 } 791 }
429 break; 792 break;
430 793
431 case ACTION_PS_RESET: 794 case ACTION_PS_RESET:
432 pitch = 1000; 795 pitch = PITCH_SPEED_100;
433 sound_set_pitch(pitch); 796 sound_set_pitch(pitch);
434#if CONFIG_CODEC == SWCODEC 797#if CONFIG_CODEC == SWCODEC
435 stretch = 100; 798 speed = PITCH_SPEED_100;
436 dsp_set_timestretch(stretch); 799 if (dsp_timestretch_available())
437 speed = stretch * pitch; 800 {
801 dsp_set_timestretch(PITCH_SPEED_100);
802 at_limit = false;
803 }
438#endif 804#endif
805 semitone = get_semitone_from_pitch(pitch);
439 break; 806 break;
440 807
441 case ACTION_PS_TOGGLE_MODE: 808 case ACTION_PS_TOGGLE_MODE:
809 global_settings.pitch_mode_semitone = !global_settings.pitch_mode_semitone;
442#if CONFIG_CODEC == SWCODEC 810#if CONFIG_CODEC == SWCODEC
443 if (dsp_timestretch_available() && pitch_mode_semitone) 811
444 pitch_mode_timestretch = !pitch_mode_timestretch; 812 if (dsp_timestretch_available() && !global_settings.pitch_mode_semitone)
813 {
814 global_settings.pitch_mode_timestretch = !global_settings.pitch_mode_timestretch;
815 if(!global_settings.pitch_mode_timestretch)
816 {
817 /* no longer in timestretch mode. Reset speed */
818 speed = pitch;
819 dsp_set_timestretch(PITCH_SPEED_100);
820 }
821 }
822 settings_save();
445#endif 823#endif
446 pitch_mode_semitone = !pitch_mode_semitone;
447 break; 824 break;
448 825
449 case ACTION_PS_EXIT: 826 case ACTION_PS_EXIT:
@@ -457,27 +834,73 @@ int gui_syncpitchscreen_run(void)
457 } 834 }
458 if (pitch_delta) 835 if (pitch_delta)
459 { 836 {
460 if (pitch_mode_semitone) 837 if (global_settings.pitch_mode_semitone)
461 pitch = pitch_increase_semitone(pitch, pitch_delta > 0); 838 {
839 semitone = pitch_increase_semitone(pitch, semitone, pitch_delta
840#if CONFIG_CODEC == SWCODEC
841 , speed
842#endif
843 );
844 pitch = get_pitch_from_semitone(semitone);
845 }
462 else 846 else
463 pitch = pitch_increase(pitch, pitch_delta, true); 847 {
848 pitch = pitch_increase(pitch, pitch_delta, true
464#if CONFIG_CODEC == SWCODEC 849#if CONFIG_CODEC == SWCODEC
465 if (pitch_mode_timestretch) 850 , speed
851#endif
852 );
853 semitone = get_semitone_from_pitch(pitch);
854 }
855#if CONFIG_CODEC == SWCODEC
856 if (global_settings.pitch_mode_timestretch)
466 { 857 {
467 /* Set stretch to maintain speed */ 858 /* do this to make sure we properly obey the stretch limits */
468 /* i.e. increase pitch, reduce stretch */ 859 new_speed = speed;
469 int new_stretch = speed / pitch;
470 if (new_stretch >= STRETCH_MIN && new_stretch <= STRETCH_MAX)
471 {
472 stretch = new_stretch;
473 dsp_set_timestretch(stretch);
474 }
475 } 860 }
476 else 861 else
477 speed = stretch * pitch; 862 {
478#endif 863 speed = pitch;
864 }
865#endif
479 } 866 }
480 } 867
868#if CONFIG_CODEC == SWCODEC
869 if(new_speed)
870 {
871 new_stretch = GET_STRETCH(pitch, new_speed);
872
873 /* limit the amount of stretch */
874 if(new_stretch > STRETCH_MAX)
875 {
876 new_stretch = STRETCH_MAX;
877 new_speed = GET_SPEED(pitch, new_stretch);
878 }
879 else if(new_stretch < STRETCH_MIN)
880 {
881 new_stretch = STRETCH_MIN;
882 new_speed = GET_SPEED(pitch, new_stretch);
883 }
884
885 new_stretch = GET_STRETCH(pitch, new_speed);
886 if(new_stretch >= STRETCH_MAX ||
887 new_stretch <= STRETCH_MIN)
888 {
889 at_limit = true;
890 }
891
892 /* set the amount of stretch */
893 dsp_set_timestretch(new_stretch);
894
895 /* update the speed variable with the new speed */
896 speed = new_speed;
897
898 /* Reset new_speed so we only call dsp_set_timestretch */
899 /* when needed */
900 new_speed = 0;
901 }
902#endif
903}
481#if CONFIG_CODEC == SWCODEC 904#if CONFIG_CODEC == SWCODEC
482 pcmbuf_set_low_latency(false); 905 pcmbuf_set_low_latency(false);
483#endif 906#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 @@
22#ifndef _PITCHSCREEN_H_ 22#ifndef _PITCHSCREEN_H_
23#define _PITCHSCREEN_H_ 23#define _PITCHSCREEN_H_
24 24
25/* precision of the pitch and speed variables */
26/* One zero per decimal (100 means two decimal places */
27#define PITCH_SPEED_PRECISION 100L
28#define PITCH_SPEED_100 (100L * PITCH_SPEED_PRECISION) /* 100% speed */
29
25int gui_syncpitchscreen_run(void); 30int gui_syncpitchscreen_run(void);
26 31
27#endif /* _PITCHSCREEN_H_ */ 32#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 @@
12604 remote: "Remote Statusbar" 12604 remote: "Remote Statusbar"
12605 </voice> 12605 </voice>
12606</phrase> 12606</phrase>
12607<phrase>
12608 id: LANG_SEMITONE
12609 desc:
12610 user: core
12611 <source>
12612 *: "Semitone"
12613 </source>
12614 <dest>
12615 *: "Semitone"
12616 </dest>
12617 <voice>
12618 *: "Semitone"
12619 </voice>
12620</phrase>
12621<phrase>
12622 id: LANG_STRETCH_LIMIT
12623 desc: "limit" in pitch screen
12624 user: core
12625 <source>
12626 *: "Limit"
12627 </source>
12628 <dest>
12629 *: "Limit"
12630 </dest>
12631 <voice>
12632 *: "Limit"
12633 </voice>
12634</phrase>
12635<phrase>
12636 id: LANG_PLAYBACK_RATE
12637 desc: "rate" in pitch screen
12638 user: core
12639 <source>
12640 *: "Rate"
12641 </source>
12642 <dest>
12643 *: "Rate"
12644 </dest>
12645 <voice>
12646 *: "Rate"
12647 </voice>
12648</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);
128#define PLUGIN_MAGIC 0x526F634B /* RocK */ 128#define PLUGIN_MAGIC 0x526F634B /* RocK */
129 129
130/* increase this every time the api struct changes */ 130/* increase this every time the api struct changes */
131#define PLUGIN_API_VERSION 159 131#define PLUGIN_API_VERSION 160
132 132
133/* update this to latest version if a change to the api struct breaks 133/* update this to latest version if a change to the api struct breaks
134 backwards compatibility (and please take the opportunity to sort in any 134 backwards compatibility (and please take the opportunity to sort in any
135 new function which are "waiting" at the end of the function table) */ 135 new function which are "waiting" at the end of the function table) */
136#define PLUGIN_MIN_API_VERSION 159 136#define PLUGIN_MIN_API_VERSION 160
137 137
138/* plugin return codes */ 138/* plugin return codes */
139enum plugin_status { 139enum plugin_status {
@@ -618,7 +618,7 @@ struct plugin_api {
618#endif 618#endif
619#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) || \ 619#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) || \
620 (CONFIG_CODEC == SWCODEC) 620 (CONFIG_CODEC == SWCODEC)
621 void (*sound_set_pitch)(int pitch); 621 void (*sound_set_pitch)(int32_t pitch);
622#endif 622#endif
623 623
624 /* MAS communication */ 624 /* 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
739 struct touchscreen_parameter ts_calibration_data; 739 struct touchscreen_parameter ts_calibration_data;
740#endif 740#endif
741 741
742 /* pitch screen settings */
743 bool pitch_mode_semitone;
744#if CONFIG_CODEC == SWCODEC
745 bool pitch_mode_timestretch;
746#endif
742 /* If values are just added to the end, no need to bump plugin API 747 /* If values are just added to the end, no need to bump plugin API
743 version. */ 748 version. */
744 /* new stuff to be added at the end */ 749 /* 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 @@
33#include "settings_list.h" 33#include "settings_list.h"
34#include "sound.h" 34#include "sound.h"
35#include "dsp.h" 35#include "dsp.h"
36#include "debug.h"
37#include "mpeg.h" 36#include "mpeg.h"
38#include "audio.h" 37#include "audio.h"
39#include "power.h" 38#include "power.h"
@@ -1528,6 +1527,14 @@ const struct settings_list settings[] = {
1528 tsc_is_changed, tsc_set_default), 1527 tsc_is_changed, tsc_set_default),
1529#endif 1528#endif
1530 OFFON_SETTING(0, prevent_skip, LANG_PREVENT_SKIPPING, false, "prevent track skip", NULL), 1529 OFFON_SETTING(0, prevent_skip, LANG_PREVENT_SKIPPING, false, "prevent track skip", NULL),
1530
1531 OFFON_SETTING(0, pitch_mode_semitone, LANG_SEMITONE, false,
1532 "Semitone pitch change", NULL),
1533#if CONFIG_CODEC == SWCODEC
1534 OFFON_SETTING(0, pitch_mode_timestretch, LANG_TIMESTRETCH, false,
1535 "Timestretch mode", NULL),
1536#endif
1537
1531}; 1538};
1532 1539
1533const int nb_settings = sizeof(settings)/sizeof(*settings); 1540const 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 @@
25#include <stdio.h> 25#include <stdio.h>
26#include <string.h> 26#include <string.h>
27#include "buffer.h" 27#include "buffer.h"
28#include "debug.h"
29#include "system.h" 28#include "system.h"
30#include "tdspeed.h" 29#include "tdspeed.h"
31#include "settings.h" 30#include "settings.h"
@@ -72,7 +71,7 @@ void tdspeed_init()
72} 71}
73 72
74 73
75bool tdspeed_config(int samplerate, bool stereo, int factor) 74bool tdspeed_config(int samplerate, bool stereo, int32_t factor)
76{ 75{
77 struct tdspeed_state_s *st = &tdspeed_state; 76 struct tdspeed_state_s *st = &tdspeed_state;
78 int src_frame_sz; 77 int src_frame_sz;
@@ -84,7 +83,7 @@ bool tdspeed_config(int samplerate, bool stereo, int factor)
84 return false; 83 return false;
85 84
86 /* Check parameters */ 85 /* Check parameters */
87 if (factor == 100) 86 if (factor == PITCH_SPEED_100)
88 return false; 87 return false;
89 if (samplerate < MIN_RATE || samplerate > MAX_RATE) 88 if (samplerate < MIN_RATE || samplerate > MAX_RATE)
90 return false; 89 return false;
@@ -94,14 +93,14 @@ bool tdspeed_config(int samplerate, bool stereo, int factor)
94 st->stereo = stereo; 93 st->stereo = stereo;
95 st->dst_step = samplerate / MINFREQ; 94 st->dst_step = samplerate / MINFREQ;
96 95
97 if (factor > 100) 96 if (factor > PITCH_SPEED_100)
98 st->dst_step = st->dst_step * 100 / factor; 97 st->dst_step = st->dst_step * PITCH_SPEED_100 / factor;
99 st->dst_order = 1; 98 st->dst_order = 1;
100 99
101 while (st->dst_step >>= 1) 100 while (st->dst_step >>= 1)
102 st->dst_order++; 101 st->dst_order++;
103 st->dst_step = (1 << st->dst_order); 102 st->dst_step = (1 << st->dst_order);
104 st->src_step = st->dst_step * factor / 100; 103 st->src_step = st->dst_step * factor / PITCH_SPEED_100;
105 st->shift_max = (st->dst_step > st->src_step) ? st->dst_step : st->src_step; 104 st->shift_max = (st->dst_step > st->src_step) ? st->dst_step : st->src_step;
106 105
107 src_frame_sz = st->shift_max + st->dst_step; 106 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 @@
23#ifndef _TDSPEED_H 23#ifndef _TDSPEED_H
24#define _TDSPEED_H 24#define _TDSPEED_H
25 25
26#include "dsp.h"
27/* for the precision #defines: */
28#include "pitchscreen.h"
29
26#define TDSPEED_OUTBUFSIZE 4096 30#define TDSPEED_OUTBUFSIZE 4096
27 31
32/* some #define functions to get the pitch, stretch and speed values based on */
33/* two known values. Remember that params are alphabetical. */
34#define GET_SPEED(pitch, stretch) \
35 ((pitch * stretch + PITCH_SPEED_100 / 2L) / PITCH_SPEED_100)
36#define GET_PITCH(speed, stretch) \
37 ((speed * PITCH_SPEED_100 + stretch / 2L) / stretch)
38#define GET_STRETCH(pitch, speed) \
39 ((speed * PITCH_SPEED_100 + pitch / 2L) / pitch)
40
28void tdspeed_init(void); 41void tdspeed_init(void);
29bool tdspeed_config(int samplerate, bool stereo, int factor); 42bool tdspeed_config(int samplerate, bool stereo, int32_t factor);
30long tdspeed_est_output_size(void); 43long tdspeed_est_output_size(void);
31long tdspeed_est_input_size(long size); 44long tdspeed_est_input_size(long size);
32int tdspeed_doit(int32_t *src[], int count); 45int tdspeed_doit(int32_t *src[], int count);
33 46
34#define STRETCH_MAX 250 47#define STRETCH_MAX (250L * PITCH_SPEED_PRECISION) /* 250% */
35#define STRETCH_MIN 35 48#define STRETCH_MIN (35L * PITCH_SPEED_PRECISION) /* 35% */
36 49
37#endif 50#endif
diff --git a/docs/CREDITS b/docs/CREDITS
index cc626f74e6..32e7ea4df0 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -479,6 +479,7 @@ Andre Lupa
479Hilton Shumway 479Hilton Shumway
480Matthew Bonnett 480Matthew Bonnett
481Nick Tryon 481Nick Tryon
482David Johnston
482 483
483The libmad team 484The libmad team
484The wavpack team 485The wavpack team
diff --git a/firmware/export/sound.h b/firmware/export/sound.h
index 70c4a2244e..674b2f6ae2 100644
--- a/firmware/export/sound.h
+++ b/firmware/export/sound.h
@@ -60,8 +60,8 @@ void sound_set(int setting, int value);
60int sound_val2phys(int setting, int value); 60int sound_val2phys(int setting, int value);
61 61
62#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) 62#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
63void sound_set_pitch(int permille); 63void sound_set_pitch(int32_t pitch);
64int sound_get_pitch(void); 64int32_t sound_get_pitch(void);
65#endif 65#endif
66 66
67#endif 67#endif
diff --git a/firmware/sound.c b/firmware/sound.c
index f4a2f87ca5..6a2f03df00 100644
--- a/firmware/sound.c
+++ b/firmware/sound.c
@@ -25,6 +25,8 @@
25#include "sound.h" 25#include "sound.h"
26#include "logf.h" 26#include "logf.h"
27#include "system.h" 27#include "system.h"
28/* for the pitch and speed precision #defines: */
29#include "pitchscreen.h"
28#ifndef SIMULATOR 30#ifndef SIMULATOR
29#include "i2c.h" 31#include "i2c.h"
30#include "mas.h" 32#include "mas.h"
@@ -159,6 +161,7 @@ sound_set_type* sound_get_fn(int setting)
159 161
160#if CONFIG_CODEC == SWCODEC 162#if CONFIG_CODEC == SWCODEC
161/* Copied from dsp.h, nasty nasty, but we don't want to include dsp.h */ 163/* Copied from dsp.h, nasty nasty, but we don't want to include dsp.h */
164
162enum { 165enum {
163 DSP_CALLBACK_SET_PRESCALE = 0, 166 DSP_CALLBACK_SET_PRESCALE = 0,
164 DSP_CALLBACK_SET_BASS, 167 DSP_CALLBACK_SET_BASS,
@@ -698,18 +701,18 @@ int sound_val2phys(int setting, int value)
698 crystal frequency than we actually have. It will adjust its internal 701 crystal frequency than we actually have. It will adjust its internal
699 parameters and the result is that the audio is played at another pitch. 702 parameters and the result is that the audio is played at another pitch.
700 703
701 The pitch value is in tenths of percent. 704 The pitch value precision is based on PITCH_SPEED_PRECISION (in dsp.h)
702*/ 705*/
703static int last_pitch = 1000; 706static int last_pitch = PITCH_SPEED_100;
704 707
705void sound_set_pitch(int pitch) 708void sound_set_pitch(int32_t pitch)
706{ 709{
707 unsigned long val; 710 unsigned long val;
708 711
709 if (pitch != last_pitch) 712 if (pitch != last_pitch)
710 { 713 {
711 /* Calculate the new (bogus) frequency */ 714 /* Calculate the new (bogus) frequency */
712 val = 18432 * 1000 / pitch; 715 val = 18432 * PITCH_SPEED_100 / pitch;
713 716
714 mas_writemem(MAS_BANK_D0, MAS_D0_OFREQ_CONTROL, &val, 1); 717 mas_writemem(MAS_BANK_D0, MAS_D0_OFREQ_CONTROL, &val, 1);
715 718
@@ -721,19 +724,19 @@ void sound_set_pitch(int pitch)
721 } 724 }
722} 725}
723 726
724int sound_get_pitch(void) 727int32_t sound_get_pitch(void)
725{ 728{
726 return last_pitch; 729 return last_pitch;
727} 730}
728#else /* SIMULATOR */ 731#else /* SIMULATOR */
729void sound_set_pitch(int pitch) 732void sound_set_pitch(int32_t pitch)
730{ 733{
731 (void)pitch; 734 (void)pitch;
732} 735}
733 736
734int sound_get_pitch(void) 737int32_t sound_get_pitch(void)
735{ 738{
736 return 1000; 739 return PITCH_SPEED_100;
737} 740}
738#endif /* SIMULATOR */ 741#endif /* SIMULATOR */
739#endif /* (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) */ 742#endif /* (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) */
diff --git a/manual/rockbox_interface/wps.tex b/manual/rockbox_interface/wps.tex
index 2d637d6830..de47f97eb5 100644
--- a/manual/rockbox_interface/wps.tex
+++ b/manual/rockbox_interface/wps.tex
@@ -273,34 +273,52 @@ Delete the currently playing file.
273\nopt{player}{ 273\nopt{player}{
274 \subsubsection{\label{sec:pitchscreen}Pitch} 274 \subsubsection{\label{sec:pitchscreen}Pitch}
275 275
276 The \setting{Pitch Screen} allows you to change the pitch and the playback 276 The \setting{Pitch Screen} allows you to change the rate of playback
277 speed of your \dap. The pitch value can be adjusted between 50\% and 200\%. 277 (i.e. the playback speed and at the same time the pitch) of your
278 50\% means half the normal playback speed and the pitch that is an octave lower 278 \dap. The rate value can be adjusted between 50\% and 200\%. 50\%
279 than the normal pitch. 200\% means double playback speed and the pitch that 279 means half the normal playback speed and a pitch that is an octave
280 is an octave higher than the normal pitch. 280 lower than the normal pitch. 200\% means double playback speed and a
281 pitch that is an octave higher than the normal pitch.
281 282
282 \opt{masf}{ 283 The rate can be changed in two modes: procentual and semitone.
283 Changing the pitch can be done in two modes: procentual and semitone. 284 Initially, procentual mode is active.
284 Initially (after the \dap{} is switched on), procentual mode is active. 285
286 \opt{swcodec}{
287 If you've enabled the \setting{Timestretch} option in
288 \setting{Sound Settings} and have since rebooted, you can also use
289 timestretch mode. This allows you to change the playback speed
290 without affecting the pitch, and vice versa.
291
292 In timestretch mode there are separate displays for pitch and
293 speed, and each can be altered independently. Due to the
294 limitations of the algorithm, speed is limited to be between 35\%
295 and 250\% of the current pitch value. Pitch must maintain the
296 same ratio as well as remain between 50\% and 200\%.
297 }
298
299 The value of the \opt{swcodec}{rate, pitch and speed}\nopt{swcodec}{rate}
300 is not persisted, i.e. after the \dap\ is turned on it will
301 always be set to 100\%.
285 302
303 \opt{masf}{
286 \begin{table} 304 \begin{table}
287 \begin{btnmap}{}{} 305 \begin{btnmap}{}{}
288 \ActionPsToggleMode 306 \ActionPsToggleMode
289 & Toggle pitch changing mode \\ 307 & Toggle pitch changing mode \\
290 % 308 %
291 \ActionPsIncSmall{} / \ActionPsDecSmall 309 \ActionPsIncSmall{} / \ActionPsDecSmall
292 & Increase / Decrease pitch by 0.1\% (in procentual mode) or a semitone 310 & Increase / Decrease pitch by 0.1\% (in procentual mode) or by 0.1
293 (in semitone mode)\\ 311 semitone (in semitone mode)\\
294 % 312 %
295 \ActionPsIncBig{} / \ActionPsDecBig 313 \ActionPsIncBig{} / \ActionPsDecBig
296 & Increase / Decrease pitch by 1\% (in procentual mode) or a semitone 314 & Increase / Decrease pitch by 1\% (in procentual mode) or a semitone
297 (in semitone mode)\\ 315 (in semitone mode)\\
298 % 316 %
299 \ActionPsNudgeLeft{} / \ActionPsNudgeRight 317 \ActionPsNudgeLeft{} / \ActionPsNudgeRight
300 & Temporarily change pitch by 2.0\% (beatmatch) \\ 318 & Temporarily change pitch by 2\% (beatmatch) \\
301 % 319 %
302 \ActionPsReset 320 \ActionPsReset
303 & Reset pitch to 100\% \\ 321 & Reset rate to 100\% \\
304 % 322 %
305 \ActionPsExit 323 \ActionPsExit
306 & Leave the Pitch Screen \\ 324 & Leave the Pitch Screen \\
@@ -312,23 +330,16 @@ Delete the currently playing file.
312 } 330 }
313 331
314 \opt{swcodec}{ 332 \opt{swcodec}{
315 Changing the pitch can be done in three modes: procentual, semitone and
316 timestretch. Initially (after the \dap{} is switched on), procentual mode is active.
317
318 Timestretch mode allows you to change the playback speed of your recording without
319 affecting the pitch, and vice versa. To access this you must enable the \setting{Timestretch}
320 option in \setting{Sound Settings} and reboot.
321
322 \begin{table} 333 \begin{table}
323 \begin{btnmap}{}{} 334 \begin{btnmap}{}{}
324 \ActionPsToggleMode 335 \ActionPsToggleMode
325 \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsToggleMode} 336 \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsToggleMode}
326 & Toggle pitch changing mode \\ 337 & Toggle pitch changing mode (cycles through all available modes)\\
327 % 338 %
328 \ActionPsIncSmall{} / \ActionPsDecSmall 339 \ActionPsIncSmall{} / \ActionPsDecSmall
329 \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsIncSmall{} / \ActionRCPsDecSmall} 340 \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsIncSmall{} / \ActionRCPsDecSmall}
330 & Increase / Decrease pitch by 0.1\% (in procentual mode) or a semitone 341 & Increase / Decrease pitch by 0.1\% (in procentual mode) or 0.1
331 (in semitone mode)\\ 342 semitone (in semitone mode)\\
332 % 343 %
333 \ActionPsIncBig{} / \ActionPsDecBig 344 \ActionPsIncBig{} / \ActionPsDecBig
334 \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsIncBig{} / \ActionRCPsDecBig} 345 \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsIncBig{} / \ActionRCPsDecBig}
@@ -337,7 +348,7 @@ Delete the currently playing file.
337 % 348 %
338 \ActionPsNudgeLeft{} / \ActionPsNudgeRight 349 \ActionPsNudgeLeft{} / \ActionPsNudgeRight
339 \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsNudgeLeft{} / \ActionPsNudgeRight} 350 \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsNudgeLeft{} / \ActionPsNudgeRight}
340 & Temporarily change pitch by 2.0\% (beatmatch), or modify speed (in timestretch mode) \\ 351 & Temporarily change pitch by 2\% (beatmatch), or modify speed (in timestretch mode) \\
341 % 352 %
342 \ActionPsReset 353 \ActionPsReset
343 \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsReset} 354 \opt{HAVEREMOTEKEYMAP}{& \ActionRCPsReset}