summaryrefslogtreecommitdiff
path: root/apps/plugins/pitch_screen.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/pitch_screen.c')
-rw-r--r--apps/plugins/pitch_screen.c1279
1 files changed, 1279 insertions, 0 deletions
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}