A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 1280 lines 44 kB view raw
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, void *userdata) 1124{ 1125 (void)userdata; 1126 int ret; 1127 long num, dec; 1128 bool bret; 1129 //rb->splashf(100, "Arg: %c", argchar); 1130 while (*parameter[0] > '/' && ispunct(*parameter[0])) (*parameter)++; 1131 switch (tolower(argchar)) 1132 { 1133 case 'q' : 1134 pitch_vars.flags &= ~PVAR_VERBOSE; 1135 break; 1136 case 'g' : 1137 pitch_vars.flags |= PVAR_GUI; 1138 break; 1139 case 'p' : 1140 ret = longnum_parse(parameter, &num, &dec); 1141 if (ret) 1142 { 1143 dec /= (ARGPARSE_FRAC_DEC_MULTIPLIER / PITCH_SPEED_PRECISION); 1144 if (num < 0) 1145 dec = -dec; 1146 pitch_vars.pitch = (num * PITCH_SPEED_PRECISION + (dec % PITCH_SPEED_PRECISION)); 1147 } 1148 break; 1149 case 'k' : 1150 ret = bool_parse(parameter, &bret); 1151 if (ret) 1152 { 1153 if(!bret && rb->dsp_timestretch_available()) 1154 { 1155 /* no longer in timestretch mode. Reset speed */ 1156 rb->dsp_set_timestretch(PITCH_SPEED_100); 1157 } 1158 rb->dsp_timestretch_enable(bret); 1159 if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE) 1160 rb->splashf(HZ, "Timestretch: %s", bret ? "true" : "false"); 1161 int n = 10; /* 1 second */ 1162 while (bret && n-- > 0 && !rb->dsp_timestretch_available()) 1163 { 1164 rb->sleep(HZ / 10); 1165 } 1166 } 1167 break; 1168 case 's' : 1169 ret = longnum_parse(parameter, &num, &dec); 1170 if (ret && rb->dsp_timestretch_available()) 1171 { 1172 dec /= (ARGPARSE_FRAC_DEC_MULTIPLIER / PITCH_SPEED_PRECISION); 1173 if (num < 0) 1174 break; 1175 pitch_vars.speed = (num * PITCH_SPEED_PRECISION + (dec % PITCH_SPEED_PRECISION)); 1176 1177 } 1178 break; 1179 default : 1180 rb->splashf(HZ, "Unknown switch '%c'",argchar); 1181 //return 0; 1182 } 1183 1184 return 1; 1185} 1186 1187void fill_pitchvars(struct pvars *pv) 1188{ 1189 if (!pv) 1190 return; 1191 pv->pitch = rb->sound_get_pitch(); 1192 1193 /* the speed variable holds the apparent speed of the playback */ 1194 if (rb->dsp_timestretch_available()) 1195 { 1196 pv->speed = GET_SPEED(pv->pitch, rb->dsp_get_timestretch()); 1197 } 1198 else 1199 { 1200 pv->speed = pv->pitch; 1201 } 1202 1203 pv->stretch = GET_STRETCH(pv->pitch, pv->speed); 1204 pv->flags |= PVAR_VERBOSE; 1205 1206} 1207/* plugin entry point */ 1208enum plugin_status plugin_start(const void* parameter) 1209{ 1210 /* pitch_screen 1211 * accepts args -q, -g, -p=, -s=, -k=; (= sign is optional) 1212 * -q silences output splash 1213 * -g runs the gui (DEFAULT) 1214 * -p100 would set pitch to 100% 1215 * -s=90 sets speed to 90% if timestrech is enabled 1216 * -k=true -k1 enables time stretch -k0 -kf-kn disables 1217*/ 1218 bool gui = false; 1219 rb->pcmbuf_set_low_latency(true); 1220 1221 /* Figure out whether to be in timestretch mode */ 1222 if (parameter == NULL) /* gui mode */ 1223 { 1224 if (rb->global_settings->pitch_mode_timestretch && !rb->dsp_timestretch_available()) 1225 { 1226 rb->global_settings->pitch_mode_timestretch = false; 1227 rb->settings_save(); 1228 } 1229 gui = true; 1230 } 1231 else 1232 { 1233 struct pvars cur; 1234 fill_pitchvars(&cur); 1235 fill_pitchvars(&pitch_vars); 1236 argparse((const char*) parameter, -1, NULL, &arg_callback); 1237 if (pitch_vars.pitch != cur.pitch) 1238 { 1239 rb->sound_set_pitch(pitch_vars.pitch); 1240 pitch_vars.pitch = rb->sound_get_pitch(); 1241 if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE) 1242 rb->splashf(HZ, "pitch: %ld.%02ld%%", 1243 pitch_vars.pitch / PITCH_SPEED_PRECISION, 1244 pitch_vars.pitch % PITCH_SPEED_PRECISION); 1245 } 1246 if (pitch_vars.speed != cur.speed) 1247 { 1248 pitch_vars.stretch = GET_STRETCH(pitch_vars.pitch, pitch_vars.speed); 1249 1250 /* limit the amount of stretch */ 1251 if(pitch_vars.stretch > STRETCH_MAX) 1252 { 1253 pitch_vars.stretch = STRETCH_MAX; 1254 pitch_vars.speed = GET_SPEED(pitch_vars.pitch, pitch_vars.stretch); 1255 } 1256 else if(pitch_vars.stretch < STRETCH_MIN) 1257 { 1258 pitch_vars.stretch = STRETCH_MIN; 1259 pitch_vars.speed = GET_SPEED(pitch_vars.pitch, pitch_vars.stretch); 1260 } 1261 1262 pitch_vars.stretch = GET_STRETCH(pitch_vars.pitch, pitch_vars.speed); 1263 if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE) 1264 rb->splashf(HZ, "speed: %ld.%02ld%%", 1265 pitch_vars.speed / PITCH_SPEED_PRECISION, 1266 pitch_vars.speed % PITCH_SPEED_PRECISION); 1267 /* set the amount of stretch */ 1268 rb->dsp_set_timestretch(pitch_vars.stretch); 1269 } 1270 gui = ((pitch_vars.flags & PVAR_GUI) == PVAR_GUI); 1271 if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE) 1272 rb->splashf(HZ, "GUI: %d", gui); 1273 1274 } 1275 1276 if (gui && gui_syncpitchscreen_run() == 1) 1277 return PLUGIN_USB_CONNECTED; 1278 rb->pcmbuf_set_low_latency(false); 1279 return PLUGIN_OK; 1280}