summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWilliam Wilgus <wilgus.william@gmail.com>2021-08-19 22:20:56 -0400
committerWilliam Wilgus <me.theuser@yahoo.com>2021-11-10 22:38:37 -0500
commitcf009b4cbb1eec083ab17ce370df090979eaf68e (patch)
tree19c2346a0e92878ef078879b77e262bf3380ec72
parent67fb558c8997f47440b176fe5553246486b1a838 (diff)
downloadrockbox-cf009b4cbb1eec083ab17ce370df090979eaf68e.tar.gz
rockbox-cf009b4cbb1eec083ab17ce370df090979eaf68e.zip
pitchscreen make it a plugin
full commandline parsing -has pitch, speed, time_stretch /* pitch_screen * accepts args -q, -g, -p=, -s=, -k=; (= sign is optional) * -q silences output splash * -g runs the gui (Runs immediately) * -p100 would set pitch to 100% * -s=90 sets speed to 90% if timestrech is enabled * -k=true -k1 enables time stretch -k0 -kf-kn disables */ Change-Id: I900c4b5d184e12ddbe47509f66fdf3e55193a520
-rw-r--r--apps/gui/pitchscreen.c1055
-rw-r--r--apps/plugin.c17
-rw-r--r--apps/plugin.h23
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES4
-rw-r--r--apps/plugins/lib/arg_helper.c4
-rw-r--r--apps/plugins/lib/arg_helper.h2
-rw-r--r--apps/plugins/pitch_screen.c1279
8 files changed, 1314 insertions, 1071 deletions
diff --git a/apps/gui/pitchscreen.c b/apps/gui/pitchscreen.c
index 871921a10f..9f42aedb5d 100644
--- a/apps/gui/pitchscreen.c
+++ b/apps/gui/pitchscreen.c
@@ -18,1059 +18,8 @@
18 * KIND, either express or implied. 18 * KIND, either express or implied.
19 * 19 *
20 ****************************************************************************/ 20 ****************************************************************************/
21 21#include "plugin.h"
22#include <stdbool.h>
23#include <string.h>
24#include <stdio.h>
25#include <math.h>
26#include <stdlib.h> /* for abs() */
27#include "config.h"
28#include "action.h"
29#include "sound.h"
30#include "pcmbuf.h"
31#include "lang.h"
32#include "icons.h"
33#include "screens.h"
34#include "talk.h"
35#include "viewport.h"
36#include "font.h"
37#include "system.h"
38#include "misc.h"
39#include "pitchscreen.h"
40#include "settings.h"
41#include "tdspeed.h"
42
43#define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */
44 /* on both sides when drawing */
45
46#define PITCH_MAX (200 * PITCH_SPEED_PRECISION)
47#define PITCH_MIN (50 * PITCH_SPEED_PRECISION)
48#define PITCH_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
49#define PITCH_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
50#define PITCH_NUDGE_DELTA (2 * PITCH_SPEED_PRECISION) /* 2% */
51
52#define SPEED_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
53#define SPEED_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
54
55#define SEMITONE_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* 10 cents */
56#define SEMITONE_BIG_DELTA PITCH_SPEED_PRECISION /* 1 semitone */
57
58enum
59{
60 PITCH_TOP = 0,
61 PITCH_MID,
62 PITCH_BOTTOM,
63 PITCH_ITEM_COUNT,
64};
65
66
67/* This is a table of semitone percentage values of the appropriate
68 precision (based on PITCH_SPEED_PRECISION). Note that these are
69 all constant expressions, which will be evaluated at compile time,
70 so no need to worry about how complex the expressions look.
71 That's just to get the precision right.
72
73 I calculated these values, starting from 50, as
74
75 x(n) = 50 * 2^(n/12)
76
77 All that math in each entry simply converts the float constant
78 to an integer equal to PITCH_SPEED_PRECISION times the float value,
79 with as little precision loss as possible (i.e. correctly rounding
80 the last digit).
81*/
82#define TO_INT_WITH_PRECISION(x) \
83 ( (unsigned short)(((x) * PITCH_SPEED_PRECISION * 10 + 5) / 10) )
84
85static const unsigned short semitone_table[] =
86{
87 TO_INT_WITH_PRECISION(50.00000000), /* Octave lower */
88 TO_INT_WITH_PRECISION(52.97315472),
89 TO_INT_WITH_PRECISION(56.12310242),
90 TO_INT_WITH_PRECISION(59.46035575),
91 TO_INT_WITH_PRECISION(62.99605249),
92 TO_INT_WITH_PRECISION(66.74199271),
93 TO_INT_WITH_PRECISION(70.71067812),
94 TO_INT_WITH_PRECISION(74.91535384),
95 TO_INT_WITH_PRECISION(79.37005260),
96 TO_INT_WITH_PRECISION(84.08964153),
97 TO_INT_WITH_PRECISION(89.08987181),
98 TO_INT_WITH_PRECISION(94.38743127),
99 TO_INT_WITH_PRECISION(100.0000000), /* Normal sound */
100 TO_INT_WITH_PRECISION(105.9463094),
101 TO_INT_WITH_PRECISION(112.2462048),
102 TO_INT_WITH_PRECISION(118.9207115),
103 TO_INT_WITH_PRECISION(125.9921049),
104 TO_INT_WITH_PRECISION(133.4839854),
105 TO_INT_WITH_PRECISION(141.4213562),
106 TO_INT_WITH_PRECISION(149.8307077),
107 TO_INT_WITH_PRECISION(158.7401052),
108 TO_INT_WITH_PRECISION(168.1792831),
109 TO_INT_WITH_PRECISION(178.1797436),
110 TO_INT_WITH_PRECISION(188.7748625),
111 TO_INT_WITH_PRECISION(200.0000000) /* Octave higher */
112};
113
114#define NUM_SEMITONES ((int)(sizeof(semitone_table)/sizeof(semitone_table[0])))
115#define SEMITONE_END (NUM_SEMITONES/2)
116#define SEMITONE_START (-SEMITONE_END)
117
118/* A table of values for approximating the cent curve with
119 linear interpolation. Multipy the next lowest semitone
120 by this much to find the corresponding cent percentage.
121
122 These values were calculated as
123 x(n) = 100 * 2^(n * 20/1200)
124*/
125
126static const unsigned short cent_interp[] =
127{
128 TO_INT_WITH_PRECISION(100.0000000),
129 TO_INT_WITH_PRECISION(101.1619440),
130 TO_INT_WITH_PRECISION(102.3373892),
131 TO_INT_WITH_PRECISION(103.5264924),
132 TO_INT_WITH_PRECISION(104.7294123),
133 /* this one's the next semitone but we have it here for convenience */
134 TO_INT_WITH_PRECISION(105.9463094),
135};
136
137/* Number of cents between entries in the cent_interp table */
138#define CENT_INTERP_INTERVAL 20
139#define CENT_INTERP_NUM ((int)(sizeof(cent_interp)/sizeof(cent_interp[0])))
140
141/* This stores whether the pitch and speed are at their own limits */
142/* or that of the timestretching algorithm */
143static bool at_limit = false;
144
145/*
146 *
147 * The pitchscreen is divided into 3 viewports (each row is a viewport)
148 * Then each viewport is again divided into 3 colums, each showsing some infos
149 * Additionally, on touchscreen, each cell represents a button
150 *
151 * Below a sketch describing what each cell will show (what's drawn on it)
152 * --------------------------
153 * | | | | <-- pitch up in the middle (text and button)
154 * | | | | <-- arrows for mode toggling on the sides for touchscreen
155 * |------------------------|
156 * | | | | <-- semitone/speed up/down on the sides
157 * | | | | <-- reset pitch&speed in the middle
158 * |------------------------|
159 * | | | | <-- pitch down in the middle
160 * | | | | <-- Two "OK" for exit on the sides for touchscreen
161 * |------------------------|
162 *
163 *
164 */
165
166static void speak_pitch_mode(bool enqueue)
167{
168 bool timestretch_mode = global_settings.pitch_mode_timestretch && dsp_timestretch_available();
169 if (timestretch_mode)
170 talk_id(VOICE_PITCH_TIMESTRETCH_MODE, enqueue);
171 if (global_settings.pitch_mode_semitone)
172 talk_id(VOICE_PITCH_SEMITONE_MODE, timestretch_mode ? true : enqueue);
173 else
174 talk_id(VOICE_PITCH_ABSOLUTE_MODE, timestretch_mode ? true : enqueue);
175 return;
176}
177
178/*
179 * Fixes the viewports so they represent the 3 rows, and adds a little margin
180 * on all sides for the icons (which are drawn outside of the grid
181 *
182 * The modified viewports need to be passed to the touchscreen handling function
183 **/
184static void pitchscreen_fix_viewports(struct viewport *parent,
185 struct viewport pitch_viewports[PITCH_ITEM_COUNT])
186{
187 int i, font_height;
188 font_height = font_get(parent->font)->height;
189 for (i = 0; i < PITCH_ITEM_COUNT; i++)
190 {
191 pitch_viewports[i] = *parent;
192 pitch_viewports[i].height = parent->height / PITCH_ITEM_COUNT;
193 pitch_viewports[i].x += ICON_BORDER;
194 pitch_viewports[i].width -= 2*ICON_BORDER;
195 }
196 pitch_viewports[PITCH_TOP].y += ICON_BORDER;
197 pitch_viewports[PITCH_TOP].height -= ICON_BORDER;
198
199 if(pitch_viewports[PITCH_MID].height < font_height * 2)
200 pitch_viewports[PITCH_MID].height = font_height * 2;
201
202 pitch_viewports[PITCH_MID].y = pitch_viewports[PITCH_TOP].y
203 + pitch_viewports[PITCH_TOP].height;
204
205 pitch_viewports[PITCH_BOTTOM].y = pitch_viewports[PITCH_MID].y
206 + pitch_viewports[PITCH_MID].height;
207
208 pitch_viewports[PITCH_BOTTOM].height -= ICON_BORDER;
209}
210
211/* must be called before pitchscreen_draw, or within
212 * since it neither clears nor updates the display */
213static void pitchscreen_draw_icons(struct screen *display,
214 struct viewport *parent)
215{
216 display->set_viewport(parent);
217 display->mono_bitmap(bitmap_icons_7x8[Icon_UpArrow],
218 parent->width/2 - 3,
219 2, 7, 8);
220 display->mono_bitmap(bitmap_icons_7x8[Icon_DownArrow],
221 parent->width /2 - 3,
222 parent->height - 10, 7, 8);
223 display->mono_bitmap(bitmap_icons_7x8[Icon_FastForward],
224 parent->width - 10,
225 parent->height /2 - 4, 7, 8);
226 display->mono_bitmap(bitmap_icons_7x8[Icon_FastBackward],
227 2,
228 parent->height /2 - 4, 7, 8);
229 display->update_viewport();
230}
231
232static void pitchscreen_draw(struct screen *display, int max_lines,
233 struct viewport pitch_viewports[PITCH_ITEM_COUNT],
234 int32_t pitch, int32_t semitone
235 ,int32_t speed
236 )
237{
238 const char* ptr;
239 char buf[32];
240 int w, h;
241 bool show_lang_pitch;
242 struct viewport *last_vp = NULL;
243
244 /* "Pitch up/Pitch down" - hide for a small screen,
245 * the text is drawn centered automatically
246 *
247 * note: this assumes 5 lines always fit on a touchscreen (should be
248 * reasonable) */
249 if (max_lines >= 5)
250 {
251 int w, h;
252 struct viewport *vp = &pitch_viewports[PITCH_TOP];
253 last_vp = display->set_viewport(vp);
254 display->clear_viewport();
255#ifdef HAVE_TOUCHSCREEN
256 /* two arrows in the top row, left and right column */
257 char *arrows[] = { "<", ">"};
258 display->getstringsize(arrows[0], &w, &h);
259 display->putsxy(0, vp->height/2 - h/2, arrows[0]);
260 display->putsxy(vp->width - w, vp->height/2 - h/2, arrows[1]);
261#endif
262 /* UP: Pitch Up */
263 if (global_settings.pitch_mode_semitone)
264 ptr = str(LANG_PITCH_UP_SEMITONE);
265 else
266 ptr = str(LANG_PITCH_UP);
267
268 display->getstringsize(ptr, &w, NULL);
269 /* draw text */
270 display->putsxy(vp->width/2 - w/2, 0, ptr);
271 display->update_viewport();
272
273 /* DOWN: Pitch Down */
274 vp = &pitch_viewports[PITCH_BOTTOM];
275 display->set_viewport(vp);
276 display->clear_viewport();
277
278#ifdef HAVE_TOUCHSCREEN
279 ptr = str(LANG_KBD_OK);
280 display->getstringsize(ptr, &w, &h);
281 /* one OK in the middle first column of the vp (at half height) */
282 display->putsxy(vp->width/6 - w/2, vp->height/2 - h/2, ptr);
283 /* one OK in the middle of the last column of the vp (at half height) */
284 display->putsxy(5*vp->width/6 - w/2, vp->height/2 - h/2, ptr);
285#endif
286 if (global_settings.pitch_mode_semitone)
287 ptr = str(LANG_PITCH_DOWN_SEMITONE);
288 else
289 ptr = str(LANG_PITCH_DOWN);
290 display->getstringsize(ptr, &w, &h);
291 /* draw text */
292 display->putsxy(vp->width/2 - w/2, vp->height - h, ptr);
293 display->update_viewport();
294 }
295
296 /* Middle section */
297 display->set_viewport(&pitch_viewports[PITCH_MID]);
298 display->clear_viewport();
299 int width_used = 0;
300
301 /* Middle section upper line - hide for a small screen */
302 if ((show_lang_pitch = (max_lines >= 3)))
303 {
304 if(global_settings.pitch_mode_timestretch)
305 {
306 /* Pitch:XXX.X% */
307 if(global_settings.pitch_mode_semitone)
308 {
309 snprintf(buf, sizeof(buf), "%s: %s%d.%02d", str(LANG_PITCH),
310 semitone >= 0 ? "+" : "-",
311 abs(semitone / PITCH_SPEED_PRECISION),
312 abs((semitone % PITCH_SPEED_PRECISION) /
313 (PITCH_SPEED_PRECISION / 100))
314 );
315 }
316 else
317 {
318 snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_PITCH),
319 pitch / PITCH_SPEED_PRECISION,
320 (pitch % PITCH_SPEED_PRECISION) /
321 (PITCH_SPEED_PRECISION / 10));
322 }
323 }
324 else
325 {
326 /* Rate */
327 snprintf(buf, sizeof(buf), "%s:", str(LANG_PLAYBACK_RATE));
328 }
329 display->getstringsize(buf, &w, &h);
330 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
331 (pitch_viewports[PITCH_MID].height / 2) - h, buf);
332 if (w > width_used)
333 width_used = w;
334 }
335
336 /* Middle section lower line */
337 /* "Speed:XXX%" */
338 if(global_settings.pitch_mode_timestretch)
339 {
340 snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", str(LANG_SPEED),
341 speed / PITCH_SPEED_PRECISION,
342 (speed % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
343 }
344 else
345 {
346 if(global_settings.pitch_mode_semitone)
347 {
348 snprintf(buf, sizeof(buf), "%s%d.%02d",
349 semitone >= 0 ? "+" : "-",
350 abs(semitone / PITCH_SPEED_PRECISION),
351 abs((semitone % PITCH_SPEED_PRECISION) /
352 (PITCH_SPEED_PRECISION / 100))
353 );
354 }
355 else
356 {
357 snprintf(buf, sizeof(buf), "%ld.%ld%%",
358 pitch / PITCH_SPEED_PRECISION,
359 (pitch % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
360 }
361 }
362
363 display->getstringsize(buf, &w, &h);
364 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
365 show_lang_pitch ? (pitch_viewports[PITCH_MID].height / 2) :
366 (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
367 buf);
368 if (w > width_used)
369 width_used = w;
370
371 /* "limit" and "timestretch" labels */
372 if (max_lines >= 7)
373 {
374 if(at_limit)
375 {
376 const char * const p = str(LANG_STRETCH_LIMIT);
377 display->getstringsize(p, &w, &h);
378 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
379 (pitch_viewports[PITCH_MID].height / 2) + h, p);
380 if (w > width_used)
381 width_used = w;
382 }
383 }
384
385 /* Middle section left/right labels */
386 const char *leftlabel = "-2%";
387 const char *rightlabel = "+2%";
388 if (global_settings.pitch_mode_timestretch)
389 {
390 leftlabel = "<<";
391 rightlabel = ">>";
392 }
393
394 /* Only display if they fit */
395 display->getstringsize(leftlabel, &w, &h);
396 width_used += w;
397 display->getstringsize(rightlabel, &w, &h);
398 width_used += w;
399
400 if (width_used <= pitch_viewports[PITCH_MID].width)
401 {
402 display->putsxy(0, (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
403 leftlabel);
404 display->putsxy((pitch_viewports[PITCH_MID].width - w),
405 (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
406 rightlabel);
407 }
408 display->update_viewport();
409 display->set_viewport(last_vp);
410}
411
412static int32_t pitch_increase(int32_t pitch, int32_t pitch_delta, bool allow_cutoff
413 /* need this to maintain correct pitch/speed caps */
414 , int32_t speed
415 )
416{
417 int32_t new_pitch;
418 int32_t new_stretch;
419 at_limit = false;
420
421 if (pitch_delta < 0)
422 {
423 /* for large jumps, snap up to whole numbers */
424 if(allow_cutoff && pitch_delta <= -PITCH_SPEED_PRECISION &&
425 (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
426 {
427 pitch_delta += PITCH_SPEED_PRECISION - ((pitch + pitch_delta) % PITCH_SPEED_PRECISION);
428 }
429
430 new_pitch = pitch + pitch_delta;
431
432 if (new_pitch < PITCH_MIN)
433 {
434 if (!allow_cutoff)
435 {
436 return pitch;
437 }
438 new_pitch = PITCH_MIN;
439 at_limit = true;
440 }
441 }
442 else if (pitch_delta > 0)
443 {
444 /* for large jumps, snap down to whole numbers */
445 if(allow_cutoff && pitch_delta >= PITCH_SPEED_PRECISION &&
446 (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
447 {
448 pitch_delta -= (pitch + pitch_delta) % PITCH_SPEED_PRECISION;
449 }
450
451 new_pitch = pitch + pitch_delta;
452
453 if (new_pitch > PITCH_MAX)
454 {
455 if (!allow_cutoff)
456 return pitch;
457 new_pitch = PITCH_MAX;
458 at_limit = true;
459 }
460 }
461 else
462 {
463 /* pitch_delta == 0 -> no real change */
464 return pitch;
465 }
466 if (dsp_timestretch_available())
467 {
468 /* increase the multiple to increase precision of this calculation */
469 new_stretch = GET_STRETCH(new_pitch, speed);
470 if(new_stretch < STRETCH_MIN)
471 {
472 /* we have to ignore allow_cutoff, because we can't have the */
473 /* stretch go higher than STRETCH_MAX */
474 new_pitch = GET_PITCH(speed, STRETCH_MIN);
475 }
476 else if(new_stretch > STRETCH_MAX)
477 {
478 /* we have to ignore allow_cutoff, because we can't have the */
479 /* stretch go higher than STRETCH_MAX */
480 new_pitch = GET_PITCH(speed, STRETCH_MAX);
481 }
482
483 if(new_stretch >= STRETCH_MAX ||
484 new_stretch <= STRETCH_MIN)
485 {
486 at_limit = true;
487 }
488 }
489
490 sound_set_pitch(new_pitch);
491
492 return new_pitch;
493}
494
495static int32_t get_semitone_from_pitch(int32_t pitch)
496{
497 int semitone = 0;
498 int32_t fractional_index = 0;
499
500 while(semitone < NUM_SEMITONES - 1 &&
501 pitch >= semitone_table[semitone + 1])
502 {
503 semitone++;
504 }
505
506
507 /* now find the fractional part */
508 while(pitch > (cent_interp[fractional_index + 1] *
509 semitone_table[semitone] / PITCH_SPEED_100))
510 {
511 /* Check to make sure fractional_index isn't too big */
512 /* This should never happen. */
513 if(fractional_index >= CENT_INTERP_NUM - 1)
514 {
515 break;
516 }
517 fractional_index++;
518 }
519
520 int32_t semitone_pitch_a = cent_interp[fractional_index] *
521 semitone_table[semitone] /
522 PITCH_SPEED_100;
523 int32_t semitone_pitch_b = cent_interp[fractional_index + 1] *
524 semitone_table[semitone] /
525 PITCH_SPEED_100;
526 /* this will be the integer offset from the cent_interp entry */
527 int32_t semitone_frac_ofs = (pitch - semitone_pitch_a) * CENT_INTERP_INTERVAL /
528 (semitone_pitch_b - semitone_pitch_a);
529 semitone = (semitone + SEMITONE_START) * PITCH_SPEED_PRECISION +
530 fractional_index * CENT_INTERP_INTERVAL +
531 semitone_frac_ofs;
532
533 return semitone;
534}
535
536static int32_t get_pitch_from_semitone(int32_t semitone)
537{
538 int32_t adjusted_semitone = semitone - SEMITONE_START * PITCH_SPEED_PRECISION;
539
540 /* Find the index into the semitone table */
541 int32_t semitone_index = (adjusted_semitone / PITCH_SPEED_PRECISION);
542
543 /* set pitch to the semitone's integer part value */
544 int32_t pitch = semitone_table[semitone_index];
545 /* get the range of the cent modification for future calculation */
546 int32_t pitch_mod_a =
547 cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
548 CENT_INTERP_INTERVAL];
549 int32_t pitch_mod_b =
550 cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
551 CENT_INTERP_INTERVAL + 1];
552 /* figure out the cent mod amount based on the semitone fractional value */
553 int32_t pitch_mod = pitch_mod_a + (pitch_mod_b - pitch_mod_a) *
554 (adjusted_semitone % CENT_INTERP_INTERVAL) / CENT_INTERP_INTERVAL;
555
556 /* modify pitch based on the mod amount we just calculated */
557 return (pitch * pitch_mod + PITCH_SPEED_100 / 2) / PITCH_SPEED_100;
558}
559
560static int32_t pitch_increase_semitone(int32_t pitch,
561 int32_t current_semitone,
562 int32_t semitone_delta
563 , int32_t speed
564 )
565{
566 int32_t new_semitone = current_semitone;
567
568 /* snap to the delta interval */
569 if(current_semitone % semitone_delta != 0)
570 {
571 if(current_semitone > 0 && semitone_delta > 0)
572 new_semitone += semitone_delta;
573 else if(current_semitone < 0 && semitone_delta < 0)
574 new_semitone += semitone_delta;
575
576 new_semitone -= new_semitone % semitone_delta;
577 }
578 else
579 new_semitone += semitone_delta;
580
581 /* clamp the pitch so it doesn't go beyond the pitch limits */
582 if(new_semitone < (SEMITONE_START * PITCH_SPEED_PRECISION))
583 {
584 new_semitone = SEMITONE_START * PITCH_SPEED_PRECISION;
585 at_limit = true;
586 }
587 else if(new_semitone > (SEMITONE_END * PITCH_SPEED_PRECISION))
588 {
589 new_semitone = SEMITONE_END * PITCH_SPEED_PRECISION;
590 at_limit = true;
591 }
592
593 int32_t new_pitch = get_pitch_from_semitone(new_semitone);
594
595 int32_t new_stretch = GET_STRETCH(new_pitch, speed);
596
597 /* clamp the pitch so it doesn't go beyond the stretch limits */
598 if( new_stretch > STRETCH_MAX)
599 {
600 new_pitch = GET_PITCH(speed, STRETCH_MAX);
601 new_semitone = get_semitone_from_pitch(new_pitch);
602 at_limit = true;
603 }
604 else if (new_stretch < STRETCH_MIN)
605 {
606 new_pitch = GET_PITCH(speed, STRETCH_MIN);
607 new_semitone = get_semitone_from_pitch(new_pitch);
608 at_limit = true;
609 }
610
611 pitch_increase(pitch, new_pitch - pitch, false
612 , speed
613 );
614
615 return new_semitone;
616}
617
618#ifdef HAVE_TOUCHSCREEN
619/*
620 * Check for touchscreen presses as per sketch above in this file
621 *
622 * goes through each row of the, checks whether the touchscreen
623 * was pressed in it. Then it looks the columns of each row for specific actions
624 */
625static int pitchscreen_do_touchscreen(struct viewport vps[])
626{
627 short x, y;
628 struct viewport *this_vp = &vps[PITCH_TOP];
629 int ret;
630 static bool wait_for_release = false;
631 ret = action_get_touchscreen_press_in_vp(&x, &y, this_vp);
632
633 /* top row */
634 if (ret > ACTION_UNKNOWN)
635 { /* press on top row, left or right column
636 * only toggle mode if released */
637 int column = this_vp->width / 3;
638 if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
639 return ACTION_PS_TOGGLE_MODE;
640
641
642 else if (x >= column && x <= (2*column))
643 { /* center column pressed */
644 if (ret == BUTTON_REPEAT)
645 return ACTION_PS_INC_BIG;
646 else if (ret & BUTTON_REL)
647 return ACTION_PS_INC_SMALL;
648 }
649 return ACTION_NONE;
650 }
651
652 /* now the center row */
653 this_vp = &vps[PITCH_MID];
654 ret = action_get_touchscreen_press_in_vp(&x, &y, this_vp);
655
656 if (ret > ACTION_UNKNOWN)
657 {
658 int column = this_vp->width / 3;
659
660 if (x < column)
661 { /* left column */
662 if (ret & BUTTON_REL)
663 {
664 wait_for_release = false;
665 return ACTION_PS_NUDGE_LEFTOFF;
666 }
667 else if (ret & BUTTON_REPEAT)
668 return ACTION_PS_SLOWER;
669 if (!wait_for_release)
670 {
671 wait_for_release = true;
672 return ACTION_PS_NUDGE_LEFT;
673 }
674 }
675 else if (x > (2*column))
676 { /* right column */
677 if (ret & BUTTON_REL)
678 {
679 wait_for_release = false;
680 return ACTION_PS_NUDGE_RIGHTOFF;
681 }
682 else if (ret & BUTTON_REPEAT)
683 return ACTION_PS_FASTER;
684 if (!wait_for_release)
685 {
686 wait_for_release = true;
687 return ACTION_PS_NUDGE_RIGHT;
688 }
689 }
690 else
691 /* center column was pressed */
692 return ACTION_PS_RESET;
693 }
694
695 /* now the bottom row */
696 this_vp = &vps[PITCH_BOTTOM];
697 ret = action_get_touchscreen_press_in_vp(&x, &y, this_vp);
698
699 if (ret > ACTION_UNKNOWN)
700 {
701 int column = this_vp->width / 3;
702
703 /* left or right column is exit */
704 if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
705 return ACTION_PS_EXIT;
706 else if (x >= column && x <= (2*column))
707 { /* center column was pressed */
708 if (ret & BUTTON_REPEAT)
709 return ACTION_PS_DEC_BIG;
710 else if (ret & BUTTON_REL)
711 return ACTION_PS_DEC_SMALL;
712 }
713 return ACTION_NONE;
714 }
715 return ACTION_NONE;
716}
717
718#endif
719/*
720 returns:
721 0 on exit
722 1 if USB was connected
723*/
724
725int gui_syncpitchscreen_run(void) 22int gui_syncpitchscreen_run(void)
726{ 23{
727 int button; 24 return (plugin_load(VIEWERS_DIR"/pitch_screen.rock", NULL) == PLUGIN_USB_CONNECTED);
728 int32_t pitch = sound_get_pitch();
729 int32_t semitone;
730
731 int32_t new_pitch;
732 int32_t pitch_delta;
733 bool nudged = false;
734 int i, updated = 4, decimals = 0;
735 bool exit = false;
736 /* should maybe be passed per parameter later, not needed for now */
737 struct viewport parent[NB_SCREENS];
738 struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT];
739 int max_lines[NB_SCREENS];
740
741 push_current_activity(ACTIVITY_PITCHSCREEN);
742
743 int32_t new_speed = 0, new_stretch;
744
745 /* the speed variable holds the apparent speed of the playback */
746 int32_t speed;
747 if (dsp_timestretch_available())
748 {
749 speed = GET_SPEED(pitch, dsp_get_timestretch());
750 }
751 else
752 {
753 speed = pitch;
754 }
755
756 /* Figure out whether to be in timestretch mode */
757 if (global_settings.pitch_mode_timestretch && !dsp_timestretch_available())
758 {
759 global_settings.pitch_mode_timestretch = false;
760 settings_save();
761 }
762
763 /* Count decimals for speaking */
764 for (i = PITCH_SPEED_PRECISION; i >= 10; i /= 10)
765 decimals++;
766
767 /* set the semitone index based on the current pitch */
768 semitone = get_semitone_from_pitch(pitch);
769
770 /* initialize pitchscreen vps */
771 FOR_NB_SCREENS(i)
772 {
773 viewport_set_defaults(&parent[i], i);
774 max_lines[i] = viewport_get_nb_lines(&parent[i]);
775 pitchscreen_fix_viewports(&parent[i], pitch_viewports[i]);
776 screens[i].set_viewport(&parent[i]);
777 screens[i].clear_viewport();
778
779 /* also, draw the icons now, it's only needed once */
780 pitchscreen_draw_icons(&screens[i], &parent[i]);
781 }
782 pcmbuf_set_low_latency(true);
783
784 while (!exit)
785 {
786 FOR_NB_SCREENS(i)
787 pitchscreen_draw(&screens[i], max_lines[i],
788 pitch_viewports[i], pitch, semitone
789 , speed
790 );
791 pitch_delta = 0;
792 new_speed = 0;
793
794 if (global_settings.talk_menu && updated)
795 {
796 talk_shutup();
797 switch (updated)
798 {
799 case 1:
800 if (global_settings.pitch_mode_semitone)
801 talk_value_decimal(semitone, UNIT_SIGNED, decimals, false);
802 else
803 talk_value_decimal(pitch, UNIT_PERCENT, decimals, false);
804 break;
805 case 2:
806 talk_value_decimal(speed, UNIT_PERCENT, decimals, false);
807 break;
808 case 3:
809 speak_pitch_mode(false);
810 break;
811 case 4:
812 if (global_settings.pitch_mode_timestretch && dsp_timestretch_available())
813 talk_id(LANG_PITCH, false);
814 else
815 talk_id(LANG_PLAYBACK_RATE, false);
816 talk_value_decimal(pitch, UNIT_PERCENT, decimals, true);
817 if (global_settings.pitch_mode_timestretch && dsp_timestretch_available())
818 {
819 talk_id(LANG_SPEED, true);
820 talk_value_decimal(speed, UNIT_PERCENT, decimals, true);
821 }
822 speak_pitch_mode(true);
823 break;
824 default:
825 break;
826 }
827 }
828 updated = 0;
829
830 button = get_action(CONTEXT_PITCHSCREEN, HZ);
831
832#ifdef HAVE_TOUCHSCREEN
833 if (button == ACTION_TOUCHSCREEN)
834 {
835 FOR_NB_SCREENS(i)
836 button = pitchscreen_do_touchscreen(pitch_viewports[i]);
837 }
838#endif
839 switch (button)
840 {
841 case ACTION_PS_INC_SMALL:
842 if(global_settings.pitch_mode_semitone)
843 pitch_delta = SEMITONE_SMALL_DELTA;
844 else
845 pitch_delta = PITCH_SMALL_DELTA;
846 updated = 1;
847 break;
848
849 case ACTION_PS_INC_BIG:
850 if(global_settings.pitch_mode_semitone)
851 pitch_delta = SEMITONE_BIG_DELTA;
852 else
853 pitch_delta = PITCH_BIG_DELTA;
854 updated = 1;
855 break;
856
857 case ACTION_PS_DEC_SMALL:
858 if(global_settings.pitch_mode_semitone)
859 pitch_delta = -SEMITONE_SMALL_DELTA;
860 else
861 pitch_delta = -PITCH_SMALL_DELTA;
862 updated = 1;
863 break;
864
865 case ACTION_PS_DEC_BIG:
866 if(global_settings.pitch_mode_semitone)
867 pitch_delta = -SEMITONE_BIG_DELTA;
868 else
869 pitch_delta = -PITCH_BIG_DELTA;
870 updated = 1;
871 break;
872
873 case ACTION_PS_NUDGE_RIGHT:
874 if (!global_settings.pitch_mode_timestretch)
875 {
876 new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
877 , speed
878 );
879 nudged = (new_pitch != pitch);
880 pitch = new_pitch;
881 semitone = get_semitone_from_pitch(pitch);
882 speed = pitch;
883 updated = nudged ? 1 : 0;
884 break;
885 }
886 else
887 {
888 new_speed = speed + SPEED_SMALL_DELTA;
889 at_limit = false;
890 updated = 2;
891 }
892 break;
893
894 case ACTION_PS_FASTER:
895 if (global_settings.pitch_mode_timestretch)
896 {
897 new_speed = speed + SPEED_BIG_DELTA;
898 /* snap to whole numbers */
899 if(new_speed % PITCH_SPEED_PRECISION != 0)
900 new_speed -= new_speed % PITCH_SPEED_PRECISION;
901 at_limit = false;
902 updated = 2;
903 }
904 break;
905
906 case ACTION_PS_NUDGE_RIGHTOFF:
907 if (nudged)
908 {
909 pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
910 , speed
911 );
912 speed = pitch;
913 semitone = get_semitone_from_pitch(pitch);
914 nudged = false;
915 updated = 1;
916 }
917 break;
918
919 case ACTION_PS_NUDGE_LEFT:
920 if (!global_settings.pitch_mode_timestretch)
921 {
922 new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
923 , speed
924 );
925 nudged = (new_pitch != pitch);
926 pitch = new_pitch;
927 semitone = get_semitone_from_pitch(pitch);
928 speed = pitch;
929 updated = nudged ? 1 : 0;
930 break;
931 }
932 else
933 {
934 new_speed = speed - SPEED_SMALL_DELTA;
935 at_limit = false;
936 updated = 2;
937 }
938 break;
939
940 case ACTION_PS_SLOWER:
941 if (global_settings.pitch_mode_timestretch)
942 {
943 new_speed = speed - SPEED_BIG_DELTA;
944 /* snap to whole numbers */
945 if(new_speed % PITCH_SPEED_PRECISION != 0)
946 new_speed += PITCH_SPEED_PRECISION - speed % PITCH_SPEED_PRECISION;
947 at_limit = false;
948 updated = 2;
949 }
950 break;
951
952 case ACTION_PS_NUDGE_LEFTOFF:
953 if (nudged)
954 {
955 pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
956 , speed
957 );
958 speed = pitch;
959 semitone = get_semitone_from_pitch(pitch);
960 nudged = false;
961 updated = 1;
962 }
963 break;
964
965 case ACTION_PS_RESET:
966 pitch = PITCH_SPEED_100;
967 sound_set_pitch(pitch);
968 speed = PITCH_SPEED_100;
969 if (dsp_timestretch_available())
970 {
971 dsp_set_timestretch(PITCH_SPEED_100);
972 at_limit = false;
973 }
974 semitone = get_semitone_from_pitch(pitch);
975 updated = 4;
976 break;
977
978 case ACTION_PS_TOGGLE_MODE:
979 global_settings.pitch_mode_semitone = !global_settings.pitch_mode_semitone;
980
981 if (dsp_timestretch_available() && !global_settings.pitch_mode_semitone)
982 {
983 global_settings.pitch_mode_timestretch = !global_settings.pitch_mode_timestretch;
984 if(!global_settings.pitch_mode_timestretch)
985 {
986 /* no longer in timestretch mode. Reset speed */
987 speed = pitch;
988 dsp_set_timestretch(PITCH_SPEED_100);
989 }
990 }
991 settings_save();
992 updated = 3;
993 break;
994
995 case ACTION_PS_EXIT:
996 exit = true;
997 break;
998
999 default:
1000 if (default_event_handler(button) == SYS_USB_CONNECTED)
1001 return 1;
1002 break;
1003 }
1004 if (pitch_delta)
1005 {
1006 if (global_settings.pitch_mode_semitone)
1007 {
1008 semitone = pitch_increase_semitone(pitch, semitone, pitch_delta
1009 , speed
1010 );
1011 pitch = get_pitch_from_semitone(semitone);
1012 }
1013 else
1014 {
1015 pitch = pitch_increase(pitch, pitch_delta, true
1016 , speed
1017 );
1018 semitone = get_semitone_from_pitch(pitch);
1019 }
1020 if (global_settings.pitch_mode_timestretch)
1021 {
1022 /* do this to make sure we properly obey the stretch limits */
1023 new_speed = speed;
1024 }
1025 else
1026 {
1027 speed = pitch;
1028 }
1029 }
1030
1031 if(new_speed)
1032 {
1033 new_stretch = GET_STRETCH(pitch, new_speed);
1034
1035 /* limit the amount of stretch */
1036 if(new_stretch > STRETCH_MAX)
1037 {
1038 new_stretch = STRETCH_MAX;
1039 new_speed = GET_SPEED(pitch, new_stretch);
1040 }
1041 else if(new_stretch < STRETCH_MIN)
1042 {
1043 new_stretch = STRETCH_MIN;
1044 new_speed = GET_SPEED(pitch, new_stretch);
1045 }
1046
1047 new_stretch = GET_STRETCH(pitch, new_speed);
1048 if(new_stretch >= STRETCH_MAX ||
1049 new_stretch <= STRETCH_MIN)
1050 {
1051 at_limit = true;
1052 }
1053
1054 /* set the amount of stretch */
1055 dsp_set_timestretch(new_stretch);
1056
1057 /* update the speed variable with the new speed */
1058 speed = new_speed;
1059
1060 /* Reset new_speed so we only call dsp_set_timestretch */
1061 /* when needed */
1062 new_speed = 0;
1063 }
1064 }
1065
1066 pcmbuf_set_low_latency(false);
1067 pop_current_activity();
1068
1069 /* Clean up */
1070 FOR_NB_SCREENS(i)
1071 {
1072 screens[i].set_viewport(NULL);
1073 }
1074
1075 return 0;
1076} 25}
diff --git a/apps/plugin.c b/apps/plugin.c
index d1f0348cc3..c9c3d047d2 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -352,6 +352,7 @@ static const struct plugin_api rockbox_api = {
352 get_action, 352 get_action,
353#ifdef HAVE_TOUCHSCREEN 353#ifdef HAVE_TOUCHSCREEN
354 action_get_touchscreen_press, 354 action_get_touchscreen_press,
355 action_get_touchscreen_press_in_vp,
355#endif 356#endif
356 action_userabort, 357 action_userabort,
357 358
@@ -413,6 +414,7 @@ static const struct plugin_api rockbox_api = {
413 FS_PREFIX(file_exists), 414 FS_PREFIX(file_exists),
414 strip_extension, 415 strip_extension,
415 crc_32, 416 crc_32,
417 crc_32r,
416 filetype_get_attr, 418 filetype_get_attr,
417 419
418 /* dir */ 420 /* dir */
@@ -593,6 +595,7 @@ static const struct plugin_api rockbox_api = {
593 sound_enum_hw_eq_band_setting, 595 sound_enum_hw_eq_band_setting,
594#endif 596#endif
595#if defined (HAVE_PITCHCONTROL) 597#if defined (HAVE_PITCHCONTROL)
598 sound_get_pitch,
596 sound_set_pitch, 599 sound_set_pitch,
597#endif 600#endif
598 &audio_master_sampr_list[0], 601 &audio_master_sampr_list[0],
@@ -622,7 +625,10 @@ static const struct plugin_api rockbox_api = {
622 dsp_eq_enable, 625 dsp_eq_enable,
623 dsp_dither_enable, 626 dsp_dither_enable,
624#ifdef HAVE_PITCHCONTROL 627#ifdef HAVE_PITCHCONTROL
628 dsp_get_timestretch,
625 dsp_set_timestretch, 629 dsp_set_timestretch,
630 dsp_timestretch_enable,
631 dsp_timestretch_available,
626#endif 632#endif
627 dsp_configure, 633 dsp_configure,
628 dsp_get_config, 634 dsp_get_config,
@@ -641,6 +647,7 @@ static const struct plugin_api rockbox_api = {
641 mixer_get_frequency, 647 mixer_get_frequency,
642 648
643 pcmbuf_fade, 649 pcmbuf_fade,
650 pcmbuf_set_low_latency,
644 system_sound_play, 651 system_sound_play,
645 keyclick_click, 652 keyclick_click,
646 653
@@ -692,6 +699,9 @@ static const struct plugin_api rockbox_api = {
692 audio_current_track, 699 audio_current_track,
693 audio_flush_and_reload_tracks, 700 audio_flush_and_reload_tracks,
694 audio_get_file_pos, 701 audio_get_file_pos,
702#ifdef PLUGIN_USE_IRAM
703 audio_hard_stop,
704#endif
695 705
696 /* menu */ 706 /* menu */
697 root_menu_get_options, 707 root_menu_get_options,
@@ -735,6 +745,7 @@ static const struct plugin_api rockbox_api = {
735#if (CONFIG_PLATFORM & PLATFORM_NATIVE) 745#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
736 __errno, 746 __errno,
737#endif 747#endif
748 led,
738 srand, 749 srand,
739 rand, 750 rand,
740 (void *)qsort, 751 (void *)qsort,
@@ -780,7 +791,6 @@ static const struct plugin_api rockbox_api = {
780 detect_flashed_ramimage, 791 detect_flashed_ramimage,
781 detect_flashed_romimage, 792 detect_flashed_romimage,
782#endif 793#endif
783 led,
784 794
785 /*plugin*/ 795 /*plugin*/
786 plugin_open, 796 plugin_open,
@@ -789,11 +799,6 @@ static const struct plugin_api rockbox_api = {
789 plugin_release_audio_buffer, /* defined in plugin.c */ 799 plugin_release_audio_buffer, /* defined in plugin.c */
790 plugin_tsr, /* defined in plugin.c */ 800 plugin_tsr, /* defined in plugin.c */
791 plugin_get_current_filename, 801 plugin_get_current_filename,
792#ifdef PLUGIN_USE_IRAM
793 audio_hard_stop,
794#endif
795 crc_32r,
796
797 /* new stuff at the end, sort into place next time 802 /* new stuff at the end, sort into place next time
798 the API gets incompatible */ 803 the API gets incompatible */
799 804
diff --git a/apps/plugin.h b/apps/plugin.h
index bd95331143..6ca0488e06 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -155,12 +155,12 @@ int plugin_open(const char *plugin, const char *parameter);
155#define PLUGIN_MAGIC 0x526F634B /* RocK */ 155#define PLUGIN_MAGIC 0x526F634B /* RocK */
156 156
157/* increase this every time the api struct changes */ 157/* increase this every time the api struct changes */
158#define PLUGIN_API_VERSION 244 158#define PLUGIN_API_VERSION 245
159 159
160/* update this to latest version if a change to the api struct breaks 160/* update this to latest version if a change to the api struct breaks
161 backwards compatibility (and please take the opportunity to sort in any 161 backwards compatibility (and please take the opportunity to sort in any
162 new function which are "waiting" at the end of the function table) */ 162 new function which are "waiting" at the end of the function table) */
163#define PLUGIN_MIN_API_VERSION 244 163#define PLUGIN_MIN_API_VERSION 245
164 164
165/* 239 Marks the removal of ARCHOS HWCODEC and CHARCELL */ 165/* 239 Marks the removal of ARCHOS HWCODEC and CHARCELL */
166 166
@@ -400,6 +400,7 @@ struct plugin_api {
400 int (*get_action)(int context, int timeout); 400 int (*get_action)(int context, int timeout);
401#ifdef HAVE_TOUCHSCREEN 401#ifdef HAVE_TOUCHSCREEN
402 int (*action_get_touchscreen_press)(short *x, short *y); 402 int (*action_get_touchscreen_press)(short *x, short *y);
403 int (*action_get_touchscreen_press_in_vp)(short *x1, short *y1, struct viewport *vp);
403#endif 404#endif
404 bool (*action_userabort)(int timeout); 405 bool (*action_userabort)(int timeout);
405 406
@@ -462,6 +463,7 @@ struct plugin_api {
462 bool (*file_exists)(const char *path); 463 bool (*file_exists)(const char *path);
463 char* (*strip_extension)(char* buffer, int buffer_size, const char *filename); 464 char* (*strip_extension)(char* buffer, int buffer_size, const char *filename);
464 uint32_t (*crc_32)(const void *src, uint32_t len, uint32_t crc32); 465 uint32_t (*crc_32)(const void *src, uint32_t len, uint32_t crc32);
466 uint32_t (*crc_32r)(const void *src, uint32_t len, uint32_t crc32);
465 467
466 int (*filetype_get_attr)(const char* file); 468 int (*filetype_get_attr)(const char* file);
467 469
@@ -667,6 +669,7 @@ struct plugin_api {
667 unsigned int band_setting); 669 unsigned int band_setting);
668#endif /* AUDIOHW_HAVE_EQ */ 670#endif /* AUDIOHW_HAVE_EQ */
669#if defined (HAVE_PITCHCONTROL) 671#if defined (HAVE_PITCHCONTROL)
672 int32_t (*sound_get_pitch)(void);
670 void (*sound_set_pitch)(int32_t pitch); 673 void (*sound_set_pitch)(int32_t pitch);
671#endif 674#endif
672 const unsigned long *audio_master_sampr_list; 675 const unsigned long *audio_master_sampr_list;
@@ -701,7 +704,10 @@ struct plugin_api {
701 void (*dsp_eq_enable)(bool enable); 704 void (*dsp_eq_enable)(bool enable);
702 void (*dsp_dither_enable)(bool enable); 705 void (*dsp_dither_enable)(bool enable);
703#ifdef HAVE_PITCHCONTROL 706#ifdef HAVE_PITCHCONTROL
707 int32_t (*dsp_get_timestretch)(void);
704 void (*dsp_set_timestretch)(int32_t percent); 708 void (*dsp_set_timestretch)(int32_t percent);
709 void (*dsp_timestretch_enable)(bool enabled);
710 bool (*dsp_timestretch_available)(void);
705#endif 711#endif
706 intptr_t (*dsp_configure)(struct dsp_config *dsp, 712 intptr_t (*dsp_configure)(struct dsp_config *dsp,
707 unsigned int setting, intptr_t value); 713 unsigned int setting, intptr_t value);
@@ -727,6 +733,7 @@ struct plugin_api {
727 void (*mixer_set_frequency)(unsigned int samplerate); 733 void (*mixer_set_frequency)(unsigned int samplerate);
728 unsigned int (*mixer_get_frequency)(void); 734 unsigned int (*mixer_get_frequency)(void);
729 void (*pcmbuf_fade)(bool fade, bool in); 735 void (*pcmbuf_fade)(bool fade, bool in);
736 void (*pcmbuf_set_low_latency)(bool state);
730 void (*system_sound_play)(enum system_sound sound); 737 void (*system_sound_play)(enum system_sound sound);
731 void (*keyclick_click)(bool rawbutton, int action); 738 void (*keyclick_click)(bool rawbutton, int action);
732 739
@@ -793,6 +800,9 @@ struct plugin_api {
793 struct mp3entry* (*audio_current_track)(void); 800 struct mp3entry* (*audio_current_track)(void);
794 void (*audio_flush_and_reload_tracks)(void); 801 void (*audio_flush_and_reload_tracks)(void);
795 int (*audio_get_file_pos)(void); 802 int (*audio_get_file_pos)(void);
803#ifdef PLUGIN_USE_IRAM
804 void (*audio_hard_stop)(void);
805#endif
796 806
797 /* menu */ 807 /* menu */
798 struct menu_table *(*root_menu_get_options)(int *nb_options); 808 struct menu_table *(*root_menu_get_options)(int *nb_options);
@@ -853,6 +863,7 @@ struct plugin_api {
853#if (CONFIG_PLATFORM & PLATFORM_NATIVE) 863#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
854 int * (*__errno)(void); 864 int * (*__errno)(void);
855#endif 865#endif
866 void (*led)(bool on);
856 void (*srand)(unsigned int seed); 867 void (*srand)(unsigned int seed);
857 int (*rand)(void); 868 int (*rand)(void);
858 void (*qsort)(void *base, size_t nmemb, size_t size, 869 void (*qsort)(void *base, size_t nmemb, size_t size,
@@ -907,9 +918,6 @@ struct plugin_api {
907 bool (*detect_flashed_ramimage)(void); 918 bool (*detect_flashed_ramimage)(void);
908 bool (*detect_flashed_romimage)(void); 919 bool (*detect_flashed_romimage)(void);
909#endif 920#endif
910
911 void (*led)(bool on);
912
913 /*plugin*/ 921 /*plugin*/
914 int (*plugin_open)(const char *path, const char *parameter); 922 int (*plugin_open)(const char *path, const char *parameter);
915 void* (*plugin_get_buffer)(size_t *buffer_size); 923 void* (*plugin_get_buffer)(size_t *buffer_size);
@@ -917,11 +925,6 @@ struct plugin_api {
917 void (*plugin_release_audio_buffer)(void); 925 void (*plugin_release_audio_buffer)(void);
918 void (*plugin_tsr)(bool (*exit_callback)(bool reenter)); 926 void (*plugin_tsr)(bool (*exit_callback)(bool reenter));
919 char* (*plugin_get_current_filename)(void); 927 char* (*plugin_get_current_filename)(void);
920#ifdef PLUGIN_USE_IRAM
921 void (*audio_hard_stop)(void);
922#endif
923 uint32_t (*crc_32r)(const void *src, uint32_t len, uint32_t crc32);
924
925 /* new stuff at the end, sort into place next time 928 /* new stuff at the end, sort into place next time
926 the API gets incompatible */ 929 the API gets incompatible */
927 930
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index d3093689f9..471a32b9e1 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -76,6 +76,7 @@ pegbox,games
76periodic_table,apps 76periodic_table,apps
77pictureflow,demos 77pictureflow,demos
78pitch_detector,apps 78pitch_detector,apps
79pitch_screen,viewers
79pixel-painter,games 80pixel-painter,games
80plasma,demos 81plasma,demos
81png,viewers 82png,viewers
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 910ffe4161..ec13c17dc9 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -48,6 +48,10 @@ lamp.c
48pitch_detector.c 48pitch_detector.c
49#endif 49#endif
50 50
51#ifdef HAVE_PITCHCONTROL
52pitch_screen.c
53#endif
54
51mp3_encoder.c 55mp3_encoder.c
52wav2wv.c 56wav2wv.c
53 57
diff --git a/apps/plugins/lib/arg_helper.c b/apps/plugins/lib/arg_helper.c
index d402300900..3ea5ba714d 100644
--- a/apps/plugins/lib/arg_helper.c
+++ b/apps/plugins/lib/arg_helper.c
@@ -28,7 +28,9 @@
28 28
29#define SWCHAR '-' 29#define SWCHAR '-'
30#define DECSEPCHAR '.' 30#define DECSEPCHAR '.'
31 31#ifdef PLUGIN
32 #define strchr rb->strchr
33#endif
32int string_parse(const char **parameter, char* buf, size_t buf_sz) 34int string_parse(const char **parameter, char* buf, size_t buf_sz)
33{ 35{
34/* fills buf with a string upto buf_sz, null terminates the buffer 36/* fills buf with a string upto buf_sz, null terminates the buffer
diff --git a/apps/plugins/lib/arg_helper.h b/apps/plugins/lib/arg_helper.h
index c7b14f7f7a..2cf94ba1dd 100644
--- a/apps/plugins/lib/arg_helper.h
+++ b/apps/plugins/lib/arg_helper.h
@@ -26,7 +26,7 @@
26#define ARGPARSE_MAX_FRAC_DIGITS 9 /* Uses 30 bits max (0.999999999) */ 26#define ARGPARSE_MAX_FRAC_DIGITS 9 /* Uses 30 bits max (0.999999999) */
27 27
28#define ARGP_EXP(a, b) (a ##E## b) 28#define ARGP_EXP(a, b) (a ##E## b)
29#define ARGP_FRAC_DEC_MULTIPLIER(n) AP_EXP(1,n) /*1x10^n*/ 29#define ARGP_FRAC_DEC_MULTIPLIER(n) ARGP_EXP(1,n) /*1x10^n*/
30#define ARGPARSE_FRAC_DEC_MULTIPLIER \ 30#define ARGPARSE_FRAC_DEC_MULTIPLIER \
31 (long)ARGP_FRAC_DEC_MULTIPLIER(ARGPARSE_MAX_FRAC_DIGITS) 31 (long)ARGP_FRAC_DEC_MULTIPLIER(ARGPARSE_MAX_FRAC_DIGITS)
32 32
diff --git a/apps/plugins/pitch_screen.c b/apps/plugins/pitch_screen.c
new file mode 100644
index 0000000000..e24e0240a2
--- /dev/null
+++ b/apps/plugins/pitch_screen.c
@@ -0,0 +1,1279 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Björn Stenberg
11 *
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22#include "plugin.h"
23#include "lib/icon_helper.h"
24#include "lib/arg_helper.h"
25
26#define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */
27 /* on both sides when drawing */
28
29#define PITCH_MAX (200 * PITCH_SPEED_PRECISION)
30#define PITCH_MIN (50 * PITCH_SPEED_PRECISION)
31#define PITCH_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
32#define PITCH_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
33#define PITCH_NUDGE_DELTA (2 * PITCH_SPEED_PRECISION) /* 2% */
34
35#define SPEED_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
36#define SPEED_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
37
38#define SEMITONE_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* 10 cents */
39#define SEMITONE_BIG_DELTA PITCH_SPEED_PRECISION /* 1 semitone */
40
41#define PVAR_VERBOSE 0x01
42#define PVAR_GUI 0x02
43struct pvars
44{
45 int32_t speed;
46 int32_t pitch;
47 int32_t stretch;
48 int32_t flags;
49};
50static struct pvars pitch_vars;
51
52enum
53{
54 PITCH_TOP = 0,
55 PITCH_MID,
56 PITCH_BOTTOM,
57 PITCH_ITEM_COUNT,
58};
59
60/* This is a table of semitone percentage values of the appropriate
61 precision (based on PITCH_SPEED_PRECISION). Note that these are
62 all constant expressions, which will be evaluated at compile time,
63 so no need to worry about how complex the expressions look.
64 That's just to get the precision right.
65
66 I calculated these values, starting from 50, as
67
68 x(n) = 50 * 2^(n/12)
69
70 All that math in each entry simply converts the float constant
71 to an integer equal to PITCH_SPEED_PRECISION times the float value,
72 with as little precision loss as possible (i.e. correctly rounding
73 the last digit).
74*/
75#define TO_INT_WITH_PRECISION(x) \
76 ( (unsigned short)(((x) * PITCH_SPEED_PRECISION * 10 + 5) / 10) )
77
78static const unsigned short semitone_table[] =
79{
80 TO_INT_WITH_PRECISION(50.00000000), /* Octave lower */
81 TO_INT_WITH_PRECISION(52.97315472),
82 TO_INT_WITH_PRECISION(56.12310242),
83 TO_INT_WITH_PRECISION(59.46035575),
84 TO_INT_WITH_PRECISION(62.99605249),
85 TO_INT_WITH_PRECISION(66.74199271),
86 TO_INT_WITH_PRECISION(70.71067812),
87 TO_INT_WITH_PRECISION(74.91535384),
88 TO_INT_WITH_PRECISION(79.37005260),
89 TO_INT_WITH_PRECISION(84.08964153),
90 TO_INT_WITH_PRECISION(89.08987181),
91 TO_INT_WITH_PRECISION(94.38743127),
92 TO_INT_WITH_PRECISION(100.0000000), /* Normal sound */
93 TO_INT_WITH_PRECISION(105.9463094),
94 TO_INT_WITH_PRECISION(112.2462048),
95 TO_INT_WITH_PRECISION(118.9207115),
96 TO_INT_WITH_PRECISION(125.9921049),
97 TO_INT_WITH_PRECISION(133.4839854),
98 TO_INT_WITH_PRECISION(141.4213562),
99 TO_INT_WITH_PRECISION(149.8307077),
100 TO_INT_WITH_PRECISION(158.7401052),
101 TO_INT_WITH_PRECISION(168.1792831),
102 TO_INT_WITH_PRECISION(178.1797436),
103 TO_INT_WITH_PRECISION(188.7748625),
104 TO_INT_WITH_PRECISION(200.0000000) /* Octave higher */
105};
106
107#define NUM_SEMITONES ((int)(sizeof(semitone_table)/sizeof(semitone_table[0])))
108#define SEMITONE_END (NUM_SEMITONES/2)
109#define SEMITONE_START (-SEMITONE_END)
110
111/* A table of values for approximating the cent curve with
112 linear interpolation. Multipy the next lowest semitone
113 by this much to find the corresponding cent percentage.
114
115 These values were calculated as
116 x(n) = 100 * 2^(n * 20/1200)
117*/
118
119static const unsigned short cent_interp[] =
120{
121 TO_INT_WITH_PRECISION(100.0000000),
122 TO_INT_WITH_PRECISION(101.1619440),
123 TO_INT_WITH_PRECISION(102.3373892),
124 TO_INT_WITH_PRECISION(103.5264924),
125 TO_INT_WITH_PRECISION(104.7294123),
126 /* this one's the next semitone but we have it here for convenience */
127 TO_INT_WITH_PRECISION(105.9463094),
128};
129
130int viewport_get_nb_lines(const struct viewport *vp)
131{
132 return vp->height/rb->font_get(vp->font)->height;
133}
134#if 0 /* replaced with cbmp_get_icon(CBMP_Mono_7x8, Icon_ABCD, &w, &h) */
135enum icons_7x8 {
136 Icon_Plug,
137 Icon_USBPlug,
138 Icon_Mute,
139 Icon_Play,
140 Icon_Stop,
141 Icon_Pause,
142 Icon_FastForward,
143 Icon_FastBackward,
144 Icon_Record,
145 Icon_RecPause,
146 Icon_Radio,
147 Icon_Radio_Mute,
148 Icon_Repeat,
149 Icon_RepeatOne,
150 Icon_Shuffle,
151 Icon_DownArrow,
152 Icon_UpArrow,
153 Icon_RepeatAB,
154 Icon7x8Last
155};
156
157const unsigned char bitmap_icons_7x8[][7] =
158{
159 {0x08,0x1c,0x3e,0x3e,0x3e,0x14,0x14}, /* Power plug */
160 {0x1c,0x14,0x3e,0x2a,0x22,0x1c,0x08}, /* USB plug */
161 {0x01,0x1e,0x1c,0x3e,0x7f,0x20,0x40}, /* Speaker mute */
162 {0x00,0x7f,0x7f,0x3e,0x1c,0x08,0x00}, /* Play */
163 {0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f}, /* Stop */
164 {0x00,0x7f,0x7f,0x00,0x7f,0x7f,0x00}, /* Pause */
165 {0x7f,0x3e,0x1c,0x7f,0x3e,0x1c,0x08}, /* Fast forward */
166 {0x08,0x1c,0x3e,0x7f,0x1c,0x3e,0x7f}, /* Fast backward */
167 {0x1c,0x3e,0x7f,0x7f,0x7f,0x3e,0x1c}, /* Record */
168 {0x1c,0x3e,0x7f,0x00,0x7f,0x3e,0x1c}, /* Record pause */
169 {0x40,0xa0,0xa0,0xa0,0x7f,0x02,0x02}, /* Radio on */
170 {0x42,0xa4,0xa8,0xb0,0x7f,0x22,0x42}, /* Radio mute */
171 {0x44,0x4e,0x5f,0x44,0x44,0x44,0x38}, /* Repeat playmode */
172 {0x44,0x4e,0x5f,0x44,0x38,0x02,0x7f}, /* Repeat-one playmode */
173 {0x3e,0x41,0x51,0x41,0x45,0x41,0x3e}, /* Shuffle playmode (dice) */
174 {0x04,0x0c,0x1c,0x3c,0x1c,0x0c,0x04}, /* Down-arrow */
175 {0x20,0x30,0x38,0x3c,0x38,0x30,0x20}, /* Up-arrow */
176 {0x7f,0x04,0x4e,0x5f,0x44,0x38,0x7f} /* Repeat-AB playmode */
177};
178#endif
179
180/* Number of cents between entries in the cent_interp table */
181#define CENT_INTERP_INTERVAL 20
182#define CENT_INTERP_NUM ((int)(sizeof(cent_interp)/sizeof(cent_interp[0])))
183
184/* This stores whether the pitch and speed are at their own limits */
185/* or that of the timestretching algorithm */
186static bool at_limit = false;
187
188/*
189 *
190 * The pitchscreen is divided into 3 viewports (each row is a viewport)
191 * Then each viewport is again divided into 3 colums, each showsing some infos
192 * Additionally, on touchscreen, each cell represents a button
193 *
194 * Below a sketch describing what each cell will show (what's drawn on it)
195 * --------------------------
196 * | | | | <-- pitch up in the middle (text and button)
197 * | | | | <-- arrows for mode toggling on the sides for touchscreen
198 * |------------------------|
199 * | | | | <-- semitone/speed up/down on the sides
200 * | | | | <-- reset pitch&speed in the middle
201 * |------------------------|
202 * | | | | <-- pitch down in the middle
203 * | | | | <-- Two "OK" for exit on the sides for touchscreen
204 * |------------------------|
205 *
206 *
207 */
208
209static void speak_pitch_mode(bool enqueue)
210{
211 bool timestretch_mode = rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available();
212 if (timestretch_mode)
213 rb->talk_id(VOICE_PITCH_TIMESTRETCH_MODE, enqueue);
214 if (rb->global_settings->pitch_mode_semitone)
215 rb->talk_id(VOICE_PITCH_SEMITONE_MODE, timestretch_mode ? true : enqueue);
216 else
217 rb->talk_id(VOICE_PITCH_ABSOLUTE_MODE, timestretch_mode ? true : enqueue);
218 return;
219}
220
221/*
222 * Fixes the viewports so they represent the 3 rows, and adds a little margin
223 * on all sides for the icons (which are drawn outside of the grid
224 *
225 * The modified viewports need to be passed to the touchscreen handling function
226 **/
227static void pitchscreen_fix_viewports(struct viewport *parent,
228 struct viewport pitch_viewports[PITCH_ITEM_COUNT])
229{
230 int i, font_height;
231 font_height = rb->font_get(parent->font)->height;
232 for (i = 0; i < PITCH_ITEM_COUNT; i++)
233 {
234 pitch_viewports[i] = *parent;
235 pitch_viewports[i].height = parent->height / PITCH_ITEM_COUNT;
236 pitch_viewports[i].x += ICON_BORDER;
237 pitch_viewports[i].width -= 2*ICON_BORDER;
238 }
239 pitch_viewports[PITCH_TOP].y += ICON_BORDER;
240 pitch_viewports[PITCH_TOP].height -= ICON_BORDER;
241
242 if(pitch_viewports[PITCH_MID].height < font_height * 2)
243 pitch_viewports[PITCH_MID].height = font_height * 2;
244
245 pitch_viewports[PITCH_MID].y = pitch_viewports[PITCH_TOP].y
246 + pitch_viewports[PITCH_TOP].height;
247
248 pitch_viewports[PITCH_BOTTOM].y = pitch_viewports[PITCH_MID].y
249 + pitch_viewports[PITCH_MID].height;
250
251 pitch_viewports[PITCH_BOTTOM].height -= ICON_BORDER;
252}
253
254/* must be called before pitchscreen_draw, or within
255 * since it neither clears nor updates the display */
256static void pitchscreen_draw_icons(struct screen *display,
257 struct viewport *parent)
258{
259
260 display->set_viewport(parent);
261 int w, h;
262 const unsigned char* uparrow = cbmp_get_icon(CBMP_Mono_7x8, Icon_UpArrow, &w, &h);
263 if (uparrow)
264 display->mono_bitmap(uparrow, parent->width/2 - 3, 2, w, h);
265
266 const unsigned char* dnarrow = cbmp_get_icon(CBMP_Mono_7x8, Icon_DownArrow, &w, &h);
267 if (dnarrow)
268 display->mono_bitmap(dnarrow, parent->width /2 - 3, parent->height - 10, w, h);
269
270 const unsigned char* fastfwd = cbmp_get_icon(CBMP_Mono_7x8, Icon_FastForward, &w, &h);
271 if (fastfwd)
272 display->mono_bitmap(fastfwd, parent->width - 10, parent->height /2 - 4, 7, 8);
273
274 const unsigned char* fastrew = cbmp_get_icon(CBMP_Mono_7x8, Icon_FastBackward, &w, &h);
275 if (fastrew)
276 display->mono_bitmap(fastrew, 2, parent->height /2 - 4, w, h);
277
278 display->update_viewport();
279
280}
281
282static void pitchscreen_draw(struct screen *display, int max_lines,
283 struct viewport pitch_viewports[PITCH_ITEM_COUNT],
284 int32_t pitch, int32_t semitone
285 ,int32_t speed
286 )
287{
288 const char* ptr;
289 char buf[32];
290 int w, h;
291 bool show_lang_pitch;
292 struct viewport *last_vp = NULL;
293
294 /* "Pitch up/Pitch down" - hide for a small screen,
295 * the text is drawn centered automatically
296 *
297 * note: this assumes 5 lines always fit on a touchscreen (should be
298 * reasonable) */
299 if (max_lines >= 5)
300 {
301 int w, h;
302 struct viewport *vp = &pitch_viewports[PITCH_TOP];
303 last_vp = display->set_viewport(vp);
304 display->clear_viewport();
305#ifdef HAVE_TOUCHSCREEN
306 /* two arrows in the top row, left and right column */
307 char *arrows[] = { "<", ">"};
308 display->getstringsize(arrows[0], &w, &h);
309 display->putsxy(0, vp->height/2 - h/2, arrows[0]);
310 display->putsxy(vp->width - w, vp->height/2 - h/2, arrows[1]);
311#endif
312 /* UP: Pitch Up */
313 if (rb->global_settings->pitch_mode_semitone)
314 ptr = rb->str(LANG_PITCH_UP_SEMITONE);
315 else
316 ptr = rb->str(LANG_PITCH_UP);
317
318 display->getstringsize(ptr, &w, NULL);
319 /* draw text */
320 display->putsxy(vp->width/2 - w/2, 0, ptr);
321 display->update_viewport();
322
323 /* DOWN: Pitch Down */
324 vp = &pitch_viewports[PITCH_BOTTOM];
325 display->set_viewport(vp);
326 display->clear_viewport();
327
328#ifdef HAVE_TOUCHSCREEN
329 ptr = rb->str(LANG_KBD_OK);
330 display->getstringsize(ptr, &w, &h);
331 /* one OK in the middle first column of the vp (at half height) */
332 display->putsxy(vp->width/6 - w/2, vp->height/2 - h/2, ptr);
333 /* one OK in the middle of the last column of the vp (at half height) */
334 display->putsxy(5*vp->width/6 - w/2, vp->height/2 - h/2, ptr);
335#endif
336 if (rb->global_settings->pitch_mode_semitone)
337 ptr = rb->str(LANG_PITCH_DOWN_SEMITONE);
338 else
339 ptr = rb->str(LANG_PITCH_DOWN);
340 display->getstringsize(ptr, &w, &h);
341 /* draw text */
342 display->putsxy(vp->width/2 - w/2, vp->height - h, ptr);
343 display->update_viewport();
344 }
345
346 /* Middle section */
347 display->set_viewport(&pitch_viewports[PITCH_MID]);
348 display->clear_viewport();
349 int width_used = 0;
350
351 /* Middle section upper line - hide for a small screen */
352 if ((show_lang_pitch = (max_lines >= 3)))
353 {
354 if(rb->global_settings->pitch_mode_timestretch)
355 {
356 /* Pitch:XXX.X% */
357 if(rb->global_settings->pitch_mode_semitone)
358 {
359 rb->snprintf(buf, sizeof(buf), "%s: %s%d.%02d", rb->str(LANG_PITCH),
360 semitone >= 0 ? "+" : "-",
361 abs(semitone / PITCH_SPEED_PRECISION),
362 abs((semitone % PITCH_SPEED_PRECISION) /
363 (PITCH_SPEED_PRECISION / 100))
364 );
365 }
366 else
367 {
368 rb->snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", rb->str(LANG_PITCH),
369 pitch / PITCH_SPEED_PRECISION,
370 (pitch % PITCH_SPEED_PRECISION) /
371 (PITCH_SPEED_PRECISION / 10));
372 }
373 }
374 else
375 {
376 /* Rate */
377 rb->snprintf(buf, sizeof(buf), "%s:", rb->str(LANG_PLAYBACK_RATE));
378 }
379 display->getstringsize(buf, &w, &h);
380 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
381 (pitch_viewports[PITCH_MID].height / 2) - h, buf);
382 if (w > width_used)
383 width_used = w;
384 }
385
386 /* Middle section lower line */
387 /* "Speed:XXX%" */
388 if(rb->global_settings->pitch_mode_timestretch)
389 {
390 rb->snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", rb->str(LANG_SPEED),
391 speed / PITCH_SPEED_PRECISION,
392 (speed % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
393 }
394 else
395 {
396 if(rb->global_settings->pitch_mode_semitone)
397 {
398 rb->snprintf(buf, sizeof(buf), "%s%d.%02d",
399 semitone >= 0 ? "+" : "-",
400 abs(semitone / PITCH_SPEED_PRECISION),
401 abs((semitone % PITCH_SPEED_PRECISION) /
402 (PITCH_SPEED_PRECISION / 100))
403 );
404 }
405 else
406 {
407 rb->snprintf(buf, sizeof(buf), "%ld.%ld%%",
408 pitch / PITCH_SPEED_PRECISION,
409 (pitch % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
410 }
411 }
412
413 display->getstringsize(buf, &w, &h);
414 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
415 show_lang_pitch ? (pitch_viewports[PITCH_MID].height / 2) :
416 (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
417 buf);
418 if (w > width_used)
419 width_used = w;
420
421 /* "limit" and "timestretch" labels */
422 if (max_lines >= 7)
423 {
424 if(at_limit)
425 {
426 const char * const p = rb->str(LANG_STRETCH_LIMIT);
427 display->getstringsize(p, &w, &h);
428 display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
429 (pitch_viewports[PITCH_MID].height / 2) + h, p);
430 if (w > width_used)
431 width_used = w;
432 }
433 }
434
435 /* Middle section left/right labels */
436 const char *leftlabel = "-2%";
437 const char *rightlabel = "+2%";
438 if (rb->global_settings->pitch_mode_timestretch)
439 {
440 leftlabel = "<<";
441 rightlabel = ">>";
442 }
443
444 /* Only display if they fit */
445 display->getstringsize(leftlabel, &w, &h);
446 width_used += w;
447 display->getstringsize(rightlabel, &w, &h);
448 width_used += w;
449
450 if (width_used <= pitch_viewports[PITCH_MID].width)
451 {
452 display->putsxy(0, (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
453 leftlabel);
454 display->putsxy((pitch_viewports[PITCH_MID].width - w),
455 (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
456 rightlabel);
457 }
458 display->update_viewport();
459 display->set_viewport(last_vp);
460}
461
462static int32_t pitch_increase(int32_t pitch, int32_t pitch_delta, bool allow_cutoff
463 /* need this to maintain correct pitch/speed caps */
464 , int32_t speed
465 )
466{
467 int32_t new_pitch;
468 int32_t new_stretch;
469 at_limit = false;
470
471 if (pitch_delta < 0)
472 {
473 /* for large jumps, snap up to whole numbers */
474 if(allow_cutoff && pitch_delta <= -PITCH_SPEED_PRECISION &&
475 (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
476 {
477 pitch_delta += PITCH_SPEED_PRECISION - ((pitch + pitch_delta) % PITCH_SPEED_PRECISION);
478 }
479
480 new_pitch = pitch + pitch_delta;
481
482 if (new_pitch < PITCH_MIN)
483 {
484 if (!allow_cutoff)
485 {
486 return pitch;
487 }
488 new_pitch = PITCH_MIN;
489 at_limit = true;
490 }
491 }
492 else if (pitch_delta > 0)
493 {
494 /* for large jumps, snap down to whole numbers */
495 if(allow_cutoff && pitch_delta >= PITCH_SPEED_PRECISION &&
496 (pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
497 {
498 pitch_delta -= (pitch + pitch_delta) % PITCH_SPEED_PRECISION;
499 }
500
501 new_pitch = pitch + pitch_delta;
502
503 if (new_pitch > PITCH_MAX)
504 {
505 if (!allow_cutoff)
506 return pitch;
507 new_pitch = PITCH_MAX;
508 at_limit = true;
509 }
510 }
511 else
512 {
513 /* pitch_delta == 0 -> no real change */
514 return pitch;
515 }
516 if (rb->dsp_timestretch_available())
517 {
518 /* increase the multiple to increase precision of this calculation */
519 new_stretch = GET_STRETCH(new_pitch, speed);
520 if(new_stretch < STRETCH_MIN)
521 {
522 /* we have to ignore allow_cutoff, because we can't have the */
523 /* stretch go higher than STRETCH_MAX */
524 new_pitch = GET_PITCH(speed, STRETCH_MIN);
525 }
526 else if(new_stretch > STRETCH_MAX)
527 {
528 /* we have to ignore allow_cutoff, because we can't have the */
529 /* stretch go higher than STRETCH_MAX */
530 new_pitch = GET_PITCH(speed, STRETCH_MAX);
531 }
532
533 if(new_stretch >= STRETCH_MAX ||
534 new_stretch <= STRETCH_MIN)
535 {
536 at_limit = true;
537 }
538 }
539
540 rb->sound_set_pitch(new_pitch);
541
542 return new_pitch;
543}
544
545static int32_t get_semitone_from_pitch(int32_t pitch)
546{
547 int semitone = 0;
548 int32_t fractional_index = 0;
549
550 while(semitone < NUM_SEMITONES - 1 &&
551 pitch >= semitone_table[semitone + 1])
552 {
553 semitone++;
554 }
555
556
557 /* now find the fractional part */
558 while(pitch > (cent_interp[fractional_index + 1] *
559 semitone_table[semitone] / PITCH_SPEED_100))
560 {
561 /* Check to make sure fractional_index isn't too big */
562 /* This should never happen. */
563 if(fractional_index >= CENT_INTERP_NUM - 1)
564 {
565 break;
566 }
567 fractional_index++;
568 }
569
570 int32_t semitone_pitch_a = cent_interp[fractional_index] *
571 semitone_table[semitone] /
572 PITCH_SPEED_100;
573 int32_t semitone_pitch_b = cent_interp[fractional_index + 1] *
574 semitone_table[semitone] /
575 PITCH_SPEED_100;
576 /* this will be the integer offset from the cent_interp entry */
577 int32_t semitone_frac_ofs = (pitch - semitone_pitch_a) * CENT_INTERP_INTERVAL /
578 (semitone_pitch_b - semitone_pitch_a);
579 semitone = (semitone + SEMITONE_START) * PITCH_SPEED_PRECISION +
580 fractional_index * CENT_INTERP_INTERVAL +
581 semitone_frac_ofs;
582
583 return semitone;
584}
585
586static int32_t get_pitch_from_semitone(int32_t semitone)
587{
588 int32_t adjusted_semitone = semitone - SEMITONE_START * PITCH_SPEED_PRECISION;
589
590 /* Find the index into the semitone table */
591 int32_t semitone_index = (adjusted_semitone / PITCH_SPEED_PRECISION);
592
593 /* set pitch to the semitone's integer part value */
594 int32_t pitch = semitone_table[semitone_index];
595 /* get the range of the cent modification for future calculation */
596 int32_t pitch_mod_a =
597 cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
598 CENT_INTERP_INTERVAL];
599 int32_t pitch_mod_b =
600 cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
601 CENT_INTERP_INTERVAL + 1];
602 /* figure out the cent mod amount based on the semitone fractional value */
603 int32_t pitch_mod = pitch_mod_a + (pitch_mod_b - pitch_mod_a) *
604 (adjusted_semitone % CENT_INTERP_INTERVAL) / CENT_INTERP_INTERVAL;
605
606 /* modify pitch based on the mod amount we just calculated */
607 return (pitch * pitch_mod + PITCH_SPEED_100 / 2) / PITCH_SPEED_100;
608}
609
610static int32_t pitch_increase_semitone(int32_t pitch,
611 int32_t current_semitone,
612 int32_t semitone_delta
613 , int32_t speed
614 )
615{
616 int32_t new_semitone = current_semitone;
617
618 /* snap to the delta interval */
619 if(current_semitone % semitone_delta != 0)
620 {
621 if(current_semitone > 0 && semitone_delta > 0)
622 new_semitone += semitone_delta;
623 else if(current_semitone < 0 && semitone_delta < 0)
624 new_semitone += semitone_delta;
625
626 new_semitone -= new_semitone % semitone_delta;
627 }
628 else
629 new_semitone += semitone_delta;
630
631 /* clamp the pitch so it doesn't go beyond the pitch limits */
632 if(new_semitone < (SEMITONE_START * PITCH_SPEED_PRECISION))
633 {
634 new_semitone = SEMITONE_START * PITCH_SPEED_PRECISION;
635 at_limit = true;
636 }
637 else if(new_semitone > (SEMITONE_END * PITCH_SPEED_PRECISION))
638 {
639 new_semitone = SEMITONE_END * PITCH_SPEED_PRECISION;
640 at_limit = true;
641 }
642
643 int32_t new_pitch = get_pitch_from_semitone(new_semitone);
644
645 int32_t new_stretch = GET_STRETCH(new_pitch, speed);
646
647 /* clamp the pitch so it doesn't go beyond the stretch limits */
648 if( new_stretch > STRETCH_MAX)
649 {
650 new_pitch = GET_PITCH(speed, STRETCH_MAX);
651 new_semitone = get_semitone_from_pitch(new_pitch);
652 at_limit = true;
653 }
654 else if (new_stretch < STRETCH_MIN)
655 {
656 new_pitch = GET_PITCH(speed, STRETCH_MIN);
657 new_semitone = get_semitone_from_pitch(new_pitch);
658 at_limit = true;
659 }
660
661 pitch_increase(pitch, new_pitch - pitch, false
662 , speed
663 );
664
665 return new_semitone;
666}
667
668#ifdef HAVE_TOUCHSCREEN
669/*
670 * Check for touchscreen presses as per sketch above in this file
671 *
672 * goes through each row of the, checks whether the touchscreen
673 * was pressed in it. Then it looks the columns of each row for specific actions
674 */
675static int pitchscreen_do_touchscreen(struct viewport vps[])
676{
677 short x, y;
678 struct viewport *this_vp = &vps[PITCH_TOP];
679 int ret;
680 static bool wait_for_release = false;
681 ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
682
683 /* top row */
684 if (ret > ACTION_UNKNOWN)
685 { /* press on top row, left or right column
686 * only toggle mode if released */
687 int column = this_vp->width / 3;
688 if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
689 return ACTION_PS_TOGGLE_MODE;
690
691
692 else if (x >= column && x <= (2*column))
693 { /* center column pressed */
694 if (ret == BUTTON_REPEAT)
695 return ACTION_PS_INC_BIG;
696 else if (ret & BUTTON_REL)
697 return ACTION_PS_INC_SMALL;
698 }
699 return ACTION_NONE;
700 }
701
702 /* now the center row */
703 this_vp = &vps[PITCH_MID];
704 ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
705
706 if (ret > ACTION_UNKNOWN)
707 {
708 int column = this_vp->width / 3;
709
710 if (x < column)
711 { /* left column */
712 if (ret & BUTTON_REL)
713 {
714 wait_for_release = false;
715 return ACTION_PS_NUDGE_LEFTOFF;
716 }
717 else if (ret & BUTTON_REPEAT)
718 return ACTION_PS_SLOWER;
719 if (!wait_for_release)
720 {
721 wait_for_release = true;
722 return ACTION_PS_NUDGE_LEFT;
723 }
724 }
725 else if (x > (2*column))
726 { /* right column */
727 if (ret & BUTTON_REL)
728 {
729 wait_for_release = false;
730 return ACTION_PS_NUDGE_RIGHTOFF;
731 }
732 else if (ret & BUTTON_REPEAT)
733 return ACTION_PS_FASTER;
734 if (!wait_for_release)
735 {
736 wait_for_release = true;
737 return ACTION_PS_NUDGE_RIGHT;
738 }
739 }
740 else
741 /* center column was pressed */
742 return ACTION_PS_RESET;
743 }
744
745 /* now the bottom row */
746 this_vp = &vps[PITCH_BOTTOM];
747 ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
748
749 if (ret > ACTION_UNKNOWN)
750 {
751 int column = this_vp->width / 3;
752
753 /* left or right column is exit */
754 if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
755 return ACTION_PS_EXIT;
756 else if (x >= column && x <= (2*column))
757 { /* center column was pressed */
758 if (ret & BUTTON_REPEAT)
759 return ACTION_PS_DEC_BIG;
760 else if (ret & BUTTON_REL)
761 return ACTION_PS_DEC_SMALL;
762 }
763 return ACTION_NONE;
764 }
765 return ACTION_NONE;
766}
767
768#endif
769/*
770 returns:
771 0 on exit
772 1 if USB was connected
773*/
774
775int gui_syncpitchscreen_run(void)
776{
777 int button;
778 int32_t pitch = rb->sound_get_pitch();
779 int32_t semitone;
780
781 int32_t new_pitch;
782 int32_t pitch_delta;
783 bool nudged = false;
784 int i, updated = 4, decimals = 0;
785 bool exit = false;
786 /* should maybe be passed per parameter later, not needed for now */
787 struct viewport parent[NB_SCREENS];
788 struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT];
789 int max_lines[NB_SCREENS];
790
791 //push_current_activity(ACTIVITY_PITCHSCREEN);
792
793 int32_t new_speed = 0, new_stretch;
794
795 /* the speed variable holds the apparent speed of the playback */
796 int32_t speed;
797 if (rb->dsp_timestretch_available())
798 {
799 speed = GET_SPEED(pitch, rb->dsp_get_timestretch());
800 }
801 else
802 {
803 speed = pitch;
804 }
805
806
807
808 /* Count decimals for speaking */
809 for (i = PITCH_SPEED_PRECISION; i >= 10; i /= 10)
810 decimals++;
811
812 /* set the semitone index based on the current pitch */
813 semitone = get_semitone_from_pitch(pitch);
814
815 /* initialize pitchscreen vps */
816 FOR_NB_SCREENS(i)
817 {
818 rb->viewport_set_defaults(&parent[i], i);
819 max_lines[i] = viewport_get_nb_lines(&parent[i]);
820 pitchscreen_fix_viewports(&parent[i], pitch_viewports[i]);
821 rb->screens[i]->set_viewport(&parent[i]);
822 rb->screens[i]->clear_viewport();
823
824 /* also, draw the icons now, it's only needed once */
825 pitchscreen_draw_icons(rb->screens[i], &parent[i]);
826 }
827
828
829 while (!exit)
830 {
831 FOR_NB_SCREENS(i)
832 pitchscreen_draw(rb->screens[i], max_lines[i],
833 pitch_viewports[i], pitch, semitone
834 , speed
835 );
836 pitch_delta = 0;
837 new_speed = 0;
838
839 if (rb->global_settings->talk_menu && updated)
840 {
841 rb->talk_shutup();
842 switch (updated)
843 {
844 case 1:
845 if (rb->global_settings->pitch_mode_semitone)
846 rb->talk_value_decimal(semitone, UNIT_SIGNED, decimals, false);
847 else
848 rb->talk_value_decimal(pitch, UNIT_PERCENT, decimals, false);
849 break;
850 case 2:
851 rb->talk_value_decimal(speed, UNIT_PERCENT, decimals, false);
852 break;
853 case 3:
854 speak_pitch_mode(false);
855 break;
856 case 4:
857 if (rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available())
858 rb->talk_id(LANG_PITCH, false);
859 else
860 rb->talk_id(LANG_PLAYBACK_RATE, false);
861 rb->talk_value_decimal(pitch, UNIT_PERCENT, decimals, true);
862 if (rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available())
863 {
864 rb->talk_id(LANG_SPEED, true);
865 rb->talk_value_decimal(speed, UNIT_PERCENT, decimals, true);
866 }
867 speak_pitch_mode(true);
868 break;
869 default:
870 break;
871 }
872 }
873 updated = 0;
874
875 button = rb->get_action(CONTEXT_PITCHSCREEN, HZ);
876
877#ifdef HAVE_TOUCHSCREEN
878 if (button == ACTION_TOUCHSCREEN)
879 {
880 FOR_NB_SCREENS(i)
881 button = pitchscreen_do_touchscreen(pitch_viewports[i]);
882 }
883#endif
884 switch (button)
885 {
886 case ACTION_PS_INC_SMALL:
887 if(rb->global_settings->pitch_mode_semitone)
888 pitch_delta = SEMITONE_SMALL_DELTA;
889 else
890 pitch_delta = PITCH_SMALL_DELTA;
891 updated = 1;
892 break;
893
894 case ACTION_PS_INC_BIG:
895 if(rb->global_settings->pitch_mode_semitone)
896 pitch_delta = SEMITONE_BIG_DELTA;
897 else
898 pitch_delta = PITCH_BIG_DELTA;
899 updated = 1;
900 break;
901
902 case ACTION_PS_DEC_SMALL:
903 if(rb->global_settings->pitch_mode_semitone)
904 pitch_delta = -SEMITONE_SMALL_DELTA;
905 else
906 pitch_delta = -PITCH_SMALL_DELTA;
907 updated = 1;
908 break;
909
910 case ACTION_PS_DEC_BIG:
911 if(rb->global_settings->pitch_mode_semitone)
912 pitch_delta = -SEMITONE_BIG_DELTA;
913 else
914 pitch_delta = -PITCH_BIG_DELTA;
915 updated = 1;
916 break;
917
918 case ACTION_PS_NUDGE_RIGHT:
919 if (!rb->global_settings->pitch_mode_timestretch)
920 {
921 new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
922 , speed
923 );
924 nudged = (new_pitch != pitch);
925 pitch = new_pitch;
926 semitone = get_semitone_from_pitch(pitch);
927 speed = pitch;
928 updated = nudged ? 1 : 0;
929 break;
930 }
931 else
932 {
933 new_speed = speed + SPEED_SMALL_DELTA;
934 at_limit = false;
935 updated = 2;
936 }
937 break;
938
939 case ACTION_PS_FASTER:
940 if (rb->global_settings->pitch_mode_timestretch)
941 {
942 new_speed = speed + SPEED_BIG_DELTA;
943 /* snap to whole numbers */
944 if(new_speed % PITCH_SPEED_PRECISION != 0)
945 new_speed -= new_speed % PITCH_SPEED_PRECISION;
946 at_limit = false;
947 updated = 2;
948 }
949 break;
950
951 case ACTION_PS_NUDGE_RIGHTOFF:
952 if (nudged)
953 {
954 pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
955 , speed
956 );
957 speed = pitch;
958 semitone = get_semitone_from_pitch(pitch);
959 nudged = false;
960 updated = 1;
961 }
962 break;
963
964 case ACTION_PS_NUDGE_LEFT:
965 if (!rb->global_settings->pitch_mode_timestretch)
966 {
967 new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
968 , speed
969 );
970 nudged = (new_pitch != pitch);
971 pitch = new_pitch;
972 semitone = get_semitone_from_pitch(pitch);
973 speed = pitch;
974 updated = nudged ? 1 : 0;
975 break;
976 }
977 else
978 {
979 new_speed = speed - SPEED_SMALL_DELTA;
980 at_limit = false;
981 updated = 2;
982 }
983 break;
984
985 case ACTION_PS_SLOWER:
986 if (rb->global_settings->pitch_mode_timestretch)
987 {
988 new_speed = speed - SPEED_BIG_DELTA;
989 /* snap to whole numbers */
990 if(new_speed % PITCH_SPEED_PRECISION != 0)
991 new_speed += PITCH_SPEED_PRECISION - speed % PITCH_SPEED_PRECISION;
992 at_limit = false;
993 updated = 2;
994 }
995 break;
996
997 case ACTION_PS_NUDGE_LEFTOFF:
998 if (nudged)
999 {
1000 pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
1001 , speed
1002 );
1003 speed = pitch;
1004 semitone = get_semitone_from_pitch(pitch);
1005 nudged = false;
1006 updated = 1;
1007 }
1008 break;
1009
1010 case ACTION_PS_RESET:
1011 pitch = PITCH_SPEED_100;
1012 rb->sound_set_pitch(pitch);
1013 speed = PITCH_SPEED_100;
1014 if (rb->dsp_timestretch_available())
1015 {
1016 rb->dsp_set_timestretch(PITCH_SPEED_100);
1017 at_limit = false;
1018 }
1019 semitone = get_semitone_from_pitch(pitch);
1020 updated = 4;
1021 break;
1022
1023 case ACTION_PS_TOGGLE_MODE:
1024 rb->global_settings->pitch_mode_semitone = !rb->global_settings->pitch_mode_semitone;
1025
1026 if (rb->dsp_timestretch_available() && !rb->global_settings->pitch_mode_semitone)
1027 {
1028 rb->global_settings->pitch_mode_timestretch = !rb->global_settings->pitch_mode_timestretch;
1029 if(!rb->global_settings->pitch_mode_timestretch)
1030 {
1031 /* no longer in timestretch mode. Reset speed */
1032 speed = pitch;
1033 rb->dsp_set_timestretch(PITCH_SPEED_100);
1034 }
1035 }
1036 rb->settings_save();
1037 updated = 3;
1038 break;
1039
1040 case ACTION_PS_EXIT:
1041 exit = true;
1042 break;
1043
1044 default:
1045 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
1046 return 1;
1047 break;
1048 }
1049 if (pitch_delta)
1050 {
1051 if (rb->global_settings->pitch_mode_semitone)
1052 {
1053 semitone = pitch_increase_semitone(pitch, semitone, pitch_delta
1054 , speed
1055 );
1056 pitch = get_pitch_from_semitone(semitone);
1057 }
1058 else
1059 {
1060 pitch = pitch_increase(pitch, pitch_delta, true
1061 , speed
1062 );
1063 semitone = get_semitone_from_pitch(pitch);
1064 }
1065 if (rb->global_settings->pitch_mode_timestretch)
1066 {
1067 /* do this to make sure we properly obey the stretch limits */
1068 new_speed = speed;
1069 }
1070 else
1071 {
1072 speed = pitch;
1073 }
1074 }
1075
1076 if(new_speed)
1077 {
1078 new_stretch = GET_STRETCH(pitch, new_speed);
1079
1080 /* limit the amount of stretch */
1081 if(new_stretch > STRETCH_MAX)
1082 {
1083 new_stretch = STRETCH_MAX;
1084 new_speed = GET_SPEED(pitch, new_stretch);
1085 }
1086 else if(new_stretch < STRETCH_MIN)
1087 {
1088 new_stretch = STRETCH_MIN;
1089 new_speed = GET_SPEED(pitch, new_stretch);
1090 }
1091
1092 new_stretch = GET_STRETCH(pitch, new_speed);
1093 if(new_stretch >= STRETCH_MAX ||
1094 new_stretch <= STRETCH_MIN)
1095 {
1096 at_limit = true;
1097 }
1098
1099 /* set the amount of stretch */
1100 rb->dsp_set_timestretch(new_stretch);
1101
1102 /* update the speed variable with the new speed */
1103 speed = new_speed;
1104
1105 /* Reset new_speed so we only call dsp_set_timestretch */
1106 /* when needed */
1107 new_speed = 0;
1108 }
1109 }
1110
1111 //rb->pcmbuf_set_low_latency(false);
1112 //pop_current_activity();
1113
1114 /* Clean up */
1115 FOR_NB_SCREENS(i)
1116 {
1117 rb->screens[i]->set_viewport(NULL);
1118 }
1119
1120 return 0;
1121}
1122
1123static int arg_callback(char argchar, const char **parameter)
1124{
1125 int ret;
1126 long num, dec;
1127 bool bret;
1128 //rb->splashf(100, "Arg: %c", argchar);
1129 while (*parameter[0] > '/' && ispunct(*parameter[0])) (*parameter)++;
1130 switch (tolower(argchar))
1131 {
1132 case 'q' :
1133 pitch_vars.flags &= ~PVAR_VERBOSE;
1134 break;
1135 case 'g' :
1136 pitch_vars.flags |= PVAR_GUI;
1137 break;
1138 case 'p' :
1139 ret = longnum_parse(parameter, &num, &dec);
1140 if (ret)
1141 {
1142 dec /= (ARGPARSE_FRAC_DEC_MULTIPLIER / PITCH_SPEED_PRECISION);
1143 if (num < 0)
1144 dec = -dec;
1145 pitch_vars.pitch = (num * PITCH_SPEED_PRECISION + (dec % PITCH_SPEED_PRECISION));
1146 }
1147 break;
1148 case 'k' :
1149 ret = bool_parse(parameter, &bret);
1150 if (ret)
1151 {
1152 if(!bret && rb->dsp_timestretch_available())
1153 {
1154 /* no longer in timestretch mode. Reset speed */
1155 rb->dsp_set_timestretch(PITCH_SPEED_100);
1156 }
1157 rb->dsp_timestretch_enable(bret);
1158 if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
1159 rb->splashf(HZ, "Timestretch: %s", bret ? "true" : "false");
1160 int n = 10; /* 1 second */
1161 while (bret && n-- > 0 && !rb->dsp_timestretch_available())
1162 {
1163 rb->sleep(HZ / 10);
1164 }
1165 }
1166 break;
1167 case 's' :
1168 ret = longnum_parse(parameter, &num, &dec);
1169 if (ret && rb->dsp_timestretch_available())
1170 {
1171 dec /= (ARGPARSE_FRAC_DEC_MULTIPLIER / PITCH_SPEED_PRECISION);
1172 if (num < 0)
1173 break;
1174 pitch_vars.speed = (num * PITCH_SPEED_PRECISION + (dec % PITCH_SPEED_PRECISION));
1175
1176 }
1177 break;
1178 default :
1179 rb->splashf(HZ, "Unknown switch '%c'",argchar);
1180 //return 0;
1181 }
1182
1183 return 1;
1184}
1185
1186void fill_pitchvars(struct pvars *pv)
1187{
1188 if (!pv)
1189 return;
1190 pv->pitch = rb->sound_get_pitch();
1191
1192 /* the speed variable holds the apparent speed of the playback */
1193 if (rb->dsp_timestretch_available())
1194 {
1195 pv->speed = GET_SPEED(pv->pitch, rb->dsp_get_timestretch());
1196 }
1197 else
1198 {
1199 pv->speed = pv->pitch;
1200 }
1201
1202 pv->stretch = GET_STRETCH(pv->pitch, pv->speed);
1203 pv->flags |= PVAR_VERBOSE;
1204
1205}
1206/* plugin entry point */
1207enum plugin_status plugin_start(const void* parameter)
1208{
1209 /* pitch_screen
1210 * accepts args -q, -g, -p=, -s=, -k=; (= sign is optional)
1211 * -q silences output splash
1212 * -g runs the gui (DEFAULT)
1213 * -p100 would set pitch to 100%
1214 * -s=90 sets speed to 90% if timestrech is enabled
1215 * -k=true -k1 enables time stretch -k0 -kf-kn disables
1216*/
1217 bool gui = false;
1218 rb->pcmbuf_set_low_latency(true);
1219
1220 /* Figure out whether to be in timestretch mode */
1221 if (parameter == NULL) /* gui mode */
1222 {
1223 if (rb->global_settings->pitch_mode_timestretch && !rb->dsp_timestretch_available())
1224 {
1225 rb->global_settings->pitch_mode_timestretch = false;
1226 rb->settings_save();
1227 }
1228 gui = true;
1229 }
1230 else
1231 {
1232 struct pvars cur;
1233 fill_pitchvars(&cur);
1234 fill_pitchvars(&pitch_vars);
1235 argparse((const char*) parameter, -1, &arg_callback);
1236 if (pitch_vars.pitch != cur.pitch)
1237 {
1238 rb->sound_set_pitch(pitch_vars.pitch);
1239 pitch_vars.pitch = rb->sound_get_pitch();
1240 if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
1241 rb->splashf(HZ, "pitch: %ld.%02ld%%",
1242 pitch_vars.pitch / PITCH_SPEED_PRECISION,
1243 pitch_vars.pitch % PITCH_SPEED_PRECISION);
1244 }
1245 if (pitch_vars.speed != cur.speed)
1246 {
1247 pitch_vars.stretch = GET_STRETCH(pitch_vars.pitch, pitch_vars.speed);
1248
1249 /* limit the amount of stretch */
1250 if(pitch_vars.stretch > STRETCH_MAX)
1251 {
1252 pitch_vars.stretch = STRETCH_MAX;
1253 pitch_vars.speed = GET_SPEED(pitch_vars.pitch, pitch_vars.stretch);
1254 }
1255 else if(pitch_vars.stretch < STRETCH_MIN)
1256 {
1257 pitch_vars.stretch = STRETCH_MIN;
1258 pitch_vars.speed = GET_SPEED(pitch_vars.pitch, pitch_vars.stretch);
1259 }
1260
1261 pitch_vars.stretch = GET_STRETCH(pitch_vars.pitch, pitch_vars.speed);
1262 if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
1263 rb->splashf(HZ, "speed: %ld.%02ld%%",
1264 pitch_vars.speed / PITCH_SPEED_PRECISION,
1265 pitch_vars.speed % PITCH_SPEED_PRECISION);
1266 /* set the amount of stretch */
1267 rb->dsp_set_timestretch(pitch_vars.stretch);
1268 }
1269 gui = ((pitch_vars.flags & PVAR_GUI) == PVAR_GUI);
1270 if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
1271 rb->splashf(HZ, "GUI: %d", gui);
1272
1273 }
1274
1275 if (gui && gui_syncpitchscreen_run() == 1)
1276 return PLUGIN_USB_CONNECTED;
1277 rb->pcmbuf_set_low_latency(false);
1278 return PLUGIN_OK;
1279}