6b87bfadff
remove some old cruft move a few things around clean-up flow Change-Id: I138c6cd9e2d58ef526eb686333da413819df725d
1279 lines
43 KiB
C
1279 lines
43 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2002 Björn Stenberg
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include "plugin.h"
|
|
#include "lib/icon_helper.h"
|
|
#include "lib/arg_helper.h"
|
|
|
|
#define ICON_BORDER 12 /* icons are currently 7x8, so add ~2 pixels */
|
|
/* on both sides when drawing */
|
|
|
|
#define PITCH_MAX (200 * PITCH_SPEED_PRECISION)
|
|
#define PITCH_MIN (50 * PITCH_SPEED_PRECISION)
|
|
#define PITCH_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
|
|
#define PITCH_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
|
|
#define PITCH_NUDGE_DELTA (2 * PITCH_SPEED_PRECISION) /* 2% */
|
|
|
|
#define SPEED_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* .1% */
|
|
#define SPEED_BIG_DELTA (PITCH_SPEED_PRECISION) /* 1% */
|
|
|
|
#define SEMITONE_SMALL_DELTA (PITCH_SPEED_PRECISION / 10) /* 10 cents */
|
|
#define SEMITONE_BIG_DELTA PITCH_SPEED_PRECISION /* 1 semitone */
|
|
|
|
#define PVAR_VERBOSE 0x01
|
|
#define PVAR_GUI 0x02
|
|
struct pvars
|
|
{
|
|
int32_t speed;
|
|
int32_t pitch;
|
|
int32_t stretch;
|
|
int32_t flags;
|
|
};
|
|
static struct pvars pitch_vars;
|
|
|
|
enum
|
|
{
|
|
PITCH_TOP = 0,
|
|
PITCH_MID,
|
|
PITCH_BOTTOM,
|
|
PITCH_ITEM_COUNT,
|
|
};
|
|
|
|
/* This is a table of semitone percentage values of the appropriate
|
|
precision (based on PITCH_SPEED_PRECISION). Note that these are
|
|
all constant expressions, which will be evaluated at compile time,
|
|
so no need to worry about how complex the expressions look.
|
|
That's just to get the precision right.
|
|
|
|
I calculated these values, starting from 50, as
|
|
|
|
x(n) = 50 * 2^(n/12)
|
|
|
|
All that math in each entry simply converts the float constant
|
|
to an integer equal to PITCH_SPEED_PRECISION times the float value,
|
|
with as little precision loss as possible (i.e. correctly rounding
|
|
the last digit).
|
|
*/
|
|
#define TO_INT_WITH_PRECISION(x) \
|
|
( (unsigned short)(((x) * PITCH_SPEED_PRECISION * 10 + 5) / 10) )
|
|
|
|
static const unsigned short semitone_table[] =
|
|
{
|
|
TO_INT_WITH_PRECISION(50.00000000), /* Octave lower */
|
|
TO_INT_WITH_PRECISION(52.97315472),
|
|
TO_INT_WITH_PRECISION(56.12310242),
|
|
TO_INT_WITH_PRECISION(59.46035575),
|
|
TO_INT_WITH_PRECISION(62.99605249),
|
|
TO_INT_WITH_PRECISION(66.74199271),
|
|
TO_INT_WITH_PRECISION(70.71067812),
|
|
TO_INT_WITH_PRECISION(74.91535384),
|
|
TO_INT_WITH_PRECISION(79.37005260),
|
|
TO_INT_WITH_PRECISION(84.08964153),
|
|
TO_INT_WITH_PRECISION(89.08987181),
|
|
TO_INT_WITH_PRECISION(94.38743127),
|
|
TO_INT_WITH_PRECISION(100.0000000), /* Normal sound */
|
|
TO_INT_WITH_PRECISION(105.9463094),
|
|
TO_INT_WITH_PRECISION(112.2462048),
|
|
TO_INT_WITH_PRECISION(118.9207115),
|
|
TO_INT_WITH_PRECISION(125.9921049),
|
|
TO_INT_WITH_PRECISION(133.4839854),
|
|
TO_INT_WITH_PRECISION(141.4213562),
|
|
TO_INT_WITH_PRECISION(149.8307077),
|
|
TO_INT_WITH_PRECISION(158.7401052),
|
|
TO_INT_WITH_PRECISION(168.1792831),
|
|
TO_INT_WITH_PRECISION(178.1797436),
|
|
TO_INT_WITH_PRECISION(188.7748625),
|
|
TO_INT_WITH_PRECISION(200.0000000) /* Octave higher */
|
|
};
|
|
|
|
#define NUM_SEMITONES ((int)(sizeof(semitone_table)/sizeof(semitone_table[0])))
|
|
#define SEMITONE_END (NUM_SEMITONES/2)
|
|
#define SEMITONE_START (-SEMITONE_END)
|
|
|
|
/* A table of values for approximating the cent curve with
|
|
linear interpolation. Multipy the next lowest semitone
|
|
by this much to find the corresponding cent percentage.
|
|
|
|
These values were calculated as
|
|
x(n) = 100 * 2^(n * 20/1200)
|
|
*/
|
|
|
|
static const unsigned short cent_interp[] =
|
|
{
|
|
TO_INT_WITH_PRECISION(100.0000000),
|
|
TO_INT_WITH_PRECISION(101.1619440),
|
|
TO_INT_WITH_PRECISION(102.3373892),
|
|
TO_INT_WITH_PRECISION(103.5264924),
|
|
TO_INT_WITH_PRECISION(104.7294123),
|
|
/* this one's the next semitone but we have it here for convenience */
|
|
TO_INT_WITH_PRECISION(105.9463094),
|
|
};
|
|
|
|
int viewport_get_nb_lines(const struct viewport *vp)
|
|
{
|
|
return vp->height/rb->font_get(vp->font)->height;
|
|
}
|
|
#if 0 /* replaced with cbmp_get_icon(CBMP_Mono_7x8, Icon_ABCD, &w, &h) */
|
|
enum icons_7x8 {
|
|
Icon_Plug,
|
|
Icon_USBPlug,
|
|
Icon_Mute,
|
|
Icon_Play,
|
|
Icon_Stop,
|
|
Icon_Pause,
|
|
Icon_FastForward,
|
|
Icon_FastBackward,
|
|
Icon_Record,
|
|
Icon_RecPause,
|
|
Icon_Radio,
|
|
Icon_Radio_Mute,
|
|
Icon_Repeat,
|
|
Icon_RepeatOne,
|
|
Icon_Shuffle,
|
|
Icon_DownArrow,
|
|
Icon_UpArrow,
|
|
Icon_RepeatAB,
|
|
Icon7x8Last
|
|
};
|
|
|
|
const unsigned char bitmap_icons_7x8[][7] =
|
|
{
|
|
{0x08,0x1c,0x3e,0x3e,0x3e,0x14,0x14}, /* Power plug */
|
|
{0x1c,0x14,0x3e,0x2a,0x22,0x1c,0x08}, /* USB plug */
|
|
{0x01,0x1e,0x1c,0x3e,0x7f,0x20,0x40}, /* Speaker mute */
|
|
{0x00,0x7f,0x7f,0x3e,0x1c,0x08,0x00}, /* Play */
|
|
{0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f}, /* Stop */
|
|
{0x00,0x7f,0x7f,0x00,0x7f,0x7f,0x00}, /* Pause */
|
|
{0x7f,0x3e,0x1c,0x7f,0x3e,0x1c,0x08}, /* Fast forward */
|
|
{0x08,0x1c,0x3e,0x7f,0x1c,0x3e,0x7f}, /* Fast backward */
|
|
{0x1c,0x3e,0x7f,0x7f,0x7f,0x3e,0x1c}, /* Record */
|
|
{0x1c,0x3e,0x7f,0x00,0x7f,0x3e,0x1c}, /* Record pause */
|
|
{0x40,0xa0,0xa0,0xa0,0x7f,0x02,0x02}, /* Radio on */
|
|
{0x42,0xa4,0xa8,0xb0,0x7f,0x22,0x42}, /* Radio mute */
|
|
{0x44,0x4e,0x5f,0x44,0x44,0x44,0x38}, /* Repeat playmode */
|
|
{0x44,0x4e,0x5f,0x44,0x38,0x02,0x7f}, /* Repeat-one playmode */
|
|
{0x3e,0x41,0x51,0x41,0x45,0x41,0x3e}, /* Shuffle playmode (dice) */
|
|
{0x04,0x0c,0x1c,0x3c,0x1c,0x0c,0x04}, /* Down-arrow */
|
|
{0x20,0x30,0x38,0x3c,0x38,0x30,0x20}, /* Up-arrow */
|
|
{0x7f,0x04,0x4e,0x5f,0x44,0x38,0x7f} /* Repeat-AB playmode */
|
|
};
|
|
#endif
|
|
|
|
/* Number of cents between entries in the cent_interp table */
|
|
#define CENT_INTERP_INTERVAL 20
|
|
#define CENT_INTERP_NUM ((int)(sizeof(cent_interp)/sizeof(cent_interp[0])))
|
|
|
|
/* This stores whether the pitch and speed are at their own limits */
|
|
/* or that of the timestretching algorithm */
|
|
static bool at_limit = false;
|
|
|
|
/*
|
|
*
|
|
* The pitchscreen is divided into 3 viewports (each row is a viewport)
|
|
* Then each viewport is again divided into 3 colums, each showsing some infos
|
|
* Additionally, on touchscreen, each cell represents a button
|
|
*
|
|
* Below a sketch describing what each cell will show (what's drawn on it)
|
|
* --------------------------
|
|
* | | | | <-- pitch up in the middle (text and button)
|
|
* | | | | <-- arrows for mode toggling on the sides for touchscreen
|
|
* |------------------------|
|
|
* | | | | <-- semitone/speed up/down on the sides
|
|
* | | | | <-- reset pitch&speed in the middle
|
|
* |------------------------|
|
|
* | | | | <-- pitch down in the middle
|
|
* | | | | <-- Two "OK" for exit on the sides for touchscreen
|
|
* |------------------------|
|
|
*
|
|
*
|
|
*/
|
|
|
|
static void speak_pitch_mode(bool enqueue)
|
|
{
|
|
bool timestretch_mode = rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available();
|
|
if (timestretch_mode)
|
|
rb->talk_id(VOICE_PITCH_TIMESTRETCH_MODE, enqueue);
|
|
if (rb->global_settings->pitch_mode_semitone)
|
|
rb->talk_id(VOICE_PITCH_SEMITONE_MODE, timestretch_mode ? true : enqueue);
|
|
else
|
|
rb->talk_id(VOICE_PITCH_ABSOLUTE_MODE, timestretch_mode ? true : enqueue);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Fixes the viewports so they represent the 3 rows, and adds a little margin
|
|
* on all sides for the icons (which are drawn outside of the grid
|
|
*
|
|
* The modified viewports need to be passed to the touchscreen handling function
|
|
**/
|
|
static void pitchscreen_fix_viewports(struct viewport *parent,
|
|
struct viewport pitch_viewports[PITCH_ITEM_COUNT])
|
|
{
|
|
int i, font_height;
|
|
font_height = rb->font_get(parent->font)->height;
|
|
for (i = 0; i < PITCH_ITEM_COUNT; i++)
|
|
{
|
|
pitch_viewports[i] = *parent;
|
|
pitch_viewports[i].height = parent->height / PITCH_ITEM_COUNT;
|
|
pitch_viewports[i].x += ICON_BORDER;
|
|
pitch_viewports[i].width -= 2*ICON_BORDER;
|
|
}
|
|
pitch_viewports[PITCH_TOP].y += ICON_BORDER;
|
|
pitch_viewports[PITCH_TOP].height -= ICON_BORDER;
|
|
|
|
if(pitch_viewports[PITCH_MID].height < font_height * 2)
|
|
pitch_viewports[PITCH_MID].height = font_height * 2;
|
|
|
|
pitch_viewports[PITCH_MID].y = pitch_viewports[PITCH_TOP].y
|
|
+ pitch_viewports[PITCH_TOP].height;
|
|
|
|
pitch_viewports[PITCH_BOTTOM].y = pitch_viewports[PITCH_MID].y
|
|
+ pitch_viewports[PITCH_MID].height;
|
|
|
|
pitch_viewports[PITCH_BOTTOM].height -= ICON_BORDER;
|
|
}
|
|
|
|
/* must be called before pitchscreen_draw, or within
|
|
* since it neither clears nor updates the display */
|
|
static void pitchscreen_draw_icons(struct screen *display,
|
|
struct viewport *parent)
|
|
{
|
|
|
|
display->set_viewport(parent);
|
|
int w, h;
|
|
const unsigned char* uparrow = cbmp_get_icon(CBMP_Mono_7x8, Icon_UpArrow, &w, &h);
|
|
if (uparrow)
|
|
display->mono_bitmap(uparrow, parent->width/2 - 3, 2, w, h);
|
|
|
|
const unsigned char* dnarrow = cbmp_get_icon(CBMP_Mono_7x8, Icon_DownArrow, &w, &h);
|
|
if (dnarrow)
|
|
display->mono_bitmap(dnarrow, parent->width /2 - 3, parent->height - 10, w, h);
|
|
|
|
const unsigned char* fastfwd = cbmp_get_icon(CBMP_Mono_7x8, Icon_FastForward, &w, &h);
|
|
if (fastfwd)
|
|
display->mono_bitmap(fastfwd, parent->width - 10, parent->height /2 - 4, 7, 8);
|
|
|
|
const unsigned char* fastrew = cbmp_get_icon(CBMP_Mono_7x8, Icon_FastBackward, &w, &h);
|
|
if (fastrew)
|
|
display->mono_bitmap(fastrew, 2, parent->height /2 - 4, w, h);
|
|
|
|
display->update_viewport();
|
|
|
|
}
|
|
|
|
static void pitchscreen_draw(struct screen *display, int max_lines,
|
|
struct viewport pitch_viewports[PITCH_ITEM_COUNT],
|
|
int32_t pitch, int32_t semitone
|
|
,int32_t speed
|
|
)
|
|
{
|
|
const char* ptr;
|
|
char buf[32];
|
|
int w, h;
|
|
bool show_lang_pitch;
|
|
struct viewport *last_vp = NULL;
|
|
|
|
/* "Pitch up/Pitch down" - hide for a small screen,
|
|
* the text is drawn centered automatically
|
|
*
|
|
* note: this assumes 5 lines always fit on a touchscreen (should be
|
|
* reasonable) */
|
|
if (max_lines >= 5)
|
|
{
|
|
int w, h;
|
|
struct viewport *vp = &pitch_viewports[PITCH_TOP];
|
|
last_vp = display->set_viewport(vp);
|
|
display->clear_viewport();
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
/* two arrows in the top row, left and right column */
|
|
char *arrows[] = { "<", ">"};
|
|
display->getstringsize(arrows[0], &w, &h);
|
|
display->putsxy(0, vp->height/2 - h/2, arrows[0]);
|
|
display->putsxy(vp->width - w, vp->height/2 - h/2, arrows[1]);
|
|
#endif
|
|
/* UP: Pitch Up */
|
|
if (rb->global_settings->pitch_mode_semitone)
|
|
ptr = rb->str(LANG_PITCH_UP_SEMITONE);
|
|
else
|
|
ptr = rb->str(LANG_PITCH_UP);
|
|
|
|
display->getstringsize(ptr, &w, NULL);
|
|
/* draw text */
|
|
display->putsxy(vp->width/2 - w/2, 0, ptr);
|
|
display->update_viewport();
|
|
|
|
/* DOWN: Pitch Down */
|
|
vp = &pitch_viewports[PITCH_BOTTOM];
|
|
display->set_viewport(vp);
|
|
display->clear_viewport();
|
|
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
ptr = rb->str(LANG_KBD_OK);
|
|
display->getstringsize(ptr, &w, &h);
|
|
/* one OK in the middle first column of the vp (at half height) */
|
|
display->putsxy(vp->width/6 - w/2, vp->height/2 - h/2, ptr);
|
|
/* one OK in the middle of the last column of the vp (at half height) */
|
|
display->putsxy(5*vp->width/6 - w/2, vp->height/2 - h/2, ptr);
|
|
#endif
|
|
if (rb->global_settings->pitch_mode_semitone)
|
|
ptr = rb->str(LANG_PITCH_DOWN_SEMITONE);
|
|
else
|
|
ptr = rb->str(LANG_PITCH_DOWN);
|
|
display->getstringsize(ptr, &w, &h);
|
|
/* draw text */
|
|
display->putsxy(vp->width/2 - w/2, vp->height - h, ptr);
|
|
display->update_viewport();
|
|
}
|
|
|
|
/* Middle section */
|
|
display->set_viewport(&pitch_viewports[PITCH_MID]);
|
|
display->clear_viewport();
|
|
int width_used = 0;
|
|
|
|
/* Middle section upper line - hide for a small screen */
|
|
if ((show_lang_pitch = (max_lines >= 3)))
|
|
{
|
|
if(rb->global_settings->pitch_mode_timestretch)
|
|
{
|
|
/* Pitch:XXX.X% */
|
|
if(rb->global_settings->pitch_mode_semitone)
|
|
{
|
|
rb->snprintf(buf, sizeof(buf), "%s: %s%d.%02d", rb->str(LANG_PITCH),
|
|
semitone >= 0 ? "+" : "-",
|
|
abs(semitone / PITCH_SPEED_PRECISION),
|
|
abs((semitone % PITCH_SPEED_PRECISION) /
|
|
(PITCH_SPEED_PRECISION / 100))
|
|
);
|
|
}
|
|
else
|
|
{
|
|
rb->snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", rb->str(LANG_PITCH),
|
|
pitch / PITCH_SPEED_PRECISION,
|
|
(pitch % PITCH_SPEED_PRECISION) /
|
|
(PITCH_SPEED_PRECISION / 10));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Rate */
|
|
rb->snprintf(buf, sizeof(buf), "%s:", rb->str(LANG_PLAYBACK_RATE));
|
|
}
|
|
display->getstringsize(buf, &w, &h);
|
|
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
|
|
(pitch_viewports[PITCH_MID].height / 2) - h, buf);
|
|
if (w > width_used)
|
|
width_used = w;
|
|
}
|
|
|
|
/* Middle section lower line */
|
|
/* "Speed:XXX%" */
|
|
if(rb->global_settings->pitch_mode_timestretch)
|
|
{
|
|
rb->snprintf(buf, sizeof(buf), "%s: %ld.%ld%%", rb->str(LANG_SPEED),
|
|
speed / PITCH_SPEED_PRECISION,
|
|
(speed % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
|
|
}
|
|
else
|
|
{
|
|
if(rb->global_settings->pitch_mode_semitone)
|
|
{
|
|
rb->snprintf(buf, sizeof(buf), "%s%d.%02d",
|
|
semitone >= 0 ? "+" : "-",
|
|
abs(semitone / PITCH_SPEED_PRECISION),
|
|
abs((semitone % PITCH_SPEED_PRECISION) /
|
|
(PITCH_SPEED_PRECISION / 100))
|
|
);
|
|
}
|
|
else
|
|
{
|
|
rb->snprintf(buf, sizeof(buf), "%ld.%ld%%",
|
|
pitch / PITCH_SPEED_PRECISION,
|
|
(pitch % PITCH_SPEED_PRECISION) / (PITCH_SPEED_PRECISION / 10));
|
|
}
|
|
}
|
|
|
|
display->getstringsize(buf, &w, &h);
|
|
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
|
|
show_lang_pitch ? (pitch_viewports[PITCH_MID].height / 2) :
|
|
(pitch_viewports[PITCH_MID].height / 2) - (h / 2),
|
|
buf);
|
|
if (w > width_used)
|
|
width_used = w;
|
|
|
|
/* "limit" and "timestretch" labels */
|
|
if (max_lines >= 7)
|
|
{
|
|
if(at_limit)
|
|
{
|
|
const char * const p = rb->str(LANG_STRETCH_LIMIT);
|
|
display->getstringsize(p, &w, &h);
|
|
display->putsxy((pitch_viewports[PITCH_MID].width / 2) - (w / 2),
|
|
(pitch_viewports[PITCH_MID].height / 2) + h, p);
|
|
if (w > width_used)
|
|
width_used = w;
|
|
}
|
|
}
|
|
|
|
/* Middle section left/right labels */
|
|
const char *leftlabel = "-2%";
|
|
const char *rightlabel = "+2%";
|
|
if (rb->global_settings->pitch_mode_timestretch)
|
|
{
|
|
leftlabel = "<<";
|
|
rightlabel = ">>";
|
|
}
|
|
|
|
/* Only display if they fit */
|
|
display->getstringsize(leftlabel, &w, &h);
|
|
width_used += w;
|
|
display->getstringsize(rightlabel, &w, &h);
|
|
width_used += w;
|
|
|
|
if (width_used <= pitch_viewports[PITCH_MID].width)
|
|
{
|
|
display->putsxy(0, (pitch_viewports[PITCH_MID].height / 2) - (h / 2),
|
|
leftlabel);
|
|
display->putsxy((pitch_viewports[PITCH_MID].width - w),
|
|
(pitch_viewports[PITCH_MID].height / 2) - (h / 2),
|
|
rightlabel);
|
|
}
|
|
display->update_viewport();
|
|
display->set_viewport(last_vp);
|
|
}
|
|
|
|
static int32_t pitch_increase(int32_t pitch, int32_t pitch_delta, bool allow_cutoff
|
|
/* need this to maintain correct pitch/speed caps */
|
|
, int32_t speed
|
|
)
|
|
{
|
|
int32_t new_pitch;
|
|
int32_t new_stretch;
|
|
at_limit = false;
|
|
|
|
if (pitch_delta < 0)
|
|
{
|
|
/* for large jumps, snap up to whole numbers */
|
|
if(allow_cutoff && pitch_delta <= -PITCH_SPEED_PRECISION &&
|
|
(pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
|
|
{
|
|
pitch_delta += PITCH_SPEED_PRECISION - ((pitch + pitch_delta) % PITCH_SPEED_PRECISION);
|
|
}
|
|
|
|
new_pitch = pitch + pitch_delta;
|
|
|
|
if (new_pitch < PITCH_MIN)
|
|
{
|
|
if (!allow_cutoff)
|
|
{
|
|
return pitch;
|
|
}
|
|
new_pitch = PITCH_MIN;
|
|
at_limit = true;
|
|
}
|
|
}
|
|
else if (pitch_delta > 0)
|
|
{
|
|
/* for large jumps, snap down to whole numbers */
|
|
if(allow_cutoff && pitch_delta >= PITCH_SPEED_PRECISION &&
|
|
(pitch + pitch_delta) % PITCH_SPEED_PRECISION != 0)
|
|
{
|
|
pitch_delta -= (pitch + pitch_delta) % PITCH_SPEED_PRECISION;
|
|
}
|
|
|
|
new_pitch = pitch + pitch_delta;
|
|
|
|
if (new_pitch > PITCH_MAX)
|
|
{
|
|
if (!allow_cutoff)
|
|
return pitch;
|
|
new_pitch = PITCH_MAX;
|
|
at_limit = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* pitch_delta == 0 -> no real change */
|
|
return pitch;
|
|
}
|
|
if (rb->dsp_timestretch_available())
|
|
{
|
|
/* increase the multiple to increase precision of this calculation */
|
|
new_stretch = GET_STRETCH(new_pitch, speed);
|
|
if(new_stretch < STRETCH_MIN)
|
|
{
|
|
/* we have to ignore allow_cutoff, because we can't have the */
|
|
/* stretch go higher than STRETCH_MAX */
|
|
new_pitch = GET_PITCH(speed, STRETCH_MIN);
|
|
}
|
|
else if(new_stretch > STRETCH_MAX)
|
|
{
|
|
/* we have to ignore allow_cutoff, because we can't have the */
|
|
/* stretch go higher than STRETCH_MAX */
|
|
new_pitch = GET_PITCH(speed, STRETCH_MAX);
|
|
}
|
|
|
|
if(new_stretch >= STRETCH_MAX ||
|
|
new_stretch <= STRETCH_MIN)
|
|
{
|
|
at_limit = true;
|
|
}
|
|
}
|
|
|
|
rb->sound_set_pitch(new_pitch);
|
|
|
|
return new_pitch;
|
|
}
|
|
|
|
static int32_t get_semitone_from_pitch(int32_t pitch)
|
|
{
|
|
int semitone = 0;
|
|
int32_t fractional_index = 0;
|
|
|
|
while(semitone < NUM_SEMITONES - 1 &&
|
|
pitch >= semitone_table[semitone + 1])
|
|
{
|
|
semitone++;
|
|
}
|
|
|
|
|
|
/* now find the fractional part */
|
|
while(pitch > (cent_interp[fractional_index + 1] *
|
|
semitone_table[semitone] / PITCH_SPEED_100))
|
|
{
|
|
/* Check to make sure fractional_index isn't too big */
|
|
/* This should never happen. */
|
|
if(fractional_index >= CENT_INTERP_NUM - 1)
|
|
{
|
|
break;
|
|
}
|
|
fractional_index++;
|
|
}
|
|
|
|
int32_t semitone_pitch_a = cent_interp[fractional_index] *
|
|
semitone_table[semitone] /
|
|
PITCH_SPEED_100;
|
|
int32_t semitone_pitch_b = cent_interp[fractional_index + 1] *
|
|
semitone_table[semitone] /
|
|
PITCH_SPEED_100;
|
|
/* this will be the integer offset from the cent_interp entry */
|
|
int32_t semitone_frac_ofs = (pitch - semitone_pitch_a) * CENT_INTERP_INTERVAL /
|
|
(semitone_pitch_b - semitone_pitch_a);
|
|
semitone = (semitone + SEMITONE_START) * PITCH_SPEED_PRECISION +
|
|
fractional_index * CENT_INTERP_INTERVAL +
|
|
semitone_frac_ofs;
|
|
|
|
return semitone;
|
|
}
|
|
|
|
static int32_t get_pitch_from_semitone(int32_t semitone)
|
|
{
|
|
int32_t adjusted_semitone = semitone - SEMITONE_START * PITCH_SPEED_PRECISION;
|
|
|
|
/* Find the index into the semitone table */
|
|
int32_t semitone_index = (adjusted_semitone / PITCH_SPEED_PRECISION);
|
|
|
|
/* set pitch to the semitone's integer part value */
|
|
int32_t pitch = semitone_table[semitone_index];
|
|
/* get the range of the cent modification for future calculation */
|
|
int32_t pitch_mod_a =
|
|
cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
|
|
CENT_INTERP_INTERVAL];
|
|
int32_t pitch_mod_b =
|
|
cent_interp[(adjusted_semitone % PITCH_SPEED_PRECISION) /
|
|
CENT_INTERP_INTERVAL + 1];
|
|
/* figure out the cent mod amount based on the semitone fractional value */
|
|
int32_t pitch_mod = pitch_mod_a + (pitch_mod_b - pitch_mod_a) *
|
|
(adjusted_semitone % CENT_INTERP_INTERVAL) / CENT_INTERP_INTERVAL;
|
|
|
|
/* modify pitch based on the mod amount we just calculated */
|
|
return (pitch * pitch_mod + PITCH_SPEED_100 / 2) / PITCH_SPEED_100;
|
|
}
|
|
|
|
static int32_t pitch_increase_semitone(int32_t pitch,
|
|
int32_t current_semitone,
|
|
int32_t semitone_delta
|
|
, int32_t speed
|
|
)
|
|
{
|
|
int32_t new_semitone = current_semitone;
|
|
|
|
/* snap to the delta interval */
|
|
if(current_semitone % semitone_delta != 0)
|
|
{
|
|
if(current_semitone > 0 && semitone_delta > 0)
|
|
new_semitone += semitone_delta;
|
|
else if(current_semitone < 0 && semitone_delta < 0)
|
|
new_semitone += semitone_delta;
|
|
|
|
new_semitone -= new_semitone % semitone_delta;
|
|
}
|
|
else
|
|
new_semitone += semitone_delta;
|
|
|
|
/* clamp the pitch so it doesn't go beyond the pitch limits */
|
|
if(new_semitone < (SEMITONE_START * PITCH_SPEED_PRECISION))
|
|
{
|
|
new_semitone = SEMITONE_START * PITCH_SPEED_PRECISION;
|
|
at_limit = true;
|
|
}
|
|
else if(new_semitone > (SEMITONE_END * PITCH_SPEED_PRECISION))
|
|
{
|
|
new_semitone = SEMITONE_END * PITCH_SPEED_PRECISION;
|
|
at_limit = true;
|
|
}
|
|
|
|
int32_t new_pitch = get_pitch_from_semitone(new_semitone);
|
|
|
|
int32_t new_stretch = GET_STRETCH(new_pitch, speed);
|
|
|
|
/* clamp the pitch so it doesn't go beyond the stretch limits */
|
|
if( new_stretch > STRETCH_MAX)
|
|
{
|
|
new_pitch = GET_PITCH(speed, STRETCH_MAX);
|
|
new_semitone = get_semitone_from_pitch(new_pitch);
|
|
at_limit = true;
|
|
}
|
|
else if (new_stretch < STRETCH_MIN)
|
|
{
|
|
new_pitch = GET_PITCH(speed, STRETCH_MIN);
|
|
new_semitone = get_semitone_from_pitch(new_pitch);
|
|
at_limit = true;
|
|
}
|
|
|
|
pitch_increase(pitch, new_pitch - pitch, false
|
|
, speed
|
|
);
|
|
|
|
return new_semitone;
|
|
}
|
|
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
/*
|
|
* Check for touchscreen presses as per sketch above in this file
|
|
*
|
|
* goes through each row of the, checks whether the touchscreen
|
|
* was pressed in it. Then it looks the columns of each row for specific actions
|
|
*/
|
|
static int pitchscreen_do_touchscreen(struct viewport vps[])
|
|
{
|
|
short x, y;
|
|
struct viewport *this_vp = &vps[PITCH_TOP];
|
|
int ret;
|
|
static bool wait_for_release = false;
|
|
ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
|
|
|
|
/* top row */
|
|
if (ret > ACTION_UNKNOWN)
|
|
{ /* press on top row, left or right column
|
|
* only toggle mode if released */
|
|
int column = this_vp->width / 3;
|
|
if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
|
|
return ACTION_PS_TOGGLE_MODE;
|
|
|
|
|
|
else if (x >= column && x <= (2*column))
|
|
{ /* center column pressed */
|
|
if (ret == BUTTON_REPEAT)
|
|
return ACTION_PS_INC_BIG;
|
|
else if (ret & BUTTON_REL)
|
|
return ACTION_PS_INC_SMALL;
|
|
}
|
|
return ACTION_NONE;
|
|
}
|
|
|
|
/* now the center row */
|
|
this_vp = &vps[PITCH_MID];
|
|
ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
|
|
|
|
if (ret > ACTION_UNKNOWN)
|
|
{
|
|
int column = this_vp->width / 3;
|
|
|
|
if (x < column)
|
|
{ /* left column */
|
|
if (ret & BUTTON_REL)
|
|
{
|
|
wait_for_release = false;
|
|
return ACTION_PS_NUDGE_LEFTOFF;
|
|
}
|
|
else if (ret & BUTTON_REPEAT)
|
|
return ACTION_PS_SLOWER;
|
|
if (!wait_for_release)
|
|
{
|
|
wait_for_release = true;
|
|
return ACTION_PS_NUDGE_LEFT;
|
|
}
|
|
}
|
|
else if (x > (2*column))
|
|
{ /* right column */
|
|
if (ret & BUTTON_REL)
|
|
{
|
|
wait_for_release = false;
|
|
return ACTION_PS_NUDGE_RIGHTOFF;
|
|
}
|
|
else if (ret & BUTTON_REPEAT)
|
|
return ACTION_PS_FASTER;
|
|
if (!wait_for_release)
|
|
{
|
|
wait_for_release = true;
|
|
return ACTION_PS_NUDGE_RIGHT;
|
|
}
|
|
}
|
|
else
|
|
/* center column was pressed */
|
|
return ACTION_PS_RESET;
|
|
}
|
|
|
|
/* now the bottom row */
|
|
this_vp = &vps[PITCH_BOTTOM];
|
|
ret = rb->action_get_touchscreen_press_in_vp(&x, &y, this_vp);
|
|
|
|
if (ret > ACTION_UNKNOWN)
|
|
{
|
|
int column = this_vp->width / 3;
|
|
|
|
/* left or right column is exit */
|
|
if ((x < column || x > (2*column)) && (ret == BUTTON_REL))
|
|
return ACTION_PS_EXIT;
|
|
else if (x >= column && x <= (2*column))
|
|
{ /* center column was pressed */
|
|
if (ret & BUTTON_REPEAT)
|
|
return ACTION_PS_DEC_BIG;
|
|
else if (ret & BUTTON_REL)
|
|
return ACTION_PS_DEC_SMALL;
|
|
}
|
|
return ACTION_NONE;
|
|
}
|
|
return ACTION_NONE;
|
|
}
|
|
|
|
#endif
|
|
/*
|
|
returns:
|
|
0 on exit
|
|
1 if USB was connected
|
|
*/
|
|
|
|
int gui_syncpitchscreen_run(void)
|
|
{
|
|
int button;
|
|
int32_t pitch = rb->sound_get_pitch();
|
|
int32_t semitone;
|
|
|
|
int32_t new_pitch;
|
|
int32_t pitch_delta;
|
|
bool nudged = false;
|
|
int i, updated = 4, decimals = 0;
|
|
bool exit = false;
|
|
/* should maybe be passed per parameter later, not needed for now */
|
|
struct viewport parent[NB_SCREENS];
|
|
struct viewport pitch_viewports[NB_SCREENS][PITCH_ITEM_COUNT];
|
|
int max_lines[NB_SCREENS];
|
|
|
|
//push_current_activity(ACTIVITY_PITCHSCREEN);
|
|
|
|
int32_t new_speed = 0, new_stretch;
|
|
|
|
/* the speed variable holds the apparent speed of the playback */
|
|
int32_t speed;
|
|
if (rb->dsp_timestretch_available())
|
|
{
|
|
speed = GET_SPEED(pitch, rb->dsp_get_timestretch());
|
|
}
|
|
else
|
|
{
|
|
speed = pitch;
|
|
}
|
|
|
|
|
|
|
|
/* Count decimals for speaking */
|
|
for (i = PITCH_SPEED_PRECISION; i >= 10; i /= 10)
|
|
decimals++;
|
|
|
|
/* set the semitone index based on the current pitch */
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
|
|
/* initialize pitchscreen vps */
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
rb->viewport_set_defaults(&parent[i], i);
|
|
max_lines[i] = viewport_get_nb_lines(&parent[i]);
|
|
pitchscreen_fix_viewports(&parent[i], pitch_viewports[i]);
|
|
rb->screens[i]->set_viewport(&parent[i]);
|
|
rb->screens[i]->clear_viewport();
|
|
|
|
/* also, draw the icons now, it's only needed once */
|
|
pitchscreen_draw_icons(rb->screens[i], &parent[i]);
|
|
}
|
|
|
|
|
|
while (!exit)
|
|
{
|
|
FOR_NB_SCREENS(i)
|
|
pitchscreen_draw(rb->screens[i], max_lines[i],
|
|
pitch_viewports[i], pitch, semitone
|
|
, speed
|
|
);
|
|
pitch_delta = 0;
|
|
new_speed = 0;
|
|
|
|
if (rb->global_settings->talk_menu && updated)
|
|
{
|
|
rb->talk_shutup();
|
|
switch (updated)
|
|
{
|
|
case 1:
|
|
if (rb->global_settings->pitch_mode_semitone)
|
|
rb->talk_value_decimal(semitone, UNIT_SIGNED, decimals, false);
|
|
else
|
|
rb->talk_value_decimal(pitch, UNIT_PERCENT, decimals, false);
|
|
break;
|
|
case 2:
|
|
rb->talk_value_decimal(speed, UNIT_PERCENT, decimals, false);
|
|
break;
|
|
case 3:
|
|
speak_pitch_mode(false);
|
|
break;
|
|
case 4:
|
|
if (rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available())
|
|
rb->talk_id(LANG_PITCH, false);
|
|
else
|
|
rb->talk_id(LANG_PLAYBACK_RATE, false);
|
|
rb->talk_value_decimal(pitch, UNIT_PERCENT, decimals, true);
|
|
if (rb->global_settings->pitch_mode_timestretch && rb->dsp_timestretch_available())
|
|
{
|
|
rb->talk_id(LANG_SPEED, true);
|
|
rb->talk_value_decimal(speed, UNIT_PERCENT, decimals, true);
|
|
}
|
|
speak_pitch_mode(true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
updated = 0;
|
|
|
|
button = rb->get_action(CONTEXT_PITCHSCREEN, HZ);
|
|
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
if (button == ACTION_TOUCHSCREEN)
|
|
{
|
|
FOR_NB_SCREENS(i)
|
|
button = pitchscreen_do_touchscreen(pitch_viewports[i]);
|
|
}
|
|
#endif
|
|
switch (button)
|
|
{
|
|
case ACTION_PS_INC_SMALL:
|
|
if(rb->global_settings->pitch_mode_semitone)
|
|
pitch_delta = SEMITONE_SMALL_DELTA;
|
|
else
|
|
pitch_delta = PITCH_SMALL_DELTA;
|
|
updated = 1;
|
|
break;
|
|
|
|
case ACTION_PS_INC_BIG:
|
|
if(rb->global_settings->pitch_mode_semitone)
|
|
pitch_delta = SEMITONE_BIG_DELTA;
|
|
else
|
|
pitch_delta = PITCH_BIG_DELTA;
|
|
updated = 1;
|
|
break;
|
|
|
|
case ACTION_PS_DEC_SMALL:
|
|
if(rb->global_settings->pitch_mode_semitone)
|
|
pitch_delta = -SEMITONE_SMALL_DELTA;
|
|
else
|
|
pitch_delta = -PITCH_SMALL_DELTA;
|
|
updated = 1;
|
|
break;
|
|
|
|
case ACTION_PS_DEC_BIG:
|
|
if(rb->global_settings->pitch_mode_semitone)
|
|
pitch_delta = -SEMITONE_BIG_DELTA;
|
|
else
|
|
pitch_delta = -PITCH_BIG_DELTA;
|
|
updated = 1;
|
|
break;
|
|
|
|
case ACTION_PS_NUDGE_RIGHT:
|
|
if (!rb->global_settings->pitch_mode_timestretch)
|
|
{
|
|
new_pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
|
|
, speed
|
|
);
|
|
nudged = (new_pitch != pitch);
|
|
pitch = new_pitch;
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
speed = pitch;
|
|
updated = nudged ? 1 : 0;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
new_speed = speed + SPEED_SMALL_DELTA;
|
|
at_limit = false;
|
|
updated = 2;
|
|
}
|
|
break;
|
|
|
|
case ACTION_PS_FASTER:
|
|
if (rb->global_settings->pitch_mode_timestretch)
|
|
{
|
|
new_speed = speed + SPEED_BIG_DELTA;
|
|
/* snap to whole numbers */
|
|
if(new_speed % PITCH_SPEED_PRECISION != 0)
|
|
new_speed -= new_speed % PITCH_SPEED_PRECISION;
|
|
at_limit = false;
|
|
updated = 2;
|
|
}
|
|
break;
|
|
|
|
case ACTION_PS_NUDGE_RIGHTOFF:
|
|
if (nudged)
|
|
{
|
|
pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
|
|
, speed
|
|
);
|
|
speed = pitch;
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
nudged = false;
|
|
updated = 1;
|
|
}
|
|
break;
|
|
|
|
case ACTION_PS_NUDGE_LEFT:
|
|
if (!rb->global_settings->pitch_mode_timestretch)
|
|
{
|
|
new_pitch = pitch_increase(pitch, -PITCH_NUDGE_DELTA, false
|
|
, speed
|
|
);
|
|
nudged = (new_pitch != pitch);
|
|
pitch = new_pitch;
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
speed = pitch;
|
|
updated = nudged ? 1 : 0;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
new_speed = speed - SPEED_SMALL_DELTA;
|
|
at_limit = false;
|
|
updated = 2;
|
|
}
|
|
break;
|
|
|
|
case ACTION_PS_SLOWER:
|
|
if (rb->global_settings->pitch_mode_timestretch)
|
|
{
|
|
new_speed = speed - SPEED_BIG_DELTA;
|
|
/* snap to whole numbers */
|
|
if(new_speed % PITCH_SPEED_PRECISION != 0)
|
|
new_speed += PITCH_SPEED_PRECISION - speed % PITCH_SPEED_PRECISION;
|
|
at_limit = false;
|
|
updated = 2;
|
|
}
|
|
break;
|
|
|
|
case ACTION_PS_NUDGE_LEFTOFF:
|
|
if (nudged)
|
|
{
|
|
pitch = pitch_increase(pitch, PITCH_NUDGE_DELTA, false
|
|
, speed
|
|
);
|
|
speed = pitch;
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
nudged = false;
|
|
updated = 1;
|
|
}
|
|
break;
|
|
|
|
case ACTION_PS_RESET:
|
|
pitch = PITCH_SPEED_100;
|
|
rb->sound_set_pitch(pitch);
|
|
speed = PITCH_SPEED_100;
|
|
if (rb->dsp_timestretch_available())
|
|
{
|
|
rb->dsp_set_timestretch(PITCH_SPEED_100);
|
|
at_limit = false;
|
|
}
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
updated = 4;
|
|
break;
|
|
|
|
case ACTION_PS_TOGGLE_MODE:
|
|
rb->global_settings->pitch_mode_semitone = !rb->global_settings->pitch_mode_semitone;
|
|
|
|
if (rb->dsp_timestretch_available() && !rb->global_settings->pitch_mode_semitone)
|
|
{
|
|
rb->global_settings->pitch_mode_timestretch = !rb->global_settings->pitch_mode_timestretch;
|
|
if(!rb->global_settings->pitch_mode_timestretch)
|
|
{
|
|
/* no longer in timestretch mode. Reset speed */
|
|
speed = pitch;
|
|
rb->dsp_set_timestretch(PITCH_SPEED_100);
|
|
}
|
|
}
|
|
rb->settings_save();
|
|
updated = 3;
|
|
break;
|
|
|
|
case ACTION_PS_EXIT:
|
|
exit = true;
|
|
break;
|
|
|
|
default:
|
|
if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
|
|
return 1;
|
|
break;
|
|
}
|
|
if (pitch_delta)
|
|
{
|
|
if (rb->global_settings->pitch_mode_semitone)
|
|
{
|
|
semitone = pitch_increase_semitone(pitch, semitone, pitch_delta
|
|
, speed
|
|
);
|
|
pitch = get_pitch_from_semitone(semitone);
|
|
}
|
|
else
|
|
{
|
|
pitch = pitch_increase(pitch, pitch_delta, true
|
|
, speed
|
|
);
|
|
semitone = get_semitone_from_pitch(pitch);
|
|
}
|
|
if (rb->global_settings->pitch_mode_timestretch)
|
|
{
|
|
/* do this to make sure we properly obey the stretch limits */
|
|
new_speed = speed;
|
|
}
|
|
else
|
|
{
|
|
speed = pitch;
|
|
}
|
|
}
|
|
|
|
if(new_speed)
|
|
{
|
|
new_stretch = GET_STRETCH(pitch, new_speed);
|
|
|
|
/* limit the amount of stretch */
|
|
if(new_stretch > STRETCH_MAX)
|
|
{
|
|
new_stretch = STRETCH_MAX;
|
|
new_speed = GET_SPEED(pitch, new_stretch);
|
|
}
|
|
else if(new_stretch < STRETCH_MIN)
|
|
{
|
|
new_stretch = STRETCH_MIN;
|
|
new_speed = GET_SPEED(pitch, new_stretch);
|
|
}
|
|
|
|
new_stretch = GET_STRETCH(pitch, new_speed);
|
|
if(new_stretch >= STRETCH_MAX ||
|
|
new_stretch <= STRETCH_MIN)
|
|
{
|
|
at_limit = true;
|
|
}
|
|
|
|
/* set the amount of stretch */
|
|
rb->dsp_set_timestretch(new_stretch);
|
|
|
|
/* update the speed variable with the new speed */
|
|
speed = new_speed;
|
|
|
|
/* Reset new_speed so we only call dsp_set_timestretch */
|
|
/* when needed */
|
|
new_speed = 0;
|
|
}
|
|
}
|
|
|
|
//rb->pcmbuf_set_low_latency(false);
|
|
//pop_current_activity();
|
|
|
|
/* Clean up */
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
rb->screens[i]->set_viewport(NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int arg_callback(char argchar, const char **parameter)
|
|
{
|
|
int ret;
|
|
long num, dec;
|
|
bool bret;
|
|
//rb->splashf(100, "Arg: %c", argchar);
|
|
while (*parameter[0] > '/' && ispunct(*parameter[0])) (*parameter)++;
|
|
switch (tolower(argchar))
|
|
{
|
|
case 'q' :
|
|
pitch_vars.flags &= ~PVAR_VERBOSE;
|
|
break;
|
|
case 'g' :
|
|
pitch_vars.flags |= PVAR_GUI;
|
|
break;
|
|
case 'p' :
|
|
ret = longnum_parse(parameter, &num, &dec);
|
|
if (ret)
|
|
{
|
|
dec /= (ARGPARSE_FRAC_DEC_MULTIPLIER / PITCH_SPEED_PRECISION);
|
|
if (num < 0)
|
|
dec = -dec;
|
|
pitch_vars.pitch = (num * PITCH_SPEED_PRECISION + (dec % PITCH_SPEED_PRECISION));
|
|
}
|
|
break;
|
|
case 'k' :
|
|
ret = bool_parse(parameter, &bret);
|
|
if (ret)
|
|
{
|
|
if(!bret && rb->dsp_timestretch_available())
|
|
{
|
|
/* no longer in timestretch mode. Reset speed */
|
|
rb->dsp_set_timestretch(PITCH_SPEED_100);
|
|
}
|
|
rb->dsp_timestretch_enable(bret);
|
|
if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
|
|
rb->splashf(HZ, "Timestretch: %s", bret ? "true" : "false");
|
|
int n = 10; /* 1 second */
|
|
while (bret && n-- > 0 && !rb->dsp_timestretch_available())
|
|
{
|
|
rb->sleep(HZ / 10);
|
|
}
|
|
}
|
|
break;
|
|
case 's' :
|
|
ret = longnum_parse(parameter, &num, &dec);
|
|
if (ret && rb->dsp_timestretch_available())
|
|
{
|
|
dec /= (ARGPARSE_FRAC_DEC_MULTIPLIER / PITCH_SPEED_PRECISION);
|
|
if (num < 0)
|
|
break;
|
|
pitch_vars.speed = (num * PITCH_SPEED_PRECISION + (dec % PITCH_SPEED_PRECISION));
|
|
|
|
}
|
|
break;
|
|
default :
|
|
rb->splashf(HZ, "Unknown switch '%c'",argchar);
|
|
//return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void fill_pitchvars(struct pvars *pv)
|
|
{
|
|
if (!pv)
|
|
return;
|
|
pv->pitch = rb->sound_get_pitch();
|
|
|
|
/* the speed variable holds the apparent speed of the playback */
|
|
if (rb->dsp_timestretch_available())
|
|
{
|
|
pv->speed = GET_SPEED(pv->pitch, rb->dsp_get_timestretch());
|
|
}
|
|
else
|
|
{
|
|
pv->speed = pv->pitch;
|
|
}
|
|
|
|
pv->stretch = GET_STRETCH(pv->pitch, pv->speed);
|
|
pv->flags |= PVAR_VERBOSE;
|
|
|
|
}
|
|
/* plugin entry point */
|
|
enum plugin_status plugin_start(const void* parameter)
|
|
{
|
|
/* pitch_screen
|
|
* accepts args -q, -g, -p=, -s=, -k=; (= sign is optional)
|
|
* -q silences output splash
|
|
* -g runs the gui (DEFAULT)
|
|
* -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
|
|
*/
|
|
bool gui = false;
|
|
rb->pcmbuf_set_low_latency(true);
|
|
|
|
/* Figure out whether to be in timestretch mode */
|
|
if (parameter == NULL) /* gui mode */
|
|
{
|
|
if (rb->global_settings->pitch_mode_timestretch && !rb->dsp_timestretch_available())
|
|
{
|
|
rb->global_settings->pitch_mode_timestretch = false;
|
|
rb->settings_save();
|
|
}
|
|
gui = true;
|
|
}
|
|
else
|
|
{
|
|
struct pvars cur;
|
|
fill_pitchvars(&cur);
|
|
fill_pitchvars(&pitch_vars);
|
|
argparse((const char*) parameter, -1, &arg_callback);
|
|
if (pitch_vars.pitch != cur.pitch)
|
|
{
|
|
rb->sound_set_pitch(pitch_vars.pitch);
|
|
pitch_vars.pitch = rb->sound_get_pitch();
|
|
if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
|
|
rb->splashf(HZ, "pitch: %ld.%02ld%%",
|
|
pitch_vars.pitch / PITCH_SPEED_PRECISION,
|
|
pitch_vars.pitch % PITCH_SPEED_PRECISION);
|
|
}
|
|
if (pitch_vars.speed != cur.speed)
|
|
{
|
|
pitch_vars.stretch = GET_STRETCH(pitch_vars.pitch, pitch_vars.speed);
|
|
|
|
/* limit the amount of stretch */
|
|
if(pitch_vars.stretch > STRETCH_MAX)
|
|
{
|
|
pitch_vars.stretch = STRETCH_MAX;
|
|
pitch_vars.speed = GET_SPEED(pitch_vars.pitch, pitch_vars.stretch);
|
|
}
|
|
else if(pitch_vars.stretch < STRETCH_MIN)
|
|
{
|
|
pitch_vars.stretch = STRETCH_MIN;
|
|
pitch_vars.speed = GET_SPEED(pitch_vars.pitch, pitch_vars.stretch);
|
|
}
|
|
|
|
pitch_vars.stretch = GET_STRETCH(pitch_vars.pitch, pitch_vars.speed);
|
|
if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
|
|
rb->splashf(HZ, "speed: %ld.%02ld%%",
|
|
pitch_vars.speed / PITCH_SPEED_PRECISION,
|
|
pitch_vars.speed % PITCH_SPEED_PRECISION);
|
|
/* set the amount of stretch */
|
|
rb->dsp_set_timestretch(pitch_vars.stretch);
|
|
}
|
|
gui = ((pitch_vars.flags & PVAR_GUI) == PVAR_GUI);
|
|
if ((pitch_vars.flags & PVAR_VERBOSE) == PVAR_VERBOSE)
|
|
rb->splashf(HZ, "GUI: %d", gui);
|
|
|
|
}
|
|
|
|
if (gui && gui_syncpitchscreen_run() == 1)
|
|
return PLUGIN_USB_CONNECTED;
|
|
rb->pcmbuf_set_low_latency(false);
|
|
return PLUGIN_OK;
|
|
}
|