diff options
author | William Wilgus <wilgus.william@gmail.com> | 2021-08-19 22:20:56 -0400 |
---|---|---|
committer | William Wilgus <me.theuser@yahoo.com> | 2021-11-10 22:38:37 -0500 |
commit | cf009b4cbb1eec083ab17ce370df090979eaf68e (patch) | |
tree | 19c2346a0e92878ef078879b77e262bf3380ec72 /apps/plugins/pitch_screen.c | |
parent | 67fb558c8997f47440b176fe5553246486b1a838 (diff) | |
download | rockbox-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
Diffstat (limited to 'apps/plugins/pitch_screen.c')
-rw-r--r-- | apps/plugins/pitch_screen.c | 1279 |
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 | ||
43 | struct pvars | ||
44 | { | ||
45 | int32_t speed; | ||
46 | int32_t pitch; | ||
47 | int32_t stretch; | ||
48 | int32_t flags; | ||
49 | }; | ||
50 | static struct pvars pitch_vars; | ||
51 | |||
52 | enum | ||
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 | |||
78 | static 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 | |||
119 | static 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 | |||
130 | int 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) */ | ||
135 | enum 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 | |||
157 | const 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 */ | ||
186 | static 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 | |||
209 | static 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 | **/ | ||
227 | static 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 */ | ||
256 | static 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 | |||
282 | static 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 | |||
462 | static 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 | |||
545 | static 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 | |||
586 | static 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 | |||
610 | static 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 | */ | ||
675 | static 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 | |||
775 | int 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 | |||
1123 | static 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 | |||
1186 | void 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 */ | ||
1207 | enum 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 | } | ||